From 6845736b461fa5a5b13b7c3b49bc76275a4453e9 Mon Sep 17 00:00:00 2001 From: Kai Welke Date: Tue, 9 Jan 2024 09:33:59 +0100 Subject: [PATCH] feat: postprocess only changed files --- src/sphinxawesome_theme/__init__.py | 1 + src/sphinxawesome_theme/postprocess.py | 102 +++++++++++-------------- tests/test_external_links.py | 2 + 3 files changed, 49 insertions(+), 56 deletions(-) diff --git a/src/sphinxawesome_theme/__init__.py b/src/sphinxawesome_theme/__init__.py index 8cb16b3b5..f33d11681 100644 --- a/src/sphinxawesome_theme/__init__.py +++ b/src/sphinxawesome_theme/__init__.py @@ -185,6 +185,7 @@ def setup(app: Sphinx) -> dict[str, Any]: app.connect("html-page-context", jinja_functions.setup_jinja) app.connect("html-page-context", toc.change_toc) app.connect("build-finished", logos.copy_logos) + app.connect("env-before-read-docs", postprocess.changed_docs) app.connect("build-finished", postprocess.post_process_html) JSONHTMLBuilder.out_suffix = ".json" diff --git a/src/sphinxawesome_theme/postprocess.py b/src/sphinxawesome_theme/postprocess.py index 0e757e28b..73cf48292 100644 --- a/src/sphinxawesome_theme/postprocess.py +++ b/src/sphinxawesome_theme/postprocess.py @@ -21,11 +21,12 @@ import os import pathlib -import re from dataclasses import dataclass from bs4 import BeautifulSoup, Comment from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment +from sphinx.util.display import status_iterator from . import logos @@ -42,7 +43,15 @@ class Icons: permalinks_icon: str = '' -def _get_html_files(outdir: pathlib.Path | str) -> list[str]: +def changed_docs(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> None: + """Add a list of changed docs to the environment. + + This is useful to make sure postprocessing only runs on changed files. + """ + app.env.awesome_changed_docs = docnames # type: ignore[attr-defined] + + +def get_html_files(outdir: pathlib.Path | str) -> list[str]: """Get a list of HTML files.""" html_list = [] for root, _, files in os.walk(outdir): @@ -52,7 +61,7 @@ def _get_html_files(outdir: pathlib.Path | str) -> list[str]: return html_list -def _collapsible_nav(tree: BeautifulSoup) -> None: +def collapsible_nav(tree: BeautifulSoup) -> None: """Make navigation links with children collapsible.""" for link in tree.select("#left-sidebar a"): # Check if the link has "children" @@ -84,7 +93,7 @@ def _collapsible_nav(tree: BeautifulSoup) -> None: link.append(button) -def _remove_empty_toctree(tree: BeautifulSoup) -> None: +def remove_empty_toctree(tree: BeautifulSoup) -> None: """Remove empty toctree divs. If you include a `toctree` with the `hidden` option, @@ -97,7 +106,7 @@ def _remove_empty_toctree(tree: BeautifulSoup) -> None: div.extract() -def _headerlinks(tree: BeautifulSoup) -> None: +def headerlinks(tree: BeautifulSoup) -> None: """Make headerlinks copy their URL on click.""" for link in tree("a", class_="headerlink"): link[ @@ -108,7 +117,7 @@ def _headerlinks(tree: BeautifulSoup) -> None: link["data-tooltip"] = "Copy link to this element" -def _scrollspy(tree: BeautifulSoup) -> None: +def scrollspy(tree: BeautifulSoup) -> None: """Add an active class to current TOC links in the right sidebar.""" for link in tree("a", class_="headerlink"): if link.parent.name in ["h2", "h3"] or ( @@ -124,7 +133,7 @@ def _scrollspy(tree: BeautifulSoup) -> None: link[":data-current"] = f"activeSection === '{active_link}'" -def _external_links(tree: BeautifulSoup) -> None: +def external_links(tree: BeautifulSoup) -> None: """Add `rel="nofollow noopener"` to external links. The alternative was to copy `visit_reference` in the HTMLTranslator @@ -136,46 +145,14 @@ def _external_links(tree: BeautifulSoup) -> None: link.append(BeautifulSoup(Icons.external_link, "html.parser").svg) -def _strip_comments(tree: BeautifulSoup) -> None: +def strip_comments(tree: BeautifulSoup) -> None: """Remove HTML comments from documents.""" comments = tree.find_all(string=lambda text: isinstance(text, Comment)) for c in comments: c.extract() -def _code_headers(tree: BeautifulSoup) -> None: - """Add the programming language to a code block.""" - # Find all "
None: +def modify_html(html_filename: str, app: Sphinx) -> None: """Modify a single HTML document. 1. The HTML document is parsed into a BeautifulSoup tree. @@ -188,32 +165,45 @@ def _modify_html(html_filename: str, app: Sphinx) -> None: theme_options = logos.get_theme_options(app) - _collapsible_nav(tree) + collapsible_nav(tree) if theme_options.get("awesome_external_links"): - _external_links(tree) - _remove_empty_toctree(tree) - _scrollspy(tree) + external_links(tree) + remove_empty_toctree(tree) + scrollspy(tree) if theme_options.get("awesome_headerlinks"): - _headerlinks(tree) - # _code_headers(tree) - _strip_comments(tree) + headerlinks(tree) + strip_comments(tree) with open(html_filename, "w", encoding="utf-8") as out_file: out_file.write(str(tree)) def post_process_html(app: Sphinx, exc: Exception | None) -> None: - """Perform modifications on the HTML after building. + """Modify the HTML after building. - This is an extra function, that gets a list from all HTML - files in the output directory, then runs the ``_modify_html`` - function on each of them. + Make some changes that are easier to do in the final HTML rather than + the tree of nodes. + This function requires, that the list of files to postprocess is in the + environment as ``app.env.awesome_changed_docs``. """ + if exc is not None: + return + if app.builder is not None and app.builder.name not in ["html", "dirhtml"]: return - if exc is None: - html_files = _get_html_files(app.outdir) + files_to_postprocess = [ + app.builder.get_outfilename(doc) for doc in app.env.awesome_changed_docs # type: ignore[attr-defined] + ] + + if len(files_to_postprocess) == 0: + return - for doc in html_files: - _modify_html(doc, app) + for doc in status_iterator( + files_to_postprocess, + "postprocess html... ", + "brown", + len(files_to_postprocess), + app.verbosity, + ): + modify_html(doc, app) diff --git a/tests/test_external_links.py b/tests/test_external_links.py index 0a2d58602..8cacd567c 100644 --- a/tests/test_external_links.py +++ b/tests/test_external_links.py @@ -11,6 +11,7 @@ @pytest.mark.sphinx( "html", testroot="external", + freshenv=True, confoverrides={ "html_theme": "sphinxawesome_theme", }, @@ -29,6 +30,7 @@ def test_no_external_link_icons(app: Sphinx) -> None: @pytest.mark.sphinx( "html", testroot="external", + freshenv=True, confoverrides={ "html_theme": "sphinxawesome_theme", "html_theme_options": {