From 7f55f21a60552fc096ec12111e76bf5acf763ce4 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Fri, 6 Mar 2026 18:46:14 -0800 Subject: [PATCH 01/10] Support external theme packages for SCSS compilation Refactor SCSS folder resolution to use a method that imports the theme module and retrieves the SCSS sources path, with a fallback to the bundled theme. --- sphinx_simplepdf/builders/simplepdf.py | 37 +++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index 509b486..6925b79 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -1,3 +1,4 @@ +import importlib import os import re import subprocess @@ -55,15 +56,7 @@ def __init__(self, *args, **kwargs): # Generate main.css logger.info("Generating css files from scss-templates") css_folder = os.path.join(self.app.outdir, "_static") - scss_folder = os.path.join( - os.path.dirname(__file__), - "..", - "themes", - "simplepdf_theme", - "static", - "styles", - "sources", - ) + scss_folder = self._resolve_scss_folder() sass.compile( dirname=(scss_folder, css_folder), output_style="nested", @@ -105,6 +98,32 @@ def get_theme_option_var(self, name, default): return default return simplepdf_theme_options[name] + def _resolve_scss_folder(self): + """Resolve the SCSS sources folder from the configured theme package. + + Tries to import the theme module specified by simplepdf_theme and use + its get_scss_sources_path() if available. Falls back to the bundled + simplepdf_theme if the external theme cannot be found. + """ + theme_name = self.app.config.simplepdf_theme or "simplepdf_theme" + try: + theme_module = importlib.import_module(theme_name) + if hasattr(theme_module, "get_scss_sources_path"): + return theme_module.get_scss_sources_path() + return os.path.join( + os.path.dirname(theme_module.__file__), + "static", "styles", "sources", + ) + except ImportError: + logger.info( + f"Could not import theme '{theme_name}', " + "falling back to bundled simplepdf_theme" + ) + return os.path.join( + os.path.dirname(__file__), "..", "themes", + "simplepdf_theme", "static", "styles", "sources", + ) + def finish(self) -> None: super().finish() From f42b4378523c4f8f0703689e56864349dedc40d9 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Tue, 10 Mar 2026 12:18:58 -0700 Subject: [PATCH 02/10] Improve support for external theme packages by using a get_scss_sources_path() convention --- docs/changelog.rst | 8 ++++++ docs/configuration.rst | 28 ++++++++++++++++++- pyproject.toml | 2 +- sphinx_simplepdf/builders/simplepdf.py | 26 +++++++++-------- .../themes/simplepdf_theme/__init__.py | 5 ++++ 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 97268e8..919cd48 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog ========= +Release 1.8 +----------- +:released: unreleased + +* **Bugfix**: Improve support for external theme packages by using a ``get_scss_sources_path()`` convention. + + - If needed, theme warnings can be suppressed via ``suppress_warnings = ["simplepdf.theme"]``. + Release 1.7 ----------- :released: 02.12.2025 diff --git a/docs/configuration.rst b/docs/configuration.rst index 233c40e..7cb01bd 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -166,7 +166,33 @@ simplepdf_theme --------------- .. versionadded:: 1.5 -Add custom theme for simplepdf. This overrides the default theme ``simplepdf_theme`` +Name of the theme to use for PDF output. This overrides the default theme ``simplepdf_theme``. +The value must match both the Sphinx theme name and the importable Python module name. + +.. code-block:: python + + simplepdf_theme = "my_custom_pdf_theme" + +The theme module must define a ``get_scss_sources_path()`` function that returns +the absolute path to its SCSS sources directory. This is how the builder locates +the SCSS files to compile into CSS for the PDF. + +**Minimal example:** + +.. code-block:: python + + from os import path + + def get_scss_sources_path(): + """Return the absolute path to the SCSS sources directory.""" + return path.join(path.abspath(path.dirname(__file__)), "static", "styles", "sources") + +The SCSS sources directory should contain a ``main.scss`` file as the entry point. +You can use the bundled ``simplepdf_theme`` as a reference for the expected +directory structure and SCSS files. + +.. note:: If the theme module cannot be imported or does not define ``get_scss_sources_path()``, + the builder falls back to the bundled ``simplepdf_theme`` SCSS sources and emits a warning. .. _theme_options: diff --git a/pyproject.toml b/pyproject.toml index e242d24..0735b33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "sphinx-simplepdf" -version = "1.7.0" +version = "1.8.0" description = "An easy to use PDF Builder for Sphinx with a modern PDF-Theme." readme = "README.rst" license = "MIT" diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index 6925b79..a88f943 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -101,28 +101,30 @@ def get_theme_option_var(self, name, default): def _resolve_scss_folder(self): """Resolve the SCSS sources folder from the configured theme package. - Tries to import the theme module specified by simplepdf_theme and use - its get_scss_sources_path() if available. Falls back to the bundled - simplepdf_theme if the external theme cannot be found. + Imports the theme module and calls its get_scss_sources_path(). Falls + back to the bundled simplepdf_theme if the theme cannot be imported or + does not define get_scss_sources_path(). """ theme_name = self.app.config.simplepdf_theme or "simplepdf_theme" try: theme_module = importlib.import_module(theme_name) if hasattr(theme_module, "get_scss_sources_path"): return theme_module.get_scss_sources_path() - return os.path.join( - os.path.dirname(theme_module.__file__), - "static", "styles", "sources", + logger.warning( + f"Theme '{theme_name}' does not define get_scss_sources_path(), " + "falling back to bundled simplepdf_theme", + type="simplepdf", + subtype="theme", ) except ImportError: - logger.info( + logger.warning( f"Could not import theme '{theme_name}', " - "falling back to bundled simplepdf_theme" - ) - return os.path.join( - os.path.dirname(__file__), "..", "themes", - "simplepdf_theme", "static", "styles", "sources", + "falling back to bundled simplepdf_theme", + type="simplepdf", + subtype="theme", ) + from sphinx_simplepdf.themes.simplepdf_theme import get_scss_sources_path + return get_scss_sources_path() def finish(self) -> None: super().finish() diff --git a/sphinx_simplepdf/themes/simplepdf_theme/__init__.py b/sphinx_simplepdf/themes/simplepdf_theme/__init__.py index 8a873f5..bbc3bcd 100644 --- a/sphinx_simplepdf/themes/simplepdf_theme/__init__.py +++ b/sphinx_simplepdf/themes/simplepdf_theme/__init__.py @@ -14,6 +14,11 @@ def get_html_theme_path(): return cur_dir +def get_scss_sources_path(): + """Return the absolute path to the SCSS sources directory.""" + return path.join(path.abspath(path.dirname(__file__)), "static", "styles", "sources") + + # See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package def setup(app): app.add_html_theme("simplepdf_theme", path.abspath(path.dirname(__file__))) From 4c8e24493980163cee81a0e0a2ab7315d40d22de Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Tue, 10 Mar 2026 12:54:48 -0700 Subject: [PATCH 03/10] Add PR reference to changelog bugfix entry --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 919cd48..a1c923d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,7 +5,7 @@ Release 1.8 ----------- :released: unreleased -* **Bugfix**: Improve support for external theme packages by using a ``get_scss_sources_path()`` convention. +* **Bugfix**: [#134] Improve support for external theme packages by using a ``get_scss_sources_path()`` convention. - If needed, theme warnings can be suppressed via ``suppress_warnings = ["simplepdf.theme"]``. From 7ffa7cbf1bbbb029093c9fef92ce61356ee73390 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Wed, 1 Apr 2026 11:38:38 -0700 Subject: [PATCH 04/10] chore: keep package version on 1.7.0 --- docs/changelog.rst | 7 +++---- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a1c923d..89e46f9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,11 +1,10 @@ Changelog ========= -Release 1.8 ------------ -:released: unreleased +Unreleased +---------- -* **Bugfix**: [#134] Improve support for external theme packages by using a ``get_scss_sources_path()`` convention. +* **Bugfix** [#134] Improve support for external theme packages by using a ``get_scss_sources_path()`` convention. - If needed, theme warnings can be suppressed via ``suppress_warnings = ["simplepdf.theme"]``. diff --git a/pyproject.toml b/pyproject.toml index 0735b33..e242d24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name = "sphinx-simplepdf" -version = "1.8.0" +version = "1.7.0" description = "An easy to use PDF Builder for Sphinx with a modern PDF-Theme." readme = "README.rst" license = "MIT" From 3057a9154d66f549785f69987c456ca6b8ea1312 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Wed, 1 Apr 2026 14:48:01 -0700 Subject: [PATCH 05/10] test: add integration coverage for external theme SCSS resolution --- tests/doc_test/with_external_theme/conf.py | 15 +++++++++ tests/doc_test/with_external_theme/index.rst | 4 +++ .../stub_external_pdf_theme/__init__.py | 13 ++++++++ .../static/styles/sources/main.scss | 6 ++++ .../stub_external_pdf_theme/theme.conf | 4 +++ .../with_theme_missing_scss_hook/conf.py | 15 +++++++++ .../with_theme_missing_scss_hook/index.rst | 4 +++ .../stub_theme_no_scss_hook/__init__.py | 8 +++++ .../stub_theme_no_scss_hook/theme.conf | 4 +++ tests/test_external_theme.py | 31 +++++++++++++++++++ 10 files changed, 104 insertions(+) create mode 100644 tests/doc_test/with_external_theme/conf.py create mode 100644 tests/doc_test/with_external_theme/index.rst create mode 100644 tests/doc_test/with_external_theme/stub_external_pdf_theme/__init__.py create mode 100644 tests/doc_test/with_external_theme/stub_external_pdf_theme/static/styles/sources/main.scss create mode 100644 tests/doc_test/with_external_theme/stub_external_pdf_theme/theme.conf create mode 100644 tests/doc_test/with_theme_missing_scss_hook/conf.py create mode 100644 tests/doc_test/with_theme_missing_scss_hook/index.rst create mode 100644 tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/__init__.py create mode 100644 tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/theme.conf create mode 100644 tests/test_external_theme.py diff --git a/tests/doc_test/with_external_theme/conf.py b/tests/doc_test/with_external_theme/conf.py new file mode 100644 index 0000000..9d91b4c --- /dev/null +++ b/tests/doc_test/with_external_theme/conf.py @@ -0,0 +1,15 @@ +"""Sphinx project that loads a test-only external PDF theme package.""" + +from pathlib import Path +import sys + +_doc = Path(__file__).resolve().parent +if str(_doc) not in sys.path: + sys.path.insert(0, str(_doc)) + +project = "ExternalThemeTest" +extensions = ["sphinx_simplepdf", "stub_external_pdf_theme"] +master_doc = "index" +exclude_patterns = ["_build"] + +simplepdf_theme = "stub_external_pdf_theme" diff --git a/tests/doc_test/with_external_theme/index.rst b/tests/doc_test/with_external_theme/index.rst new file mode 100644 index 0000000..d91ebe9 --- /dev/null +++ b/tests/doc_test/with_external_theme/index.rst @@ -0,0 +1,4 @@ +External theme +============== + +Minimal document for :py:func:`get_scss_sources_path` integration tests. diff --git a/tests/doc_test/with_external_theme/stub_external_pdf_theme/__init__.py b/tests/doc_test/with_external_theme/stub_external_pdf_theme/__init__.py new file mode 100644 index 0000000..f29abce --- /dev/null +++ b/tests/doc_test/with_external_theme/stub_external_pdf_theme/__init__.py @@ -0,0 +1,13 @@ +"""Minimal external theme for tests (Sphinx HTML theme + SimplePDF SCSS hook).""" + +from os import path + + +def get_scss_sources_path(): + """Return SCSS sources for SimplePDF (same layout convention as simplepdf_theme).""" + return path.join(path.abspath(path.dirname(__file__)), "static", "styles", "sources") + + +def setup(app): + app.add_html_theme("stub_external_pdf_theme", path.abspath(path.dirname(__file__))) + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/tests/doc_test/with_external_theme/stub_external_pdf_theme/static/styles/sources/main.scss b/tests/doc_test/with_external_theme/stub_external_pdf_theme/static/styles/sources/main.scss new file mode 100644 index 0000000..5963ba9 --- /dev/null +++ b/tests/doc_test/with_external_theme/stub_external_pdf_theme/static/styles/sources/main.scss @@ -0,0 +1,6 @@ +@charset "UTF-8"; + +/* sphinx-simplepdf-test-external-theme-marker */ +body { + color: #2a4b8d; +} diff --git a/tests/doc_test/with_external_theme/stub_external_pdf_theme/theme.conf b/tests/doc_test/with_external_theme/stub_external_pdf_theme/theme.conf new file mode 100644 index 0000000..ac8089b --- /dev/null +++ b/tests/doc_test/with_external_theme/stub_external_pdf_theme/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = main.css +pygments_style = friendly diff --git a/tests/doc_test/with_theme_missing_scss_hook/conf.py b/tests/doc_test/with_theme_missing_scss_hook/conf.py new file mode 100644 index 0000000..7aa88a3 --- /dev/null +++ b/tests/doc_test/with_theme_missing_scss_hook/conf.py @@ -0,0 +1,15 @@ +"""Theme registers for Sphinx but does not define get_scss_sources_path (fallback case).""" + +from pathlib import Path +import sys + +_doc = Path(__file__).resolve().parent +if str(_doc) not in sys.path: + sys.path.insert(0, str(_doc)) + +project = "MissingScssHookTest" +extensions = ["sphinx_simplepdf", "stub_theme_no_scss_hook"] +master_doc = "index" +exclude_patterns = ["_build"] + +simplepdf_theme = "stub_theme_no_scss_hook" diff --git a/tests/doc_test/with_theme_missing_scss_hook/index.rst b/tests/doc_test/with_theme_missing_scss_hook/index.rst new file mode 100644 index 0000000..2475ded --- /dev/null +++ b/tests/doc_test/with_theme_missing_scss_hook/index.rst @@ -0,0 +1,4 @@ +Missing SCSS hook +================= + +Used to assert fallback to bundled ``simplepdf_theme`` SCSS sources. diff --git a/tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/__init__.py b/tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/__init__.py new file mode 100644 index 0000000..b41c72d --- /dev/null +++ b/tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/__init__.py @@ -0,0 +1,8 @@ +"""Sphinx theme without get_scss_sources_path — SimplePDF must fall back to bundled SCSS.""" + +from os import path + + +def setup(app): + app.add_html_theme("stub_theme_no_scss_hook", path.abspath(path.dirname(__file__))) + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/theme.conf b/tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/theme.conf new file mode 100644 index 0000000..ac8089b --- /dev/null +++ b/tests/doc_test/with_theme_missing_scss_hook/stub_theme_no_scss_hook/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = main.css +pygments_style = friendly diff --git a/tests/test_external_theme.py b/tests/test_external_theme.py new file mode 100644 index 0000000..c5abea3 --- /dev/null +++ b/tests/test_external_theme.py @@ -0,0 +1,31 @@ +"""Integration tests for external SimplePDF themes (get_scss_sources_path). + +Covers the scenario discussed in PR #134 review: a real importable theme package +whose SCSS is compiled by the simplepdf builder. +""" + +from __future__ import annotations + +from .utils import build_and_capture_stdout + + +def test_external_theme_scss_is_compiled(sphinx_build, capsys): + """An external theme with get_scss_sources_path compiles its own main.scss.""" + result = build_and_capture_stdout(sphinx_build, capsys, srcdir="with_external_theme") + + assert result.pdf_exists() + assert not result.has_warnings("ERROR:") + assert result.outdir is not None + compiled = (result.outdir / "_static" / "main.css").read_text(encoding="utf-8") + assert "sphinx-simplepdf-test-external-theme-marker" in compiled + assert "#2a4b8d" in compiled or "2a4b8d" in compiled + + +def test_theme_without_get_scss_sources_path_falls_back(sphinx_build, capsys): + """If the theme module loads but omits get_scss_sources_path, bundled SCSS is used.""" + result = build_and_capture_stdout(sphinx_build, capsys, srcdir="with_theme_missing_scss_hook") + + assert result.pdf_exists() + assert not result.has_warnings("ERROR:") + matched = result.get_warnings_matching(r"does not define get_scss_sources_path") + assert len(matched) >= 1 From 6f8658dd295cbc016e3dc3d2b8afa084c42cd588 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Wed, 1 Apr 2026 16:39:32 -0700 Subject: [PATCH 06/10] chore: document why import_module(theme_name) is acceptable --- sphinx_simplepdf/builders/simplepdf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index 9354cc9..0708997 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -107,6 +107,7 @@ def _resolve_scss_folder(self): """ theme_name = self.app.config.simplepdf_theme or "simplepdf_theme" try: + # theme_name comes from conf.py; dynamic import is no extra trust boundary vs. Sphinx config. theme_module = importlib.import_module(theme_name) if hasattr(theme_module, "get_scss_sources_path"): return theme_module.get_scss_sources_path() From 278313562a76946612e0846b73d4c8ee7404dab6 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Wed, 1 Apr 2026 17:00:58 -0700 Subject: [PATCH 07/10] fix: harden theme SCSS resolution against third-party failures --- sphinx_simplepdf/builders/simplepdf.py | 33 +++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index 0708997..a067b78 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -1,5 +1,5 @@ -import importlib from collections import Counter +import importlib import os import re import subprocess @@ -103,29 +103,46 @@ def _resolve_scss_folder(self): Imports the theme module and calls its get_scss_sources_path(). Falls back to the bundled simplepdf_theme if the theme cannot be imported or - does not define get_scss_sources_path(). + does not define get_scss_sources_path(), or if calling the hook raises. """ theme_name = self.app.config.simplepdf_theme or "simplepdf_theme" + + def bundled_scss_folder(): + from sphinx_simplepdf.themes.simplepdf_theme import get_scss_sources_path + + return get_scss_sources_path() + try: # theme_name comes from conf.py; dynamic import is no extra trust boundary vs. Sphinx config. theme_module = importlib.import_module(theme_name) - if hasattr(theme_module, "get_scss_sources_path"): - return theme_module.get_scss_sources_path() + except Exception as exc: + logger.warning( + f"Could not import theme '{theme_name}' ({type(exc).__name__}: {exc!s}), " + "falling back to bundled simplepdf_theme", + type="simplepdf", + subtype="theme", + ) + return bundled_scss_folder() + + if not hasattr(theme_module, "get_scss_sources_path"): logger.warning( f"Theme '{theme_name}' does not define get_scss_sources_path(), " "falling back to bundled simplepdf_theme", type="simplepdf", subtype="theme", ) - except ImportError: + return bundled_scss_folder() + + try: + return theme_module.get_scss_sources_path() + except Exception as exc: logger.warning( - f"Could not import theme '{theme_name}', " + f"Theme '{theme_name}' get_scss_sources_path() failed ({type(exc).__name__}: {exc!s}), " "falling back to bundled simplepdf_theme", type="simplepdf", subtype="theme", ) - from sphinx_simplepdf.themes.simplepdf_theme import get_scss_sources_path - return get_scss_sources_path() + return bundled_scss_folder() def finish(self) -> None: super().finish() From 670ddee95d1749b25da0e0859c8d8c153757787f Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Wed, 1 Apr 2026 17:05:06 -0700 Subject: [PATCH 08/10] refactor: load bundled theme SCSS via importlib --- sphinx_simplepdf/builders/simplepdf.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index a067b78..5cf84d0 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -106,11 +106,10 @@ def _resolve_scss_folder(self): does not define get_scss_sources_path(), or if calling the hook raises. """ theme_name = self.app.config.simplepdf_theme or "simplepdf_theme" + fallback_module = importlib.import_module("sphinx_simplepdf.themes.simplepdf_theme") def bundled_scss_folder(): - from sphinx_simplepdf.themes.simplepdf_theme import get_scss_sources_path - - return get_scss_sources_path() + return fallback_module.get_scss_sources_path() try: # theme_name comes from conf.py; dynamic import is no extra trust boundary vs. Sphinx config. From 773595e1fb6304be0ab2b07845db94ff5e829084 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Wed, 1 Apr 2026 17:12:08 -0700 Subject: [PATCH 09/10] fix: validate theme SCSS directory before sass.compile --- docs/configuration.rst | 5 +++-- sphinx_simplepdf/builders/simplepdf.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 7cb01bd..3f340b4 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -174,8 +174,9 @@ The value must match both the Sphinx theme name and the importable Python module simplepdf_theme = "my_custom_pdf_theme" The theme module must define a ``get_scss_sources_path()`` function that returns -the absolute path to its SCSS sources directory. This is how the builder locates -the SCSS files to compile into CSS for the PDF. +the path to its SCSS sources directory. This is how the builder locates the SCSS +files to compile into CSS for the PDF. Prefer returning an absolute path +based on ``__file__`` so behavior does not depend on Sphinx's working directory. **Minimal example:** diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index 5cf84d0..b74019d 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -10,6 +10,7 @@ from sphinx import __version__ from sphinx.application import Sphinx from sphinx.builders.singlehtml import SingleFileHTMLBuilder +from sphinx.errors import ExtensionError from sphinx.util import logging import weasyprint @@ -104,6 +105,8 @@ def _resolve_scss_folder(self): Imports the theme module and calls its get_scss_sources_path(). Falls back to the bundled simplepdf_theme if the theme cannot be imported or does not define get_scss_sources_path(), or if calling the hook raises. + If the hook returns a path that is not an existing directory after + abspath normalization, raises ExtensionError. """ theme_name = self.app.config.simplepdf_theme or "simplepdf_theme" fallback_module = importlib.import_module("sphinx_simplepdf.themes.simplepdf_theme") @@ -133,7 +136,7 @@ def bundled_scss_folder(): return bundled_scss_folder() try: - return theme_module.get_scss_sources_path() + scss_folder = theme_module.get_scss_sources_path() except Exception as exc: logger.warning( f"Theme '{theme_name}' get_scss_sources_path() failed ({type(exc).__name__}: {exc!s}), " @@ -143,6 +146,14 @@ def bundled_scss_folder(): ) return bundled_scss_folder() + scss_folder = os.path.abspath(scss_folder) + if not os.path.isdir(scss_folder): + raise ExtensionError( + f"Theme '{theme_name}' get_scss_sources_path() returned " + f"non-existent directory: {scss_folder}" + ) + return scss_folder + def finish(self) -> None: super().finish() From 3df70ca815234cb05ebd2182f47e0ae3ccaa83a4 Mon Sep 17 00:00:00 2001 From: Jared Dillard Date: Thu, 2 Apr 2026 12:00:36 -0700 Subject: [PATCH 10/10] Fix ruff formatting --- sphinx_simplepdf/builders/simplepdf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sphinx_simplepdf/builders/simplepdf.py b/sphinx_simplepdf/builders/simplepdf.py index b74019d..392f1d9 100644 --- a/sphinx_simplepdf/builders/simplepdf.py +++ b/sphinx_simplepdf/builders/simplepdf.py @@ -149,8 +149,7 @@ def bundled_scss_folder(): scss_folder = os.path.abspath(scss_folder) if not os.path.isdir(scss_folder): raise ExtensionError( - f"Theme '{theme_name}' get_scss_sources_path() returned " - f"non-existent directory: {scss_folder}" + f"Theme '{theme_name}' get_scss_sources_path() returned non-existent directory: {scss_folder}" ) return scss_folder