sunpy-sphinx-theme 2.2.2__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/.readthedocs.yaml +1 -1
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/.rtd-environment.yml +1 -1
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/PKG-INFO +6 -3
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/_typos.toml +1 -0
- sunpy_sphinx_theme-2.4.0/conftest.py +142 -0
- sunpy_sphinx_theme-2.4.0/flake.nix +34 -0
- sunpy_sphinx_theme-2.4.0/package.json +8 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/pyproject.toml +11 -2
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/ruff.toml +6 -8
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/__init__.py +95 -47
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/__init__.py +3 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/helpers.py +13 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/sites/base/conf.py +9 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/sites/base/index.rst +4 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/sites/derived/conf.py +16 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/sites/derived/index.rst +4 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/sites/derived/theme/theme.toml +15 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/test_a11y.py +177 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/test_config_subtheme.py +64 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/test_goatcounter.py +164 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme/tests/test_options.py +17 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/sunpy_style.css +12 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/theme.toml +4 -3
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme.egg-info/PKG-INFO +6 -3
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme.egg-info/SOURCES.txt +16 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme.egg-info/requires.txt +5 -1
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme.egg-info/scm_file_list.json +73 -0
- sunpy_sphinx_theme-2.4.0/src/sunpy_sphinx_theme.egg-info/scm_version.json +8 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/.editorconfig +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/.stylelintrc.json +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/LICENSE.md +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/MANIFEST.in +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/README.md +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/setup.cfg +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/cards.py +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/conf.py +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/components/built-with.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/components/footer-links.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/components/navbar-logo.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/components/navbar_center.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/components/navbar_start.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/components/sst-sidebar-nav.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/sections/header.html +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/css/rtd_enhanced_search.css +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/css/sunpy_cards.css +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/img/favicon-32.ico +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/img/sunpy_icon.svg +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/img/sunpy_icon_128x128.png +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/js/rtd_enhanced_search.js +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme/theme/sunpy/static/js/submenu-concertina-toggle.js +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme.egg-info/dependency_links.txt +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme.egg-info/entry_points.txt +0 -0
- {sunpy_sphinx_theme-2.2.2 → sunpy_sphinx_theme-2.4.0}/src/sunpy_sphinx_theme.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sunpy-sphinx-theme
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: The sphinx theme for the SunPy website and documentation.
|
|
5
5
|
Author: The SunPy Developers
|
|
6
6
|
License: BSD 2-Clause License
|
|
@@ -36,10 +36,10 @@ Classifier: Programming Language :: Python :: 3
|
|
|
36
36
|
Classifier: Framework :: Sphinx
|
|
37
37
|
Classifier: Framework :: Sphinx :: Theme
|
|
38
38
|
Classifier: Operating System :: OS Independent
|
|
39
|
-
Requires-Python: >=3.
|
|
39
|
+
Requires-Python: >=3.11
|
|
40
40
|
Description-Content-Type: text/markdown
|
|
41
41
|
License-File: LICENSE.md
|
|
42
|
-
Requires-Dist: pydata-sphinx-theme<0.
|
|
42
|
+
Requires-Dist: pydata-sphinx-theme<0.24,>=0.22.0
|
|
43
43
|
Requires-Dist: sphinxext-opengraph>=0.13
|
|
44
44
|
Requires-Dist: sphinx>=7.3.0
|
|
45
45
|
Provides-Extra: docs
|
|
@@ -50,6 +50,9 @@ Requires-Dist: sphinx-design; extra == "docs"
|
|
|
50
50
|
Requires-Dist: sphinx-gallery; extra == "docs"
|
|
51
51
|
Requires-Dist: sphinx-togglebutton; extra == "docs"
|
|
52
52
|
Requires-Dist: sunpy; extra == "docs"
|
|
53
|
+
Provides-Extra: test
|
|
54
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
55
|
+
Requires-Dist: pytest-playwright; extra == "test"
|
|
53
56
|
Dynamic: license-file
|
|
54
57
|
|
|
55
58
|
# SunPy Project Sphinx Theme
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Configuration of the pytest session."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from http.client import HTTPConnection
|
|
8
|
+
from os import environ
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from shutil import copytree
|
|
11
|
+
from subprocess import PIPE, Popen
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from bs4 import BeautifulSoup
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
|
|
20
|
+
from sphinx.testing.util import SphinxTestApp
|
|
21
|
+
|
|
22
|
+
pytest_plugins = "sphinx.testing.fixtures"
|
|
23
|
+
|
|
24
|
+
repo_path = Path(__file__).parent
|
|
25
|
+
tests_path = repo_path / "src" / "sunpy_sphinx_theme" / "tests"
|
|
26
|
+
docs_build_path = repo_path / "docs" / "_build" / "html"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# -- global fixture to build sphinx tmp docs ---------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SphinxBuild:
|
|
33
|
+
"""Helper class to build a test documentation."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, app: SphinxTestApp, src: Path):
|
|
36
|
+
self.app = app
|
|
37
|
+
self.src = src
|
|
38
|
+
|
|
39
|
+
def build(self, *, no_warning: bool = True) -> SphinxBuild:
|
|
40
|
+
"""Build the application."""
|
|
41
|
+
self.app.build()
|
|
42
|
+
if no_warning is True:
|
|
43
|
+
assert self.warnings == "", self.status
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def status(self) -> str:
|
|
48
|
+
"""Returns the status of the current build."""
|
|
49
|
+
return self.app.status.getvalue()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def warnings(self) -> str:
|
|
53
|
+
"""Returns the warnings raised by the current build."""
|
|
54
|
+
return self.app.warning.getvalue()
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def outdir(self) -> Path:
|
|
58
|
+
"""Returns the output directory of the current build."""
|
|
59
|
+
return Path(self.app.outdir)
|
|
60
|
+
|
|
61
|
+
def html_tree(self, *path) -> BeautifulSoup:
|
|
62
|
+
"""Returns the html tree of the current build."""
|
|
63
|
+
path_page = self.outdir.joinpath(*path)
|
|
64
|
+
if not path_page.exists():
|
|
65
|
+
msg = f"{path_page} does not exist"
|
|
66
|
+
raise ValueError(msg)
|
|
67
|
+
return BeautifulSoup(path_page.read_text("utf8"), "html.parser")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def sphinx_build_factory(make_app: Callable, tmp_path: Path, request) -> Callable:
|
|
72
|
+
"""Return a factory builder pointing to the tmp directory."""
|
|
73
|
+
|
|
74
|
+
def _func(src_folder: str, files: dict[str, str] | None = None, **kwargs) -> SphinxBuild:
|
|
75
|
+
"""Create the SphinxBuild from the source folder."""
|
|
76
|
+
no_temp = environ.get("SST_TEST_HTML_DIR")
|
|
77
|
+
nonlocal tmp_path
|
|
78
|
+
if no_temp is not None:
|
|
79
|
+
tmp_path = Path(no_temp) / request.node.name / str(src_folder)
|
|
80
|
+
srcdir = tmp_path / src_folder
|
|
81
|
+
copytree(tests_path / "sites" / src_folder, tmp_path / src_folder)
|
|
82
|
+
for filename, contents in (files or {}).items():
|
|
83
|
+
filepath = Path(srcdir / filename).resolve()
|
|
84
|
+
filepath.parent.mkdir(exist_ok=True, parents=True)
|
|
85
|
+
filepath.write_text(contents)
|
|
86
|
+
app = make_app(srcdir=srcdir, **kwargs)
|
|
87
|
+
return SphinxBuild(app, tmp_path / src_folder)
|
|
88
|
+
|
|
89
|
+
return _func
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# -- global fixture to serve the built docs ----------------------------------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@pytest.fixture(scope="module")
|
|
96
|
+
def url_base():
|
|
97
|
+
"""Start local server on built docs and return the localhost URL as the base URL."""
|
|
98
|
+
# The accessibility tests are run against a build of our documentation, so
|
|
99
|
+
# fail early with a helpful message if it has not been built yet.
|
|
100
|
+
if not docs_build_path.is_dir():
|
|
101
|
+
msg = f"No docs build found at {docs_build_path}; build it first with 'tox -e build_docs'"
|
|
102
|
+
pytest.fail(msg, pytrace=False)
|
|
103
|
+
|
|
104
|
+
# Use a port that is not commonly used during development or else you will
|
|
105
|
+
# force the developer to stop running their dev server in order to run the
|
|
106
|
+
# tests.
|
|
107
|
+
port = 8213
|
|
108
|
+
host = "localhost"
|
|
109
|
+
url = f"http://{host}:{port}"
|
|
110
|
+
|
|
111
|
+
# Try starting the server
|
|
112
|
+
process = Popen(
|
|
113
|
+
[sys.executable, "-m", "http.server", str(port), "--directory", docs_build_path],
|
|
114
|
+
stdout=PIPE,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Try connecting to the server
|
|
118
|
+
retries = 5
|
|
119
|
+
while retries > 0:
|
|
120
|
+
conn = HTTPConnection(host, port)
|
|
121
|
+
try:
|
|
122
|
+
conn.request("HEAD", "/")
|
|
123
|
+
response = conn.getresponse()
|
|
124
|
+
if response is not None:
|
|
125
|
+
yield url
|
|
126
|
+
break
|
|
127
|
+
except ConnectionRefusedError:
|
|
128
|
+
time.sleep(1)
|
|
129
|
+
retries -= 1
|
|
130
|
+
finally:
|
|
131
|
+
conn.close()
|
|
132
|
+
|
|
133
|
+
# If the code above never yields a URL, then we were never able to connect
|
|
134
|
+
# to the server and retries == 0.
|
|
135
|
+
if not retries:
|
|
136
|
+
msg_0 = "Failed to start http server in 5 seconds"
|
|
137
|
+
raise RuntimeError(msg_0)
|
|
138
|
+
else:
|
|
139
|
+
# Otherwise the server started and this fixture is done now and we clean
|
|
140
|
+
# up by stopping the server.
|
|
141
|
+
process.terminate()
|
|
142
|
+
process.wait()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
description = "A very basic flake";
|
|
3
|
+
|
|
4
|
+
inputs = {
|
|
5
|
+
flake-parts.url = "github:hercules-ci/flake-parts";
|
|
6
|
+
nixpkgs.url = "github:nixos/nixpkgs";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
outputs = { flake-parts, ... } @ inputs: flake-parts.lib.mkFlake { inherit inputs; } {
|
|
10
|
+
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
|
11
|
+
devShells.default = pkgs.mkShell {
|
|
12
|
+
# venvShellHook needs venvDir set on the shell
|
|
13
|
+
venvDir = ".venv";
|
|
14
|
+
|
|
15
|
+
packages = with pkgs; [
|
|
16
|
+
nodejs_latest
|
|
17
|
+
playwright-driver.browsers
|
|
18
|
+
uv
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
# runs every time you enter the shell, after activation
|
|
22
|
+
shellHook = ''
|
|
23
|
+
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
|
|
24
|
+
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true;
|
|
25
|
+
uv sync --all-extras
|
|
26
|
+
source .venv/bin/activate
|
|
27
|
+
npm install
|
|
28
|
+
'';
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
# Declared systems that your flake supports. These will be enumerated in perSystem
|
|
32
|
+
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -20,9 +20,9 @@ classifiers = [
|
|
|
20
20
|
"Framework :: Sphinx :: Theme",
|
|
21
21
|
"Operating System :: OS Independent",
|
|
22
22
|
]
|
|
23
|
-
requires-python = ">=3.
|
|
23
|
+
requires-python = ">=3.11"
|
|
24
24
|
dependencies = [
|
|
25
|
-
"pydata-sphinx-theme>=0.
|
|
25
|
+
"pydata-sphinx-theme>=0.22.0,<0.24",
|
|
26
26
|
"sphinxext-opengraph>=0.13",
|
|
27
27
|
"sphinx>=7.3.0"
|
|
28
28
|
]
|
|
@@ -37,6 +37,10 @@ docs = [
|
|
|
37
37
|
"sphinx-togglebutton",
|
|
38
38
|
"sunpy",
|
|
39
39
|
]
|
|
40
|
+
test = [
|
|
41
|
+
"pytest>=7.0",
|
|
42
|
+
"pytest-playwright",
|
|
43
|
+
]
|
|
40
44
|
|
|
41
45
|
[project.entry-points]
|
|
42
46
|
"sphinx.html_themes" = { sunpy = "sunpy_sphinx_theme" }
|
|
@@ -46,6 +50,11 @@ include-package-data = true
|
|
|
46
50
|
|
|
47
51
|
[tool.setuptools_scm]
|
|
48
52
|
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
markers = "a11y: mark a test as an accessibility test"
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
addopts = "--durations=10"
|
|
57
|
+
|
|
49
58
|
[ tool.gilesbot ]
|
|
50
59
|
[ tool.gilesbot.circleci_artifacts.giles ]
|
|
51
60
|
enabled = false
|
|
@@ -31,6 +31,7 @@ lint.extend-ignore = [
|
|
|
31
31
|
"PLR2004", # Magic value used in comparison
|
|
32
32
|
"TD002", # Missing author in TODO
|
|
33
33
|
"TD003", # Missing issue link on the line following this TODO
|
|
34
|
+
"CPY001", # Copyright in each file
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[lint.per-file-ignores]
|
|
@@ -48,17 +49,14 @@ lint.extend-ignore = [
|
|
|
48
49
|
"setup.py" = [
|
|
49
50
|
"D100", # Missing docstring in public module
|
|
50
51
|
]
|
|
51
|
-
"test_*.py" = [
|
|
52
|
+
"{src/sunpy_sphinx_theme/tests/*.py,conftest.py,test_*.py}" = [
|
|
52
53
|
"D100", # Missing docstring in public module
|
|
53
54
|
"D103", # Missing docstring in public function
|
|
54
|
-
"
|
|
55
|
+
"INP001", # tests are not part of an implicit namespace package
|
|
55
56
|
"S101", # Use of `assert` detected
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
]
|
|
60
|
-
"sunpy_soar/conftest.py" = [
|
|
61
|
-
"D100", # Missing docstring in public module
|
|
57
|
+
"S404", # subprocess module is possibly insecure
|
|
58
|
+
"S603", # subprocess call: check for execution of untrusted input
|
|
59
|
+
"ARG001", # Unused function argument (kept for interface compatibility)
|
|
62
60
|
]
|
|
63
61
|
|
|
64
62
|
[lint.pydocstyle]
|
|
@@ -12,6 +12,9 @@ from urllib.parse import urljoin, urlparse
|
|
|
12
12
|
|
|
13
13
|
from pydata_sphinx_theme import utils
|
|
14
14
|
from sphinx.application import Sphinx
|
|
15
|
+
from sphinx.util import logging
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
15
18
|
|
|
16
19
|
__all__ = ["ON_RTD", "PNG_ICON", "SVG_ICON", "get_html_theme_path"]
|
|
17
20
|
|
|
@@ -45,7 +48,7 @@ def update_config(app) -> None:
|
|
|
45
48
|
theme_options = utils.get_theme_options_dict(app)
|
|
46
49
|
|
|
47
50
|
if theme_options_with_defaults.get("sst_logo") and not isinstance(theme_options_with_defaults["sst_logo"], dict):
|
|
48
|
-
sst_logo = str(
|
|
51
|
+
sst_logo = str(theme_options_with_defaults["sst_logo"])
|
|
49
52
|
theme_options["sst_logo"] = {"light": sst_logo, "dark": sst_logo}
|
|
50
53
|
|
|
51
54
|
theme_options["sst_is_root"] = bool(theme_options_with_defaults.get("sst_is_root", False))
|
|
@@ -122,6 +125,28 @@ def update_html_context(app: Sphinx, pagename: str, templatename: str, context,
|
|
|
122
125
|
context["sst_pathto"] = partial(sst_pathto, context)
|
|
123
126
|
|
|
124
127
|
|
|
128
|
+
def _get_sphinx_gallery_thumbnail(doctree):
|
|
129
|
+
"""
|
|
130
|
+
Return the generated Sphinx-Gallery thumbnail for an example page.
|
|
131
|
+
"""
|
|
132
|
+
source = doctree.get("source")
|
|
133
|
+
if not source:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
source_path = Path(source)
|
|
137
|
+
thumbnail_dir = source_path.parent / "images" / "thumb"
|
|
138
|
+
if not thumbnail_dir.is_dir():
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
# Reuse the thumbnail Sphinx-Gallery already picked for this page.
|
|
142
|
+
thumbnails = sorted(thumbnail_dir.glob(f"sphx_glr_{source_path.stem}_thumb.*"))
|
|
143
|
+
for thumbnail in thumbnails:
|
|
144
|
+
if thumbnail.suffix.lower() in SPHINX_GALLERY_IMAGE_EXTENSIONS:
|
|
145
|
+
return thumbnail
|
|
146
|
+
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
125
150
|
def update_opengraph_context(app: Sphinx, pagename: str, templatename: str, context, doctree) -> None: # NOQA: ARG001
|
|
126
151
|
"""
|
|
127
152
|
Add Sphinx-Gallery images to the Open Graph metadata context.
|
|
@@ -133,6 +158,13 @@ def update_opengraph_context(app: Sphinx, pagename: str, templatename: str, cont
|
|
|
133
158
|
if "og:image" in metadata:
|
|
134
159
|
return
|
|
135
160
|
|
|
161
|
+
# Prefer the user defined generated gallery thumbnail when one exists.
|
|
162
|
+
if thumbnail := _get_sphinx_gallery_thumbnail(doctree):
|
|
163
|
+
metadata["og:image"] = posixpath.join(app.builder.imagedir, thumbnail.name)
|
|
164
|
+
context["meta"] = metadata
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Fall back to the first gallery image
|
|
136
168
|
for node in doctree.findall():
|
|
137
169
|
if node.__class__.__name__ != "imgsgnode" or not node.get("uri"):
|
|
138
170
|
continue
|
|
@@ -159,28 +191,43 @@ def generate_search_config(app):
|
|
|
159
191
|
This function parses the config for the "Documentation" section of the theme config.
|
|
160
192
|
"""
|
|
161
193
|
theme_config = get_theme_options(app)
|
|
162
|
-
|
|
194
|
+
|
|
195
|
+
if not theme_config.get("rtd_search", True):
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# Add project-wide search
|
|
199
|
+
app.add_css_file("css/rtd_enhanced_search.css")
|
|
200
|
+
app.add_js_file(
|
|
201
|
+
"js/rtd_enhanced_search.js",
|
|
202
|
+
loading_method="async",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def filter_doc_links(links):
|
|
206
|
+
out_links = []
|
|
207
|
+
for link in links:
|
|
208
|
+
if isinstance(link[1], list):
|
|
209
|
+
out_links += filter_doc_links(link[1])
|
|
210
|
+
elif isinstance(link[1], str) and link[1].startswith("http"):
|
|
211
|
+
out_links.append({"name": link[0], "link": link[1]})
|
|
212
|
+
else:
|
|
213
|
+
err = f"Unable to parse {link} in the nav tree. Try setting search_projects explicitly or fixing navbar_links."
|
|
214
|
+
raise ValueError(err)
|
|
215
|
+
return out_links
|
|
216
|
+
|
|
217
|
+
search_projects = theme_config.get("rtd_search_projects", []) or []
|
|
163
218
|
if not search_projects:
|
|
164
219
|
navbar_links = theme_config["navbar_links"]
|
|
165
|
-
doc_links = next(section[1] for section in navbar_links if section[0] == "Documentation")
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
out_links = []
|
|
169
|
-
for link in links:
|
|
170
|
-
if isinstance(link[1], list):
|
|
171
|
-
out_links += filter_doc_links(link[1])
|
|
172
|
-
elif isinstance(link[1], str) and link[1].startswith("http"):
|
|
173
|
-
out_links.append({"name": link[0], "link": link[1]})
|
|
174
|
-
else:
|
|
175
|
-
err = f"Unable to parse {link} in the nav tree. Try setting search_projects explicitly or fixing navbar_links."
|
|
176
|
-
raise ValueError(err)
|
|
177
|
-
return out_links
|
|
178
|
-
|
|
179
|
-
search_projects = filter_doc_links(doc_links)
|
|
220
|
+
doc_links = next((section[1] for section in navbar_links if section[0] == "Documentation"), None)
|
|
221
|
+
if doc_links:
|
|
222
|
+
search_projects = filter_doc_links(doc_links)
|
|
180
223
|
|
|
181
224
|
if extra_search_projects := theme_config.get("rtd_extra_search_projects", None):
|
|
182
225
|
search_projects += filter_doc_links(extra_search_projects)
|
|
183
226
|
|
|
227
|
+
if not search_projects:
|
|
228
|
+
logger.info("No RTD search projects were found, disabling RTD search.")
|
|
229
|
+
return
|
|
230
|
+
|
|
184
231
|
load_more_label = theme_config.get("rtd_search_load_more_label", "Load more results")
|
|
185
232
|
no_results_label = theme_config.get("rtd_search_no_results_label", "There are no results for this search")
|
|
186
233
|
script = dedent(f"""
|
|
@@ -198,30 +245,24 @@ def generate_search_config(app):
|
|
|
198
245
|
app.add_js_file(None, body=script)
|
|
199
246
|
|
|
200
247
|
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
app.add_html_theme("sunpy", theme_dir)
|
|
207
|
-
app.add_css_file("sunpy_style.css", priority=600)
|
|
208
|
-
app.connect("builder-inited", update_config, priority=100)
|
|
209
|
-
app.connect("builder-inited", generate_search_config, priority=500)
|
|
210
|
-
app.connect("html-page-context", update_opengraph_context, priority=400)
|
|
211
|
-
app.connect("html-page-context", update_html_context)
|
|
212
|
-
# Conditionally include goat counter js
|
|
213
|
-
# We can't do this in update_config as that causes the scripts to be duplicated.
|
|
214
|
-
# Also in here none of the theme defaults have be applied by `update_config`
|
|
215
|
-
# TODO: Improve this mess
|
|
216
|
-
theme_options = utils.get_theme_options_dict(app)
|
|
248
|
+
def configure_goatcounter(app):
|
|
249
|
+
"""
|
|
250
|
+
Conditionally include goat counter js and configure endpoint.
|
|
251
|
+
"""
|
|
252
|
+
theme_options = get_theme_options(app)
|
|
217
253
|
# We want to default to the sunpy goat counter only if the sst_site_root is sunpy.org
|
|
218
254
|
root_domain = theme_options.get("sst_site_root", "https://sunpy.org")
|
|
219
255
|
sunpy_goat_url = "https://sunpy.goatcounter.com/count"
|
|
220
|
-
default_goat_url =
|
|
221
|
-
|
|
256
|
+
default_goat_url = (
|
|
257
|
+
sunpy_goat_url if root_domain == "https://sunpy.org" else False
|
|
258
|
+
) # Only default on sunpy.org pages
|
|
259
|
+
# The theme default is 'DEFAULT', to allow disabling it
|
|
260
|
+
primary_goat_url = theme_options.get("goatcounter_analytics_url", "DEFAULT")
|
|
261
|
+
primary_goat_url = primary_goat_url if primary_goat_url != "DEFAULT" else default_goat_url
|
|
262
|
+
if primary_goat_url:
|
|
222
263
|
root_domain = root_domain.removeprefix("https://").removeprefix("http://")
|
|
223
264
|
default_endpoint = theme_options.get("goatcounter_non_domain_endpoint", False)
|
|
224
|
-
if default_endpoint
|
|
265
|
+
if default_endpoint in (False, "False"): # sphinx config can sometimes be str
|
|
225
266
|
default_endpoint = ""
|
|
226
267
|
app.add_js_file(
|
|
227
268
|
None,
|
|
@@ -241,18 +282,25 @@ def setup(app: Sphinx):
|
|
|
241
282
|
"https://gc.zgo.at/count.js",
|
|
242
283
|
loading_method="async",
|
|
243
284
|
)
|
|
244
|
-
app.add_js_file(
|
|
245
|
-
"js/submenu-concertina-toggle.js",
|
|
246
|
-
loading_method="async",
|
|
247
|
-
)
|
|
248
285
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
286
|
+
|
|
287
|
+
def setup(app: Sphinx):
|
|
288
|
+
app.setup_extension("sphinxext.opengraph")
|
|
289
|
+
|
|
290
|
+
# Register theme
|
|
291
|
+
theme_dir = get_html_theme_path()
|
|
292
|
+
app.add_html_theme("sunpy", theme_dir)
|
|
293
|
+
app.add_css_file("sunpy_style.css", priority=600)
|
|
294
|
+
app.connect("builder-inited", update_config, priority=100)
|
|
295
|
+
app.connect("builder-inited", generate_search_config, priority=500)
|
|
296
|
+
app.connect("builder-inited", configure_goatcounter, priority=510)
|
|
297
|
+
app.connect("html-page-context", update_opengraph_context, priority=400)
|
|
298
|
+
app.connect("html-page-context", update_html_context)
|
|
299
|
+
|
|
300
|
+
app.add_js_file(
|
|
301
|
+
"js/submenu-concertina-toggle.js",
|
|
302
|
+
loading_method="async",
|
|
303
|
+
)
|
|
256
304
|
|
|
257
305
|
return {
|
|
258
306
|
"parallel_read_safe": True,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
def inline_scripts(html_tree):
|
|
2
|
+
"""Return the content of all inline scripts in a page."""
|
|
3
|
+
return [script.string for script in html_tree.select("script") if script.string]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def script_srcs(html_tree):
|
|
7
|
+
"""Return the src URLs of all external scripts in a page."""
|
|
8
|
+
return [script["src"] for script in html_tree.select("script[src]")]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def stylesheet_hrefs(html_tree):
|
|
12
|
+
"""Return the hrefs of all stylesheets in a page."""
|
|
13
|
+
return [link["href"] for link in html_tree.select("link[rel='stylesheet']")]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Test conf file for a theme derived from the sunpy theme."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
project = "SunPy Derived Theme Tests"
|
|
6
|
+
copyright = "2026, The SunPy Community" # NOQA: A001
|
|
7
|
+
author = "The SunPy Community"
|
|
8
|
+
|
|
9
|
+
extensions = ["sunpy_sphinx_theme"]
|
|
10
|
+
html_theme = "derived"
|
|
11
|
+
html_static_path = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def setup(app):
|
|
15
|
+
"""Register the derived theme with Sphinx."""
|
|
16
|
+
app.add_html_theme("derived", str(Path(__file__).parent / "theme"))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[theme]
|
|
2
|
+
inherit = "sunpy"
|
|
3
|
+
|
|
4
|
+
[options]
|
|
5
|
+
sst_site_root = "https://example.org/"
|
|
6
|
+
navbar_links = [
|
|
7
|
+
["Home", "index.html", 2],
|
|
8
|
+
["Docs", "https://docs.example.org/", 3],
|
|
9
|
+
]
|
|
10
|
+
footer_links = [
|
|
11
|
+
["Footer Example", "https://footer.example.org/", 3],
|
|
12
|
+
]
|
|
13
|
+
rtd_search_projects = [
|
|
14
|
+
{ name = "example", link = "https://example.org/docs/" },
|
|
15
|
+
]
|