diff --git a/content/booklets/adacore-technologies-for-railway-software/index.rst b/content/booklets/adacore-technologies-for-railway-software/index.rst index cd6ee4dee..f7a541c6e 100644 --- a/content/booklets/adacore-technologies-for-railway-software/index.rst +++ b/content/booklets/adacore-technologies-for-railway-software/index.rst @@ -139,6 +139,8 @@ software, please visit :footcite:`Railway_SW_AdaCore_Web_Rail`. | Ben Brosgol, AdaCore | September 2025 +.. footbibliography:: + .. toctree:: :maxdepth: 4 :numbered: diff --git a/content/index.rst b/content/index.rst index ecd6ca420..01e8d97d1 100644 --- a/content/index.rst +++ b/content/index.rst @@ -126,15 +126,13 @@ Introduction to Ada: Laboratories Bug Free Coding - .. only:: no_hidden_books - - .. toctree:: - :maxdepth: 1 - :caption: AdaCore Technologies Booklets + .. toctree:: + :maxdepth: 1 + :caption: AdaCore Technologies Booklets - AdaCore Technologies For Airborne Software - AdaCore Technologies For Railway Software - AdaCore Technologies For Space Systems Software + AdaCore Technologies For Airborne Software + AdaCore Technologies For Railway Software + AdaCore Technologies For Space Systems Software .. only:: builder_html diff --git a/frontend/Makefile b/frontend/Makefile index 4b2b6a8c9..b0df5236b 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -212,7 +212,7 @@ test_engine: webpack-production # @echo "" test_parser: ## Test the parser. - @coverage run --source=widget,rst_code_example_pipeline.chop,rst_code_example_pipeline.resource -m unittest discover --start-directory sphinx + @coverage run --source=widget,footnote_checks,rst_code_example_pipeline.chop,rst_code_example_pipeline.resource -m unittest discover --start-directory sphinx @coverage report --fail-under=90 -m test_rst_pipeline: ## Test the rst_code_example_pipeline package (epub VM). @@ -352,7 +352,7 @@ define build_complete_book $(eval TARGET_TYPE_LC := $(call lowercase_word,$(TARGET_TYPE))) @rm -rf "$(BUILDDIR)/doctrees" $(eval SPHINX_CONTENT_DIR := $(CONTENT_DIR)) - $(call build_book_complete_preps_$(TARGET_TYPE_LC)) + $(call build_complete_book_preps_$(TARGET_TYPE_LC)) $(call build_book_generic,$(TARGET_NAME),$(TARGET_TYPE),$(SPHINX_CONTENT_DIR)) endef diff --git a/frontend/sphinx/conf.py b/frontend/sphinx/conf.py index 84693db65..70d5bafc0 100644 --- a/frontend/sphinx/conf.py +++ b/frontend/sphinx/conf.py @@ -104,6 +104,7 @@ def get_file_from_conf_ini(path_to_file): 'sphinx.ext.graphviz', 'sphinx.ext.extlinks', 'widget_extension', + 'footnote_checks', 'sphinx_reredirects', 'sphinxcontrib.plantuml', ] diff --git a/frontend/sphinx/footnote_checks.py b/frontend/sphinx/footnote_checks.py new file mode 100644 index 000000000..a5ecbf692 --- /dev/null +++ b/frontend/sphinx/footnote_checks.py @@ -0,0 +1,51 @@ +"""Reject footnote references whose definitions are absent from the output.""" + +from docutils import nodes +from sphinx.errors import SphinxError +from sphinx.transforms.post_transforms import SphinxPostTransform + + +class CheckFootnoteDefinitions(SphinxPostTransform): + # Check after conditional content is removed, before LaTeX's URL-footnote + # numbering can make a missing definition look like a repeated reference. + default_priority = 390 + + def run(self, **kwargs): + current_docname = self.env.current_document.docname + definitions = { + (footnote.get('docname', current_docname), identifier) + for footnote in self.document.findall(nodes.footnote) + for identifier in footnote['ids'] + } + for reference in self.document.findall(nodes.footnote_reference): + identifier = reference.get('refid') + if identifier is None: + # Docutils reports references that could not resolve an ID. + continue + docname = reference.get('docname', current_docname) + if (docname, identifier) in definitions: + continue + + location = str(self.env.doc2path(docname)) + ancestor = reference + while ancestor is not None: + if ancestor.line is not None: + location += f':{ancestor.line}' + break + ancestor = ancestor.parent + raise SphinxError( + f"{location}: footnote reference {identifier!r} has no " + "definition in this document. Add the missing footnote " + "definition; for :footcite: citations, place " + "'.. footbibliography::' after the last citation in the " + "document." + ) + + +def setup(app): + app.add_post_transform(CheckFootnoteDefinitions) + return { + 'version': '1.0', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + }