sunpy-sphinx-theme 2.3.0__tar.gz → 2.5.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.5.0/.codespellrc +2 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/.rtd-environment.yml +1 -1
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/PKG-INFO +9 -3
- sunpy_sphinx_theme-2.5.0/conftest.py +143 -0
- sunpy_sphinx_theme-2.5.0/flake.nix +35 -0
- sunpy_sphinx_theme-2.5.0/package.json +8 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/pyproject.toml +22 -2
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/ruff.toml +7 -8
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/__init__.py +66 -47
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/__init__.py +3 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/helpers.py +13 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/sites/base/conf.py +9 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/sites/base/index.rst +4 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/sites/derived/conf.py +16 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/sites/derived/index.rst +4 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/sites/derived/theme/theme.toml +15 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/test_a11y.py +187 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/test_config_subtheme.py +64 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/test_goatcounter.py +164 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/test_options.py +17 -0
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme/tests/test_screenshots.py +40 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/theme.toml +3 -3
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme.egg-info/PKG-INFO +9 -3
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme.egg-info/SOURCES.txt +16 -1
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme.egg-info/requires.txt +9 -1
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme.egg-info/scm_file_list.json +16 -1
- sunpy_sphinx_theme-2.5.0/src/sunpy_sphinx_theme.egg-info/scm_version.json +8 -0
- sunpy_sphinx_theme-2.3.0/_typos.toml +0 -25
- sunpy_sphinx_theme-2.3.0/src/sunpy_sphinx_theme.egg-info/scm_version.json +0 -8
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/.editorconfig +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/.readthedocs.yaml +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/.stylelintrc.json +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/LICENSE.md +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/MANIFEST.in +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/README.md +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/setup.cfg +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/cards.py +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/conf.py +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/components/built-with.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/components/footer-links.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/components/navbar-logo.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/components/navbar_center.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/components/navbar_start.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/components/sst-sidebar-nav.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/sections/header.html +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/css/rtd_enhanced_search.css +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/css/sunpy_cards.css +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/img/favicon-32.ico +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/img/sunpy_icon.svg +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/img/sunpy_icon_128x128.png +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/js/rtd_enhanced_search.js +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/js/submenu-concertina-toggle.js +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme/theme/sunpy/static/sunpy_style.css +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme.egg-info/dependency_links.txt +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.0}/src/sunpy_sphinx_theme.egg-info/entry_points.txt +0 -0
- {sunpy_sphinx_theme-2.3.0 → sunpy_sphinx_theme-2.5.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.5.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.25,>=0.24.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,12 @@ 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
|
+
Provides-Extra: test-playwright
|
|
56
|
+
Requires-Dist: playwright==1.64.1; extra == "test-playwright"
|
|
57
|
+
Requires-Dist: pytest-playwright==0.10.0; extra == "test-playwright"
|
|
58
|
+
Requires-Dist: playwright-figure-tests; extra == "test-playwright"
|
|
53
59
|
Dynamic: license-file
|
|
54
60
|
|
|
55
61
|
# SunPy Project Sphinx Theme
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
|
|
25
|
+
repo_path = Path(__file__).parent
|
|
26
|
+
tests_path = repo_path / "src" / "sunpy_sphinx_theme" / "tests"
|
|
27
|
+
docs_build_path = repo_path / "docs" / "_build" / "html"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# -- global fixture to build sphinx tmp docs ---------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SphinxBuild:
|
|
34
|
+
"""Helper class to build a test documentation."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, app: SphinxTestApp, src: Path):
|
|
37
|
+
self.app = app
|
|
38
|
+
self.src = src
|
|
39
|
+
|
|
40
|
+
def build(self, *, no_warning: bool = True) -> SphinxBuild:
|
|
41
|
+
"""Build the application."""
|
|
42
|
+
self.app.build()
|
|
43
|
+
if no_warning is True:
|
|
44
|
+
assert self.warnings == "", self.status
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def status(self) -> str:
|
|
49
|
+
"""Returns the status of the current build."""
|
|
50
|
+
return self.app.status.getvalue()
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def warnings(self) -> str:
|
|
54
|
+
"""Returns the warnings raised by the current build."""
|
|
55
|
+
return self.app.warning.getvalue()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def outdir(self) -> Path:
|
|
59
|
+
"""Returns the output directory of the current build."""
|
|
60
|
+
return Path(self.app.outdir)
|
|
61
|
+
|
|
62
|
+
def html_tree(self, *path) -> BeautifulSoup:
|
|
63
|
+
"""Returns the html tree of the current build."""
|
|
64
|
+
path_page = self.outdir.joinpath(*path)
|
|
65
|
+
if not path_page.exists():
|
|
66
|
+
msg = f"{path_page} does not exist"
|
|
67
|
+
raise ValueError(msg)
|
|
68
|
+
return BeautifulSoup(path_page.read_text("utf8"), "html.parser")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.fixture
|
|
72
|
+
def sphinx_build_factory(make_app: Callable, tmp_path: Path, request) -> Callable:
|
|
73
|
+
"""Return a factory builder pointing to the tmp directory."""
|
|
74
|
+
|
|
75
|
+
def _func(src_folder: str, files: dict[str, str] | None = None, **kwargs) -> SphinxBuild:
|
|
76
|
+
"""Create the SphinxBuild from the source folder."""
|
|
77
|
+
no_temp = environ.get("SST_TEST_HTML_DIR")
|
|
78
|
+
nonlocal tmp_path
|
|
79
|
+
if no_temp is not None:
|
|
80
|
+
tmp_path = Path(no_temp) / request.node.name / str(src_folder)
|
|
81
|
+
srcdir = tmp_path / src_folder
|
|
82
|
+
copytree(tests_path / "sites" / src_folder, tmp_path / src_folder)
|
|
83
|
+
for filename, contents in (files or {}).items():
|
|
84
|
+
filepath = Path(srcdir / filename).resolve()
|
|
85
|
+
filepath.parent.mkdir(exist_ok=True, parents=True)
|
|
86
|
+
filepath.write_text(contents)
|
|
87
|
+
app = make_app(srcdir=srcdir, **kwargs)
|
|
88
|
+
return SphinxBuild(app, tmp_path / src_folder)
|
|
89
|
+
|
|
90
|
+
return _func
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# -- global fixture to serve the built docs ----------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.fixture(scope="session")
|
|
97
|
+
def url_base():
|
|
98
|
+
"""Start local server on built docs and return the localhost URL as the base URL."""
|
|
99
|
+
# The accessibility tests are run against a build of our documentation, so
|
|
100
|
+
# fail early with a helpful message if it has not been built yet.
|
|
101
|
+
if not docs_build_path.is_dir():
|
|
102
|
+
msg = f"No docs build found at {docs_build_path}; build it first with 'tox -e build_docs'"
|
|
103
|
+
pytest.fail(msg, pytrace=False)
|
|
104
|
+
|
|
105
|
+
# Use a port that is not commonly used during development or else you will
|
|
106
|
+
# force the developer to stop running their dev server in order to run the
|
|
107
|
+
# tests.
|
|
108
|
+
port = 8213
|
|
109
|
+
host = "localhost"
|
|
110
|
+
url = f"http://{host}:{port}"
|
|
111
|
+
|
|
112
|
+
# Try starting the server
|
|
113
|
+
process = Popen(
|
|
114
|
+
[sys.executable, "-m", "http.server", str(port), "--directory", docs_build_path],
|
|
115
|
+
stdout=PIPE,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Try connecting to the server
|
|
119
|
+
retries = 5
|
|
120
|
+
while retries > 0:
|
|
121
|
+
conn = HTTPConnection(host, port)
|
|
122
|
+
try:
|
|
123
|
+
conn.request("HEAD", "/")
|
|
124
|
+
response = conn.getresponse()
|
|
125
|
+
if response is not None:
|
|
126
|
+
yield url
|
|
127
|
+
break
|
|
128
|
+
except ConnectionRefusedError:
|
|
129
|
+
time.sleep(1)
|
|
130
|
+
retries -= 1
|
|
131
|
+
finally:
|
|
132
|
+
conn.close()
|
|
133
|
+
|
|
134
|
+
# If the code above never yields a URL, then we were never able to connect
|
|
135
|
+
# to the server and retries == 0.
|
|
136
|
+
if not retries:
|
|
137
|
+
msg_0 = "Failed to start http server in 5 seconds"
|
|
138
|
+
raise RuntimeError(msg_0)
|
|
139
|
+
else:
|
|
140
|
+
# Otherwise the server started and this fixture is done now and we clean
|
|
141
|
+
# up by stopping the server.
|
|
142
|
+
process.terminate()
|
|
143
|
+
process.wait()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
description = "sunpy-sphinx-theme dev shell (provides Playwright browsers for NixOS)";
|
|
3
|
+
|
|
4
|
+
inputs = {
|
|
5
|
+
flake-parts.url = "github:hercules-ci/flake-parts";
|
|
6
|
+
nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst";
|
|
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
|
+
# playwright-driver.browsers provides Firefox/Chromium patched to run on
|
|
13
|
+
# NixOS. The pip `playwright` package reads PLAYWRIGHT_BROWSERS_PATH and
|
|
14
|
+
# uses these browsers directly; its version must match whatever nixpkgs
|
|
15
|
+
# pins here. nodejs is for the a11y env's npm install (axe-core).
|
|
16
|
+
# bash is needed so `nix develop --command` can evaluate shellHook
|
|
17
|
+
# (the devShell otherwise has no shell on PATH).
|
|
18
|
+
packages = with pkgs; [
|
|
19
|
+
bash
|
|
20
|
+
nodejs_latest
|
|
21
|
+
playwright-test
|
|
22
|
+
playwright-driver.browsers
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
shellHook = ''
|
|
26
|
+
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
|
|
27
|
+
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
|
|
28
|
+
# Browsers installed by nix so no-op the install command in tox
|
|
29
|
+
export PLAYWRIGHT_INSTALL_CMD="python -c pass"
|
|
30
|
+
'';
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -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.24.0,<0.25",
|
|
26
26
|
"sphinxext-opengraph>=0.13",
|
|
27
27
|
"sphinx>=7.3.0"
|
|
28
28
|
]
|
|
@@ -37,6 +37,14 @@ docs = [
|
|
|
37
37
|
"sphinx-togglebutton",
|
|
38
38
|
"sunpy",
|
|
39
39
|
]
|
|
40
|
+
test = [
|
|
41
|
+
"pytest>=7.0",
|
|
42
|
+
]
|
|
43
|
+
test-playwright = [
|
|
44
|
+
"playwright==1.64.1",
|
|
45
|
+
"pytest-playwright==0.10.0",
|
|
46
|
+
"playwright-figure-tests",
|
|
47
|
+
]
|
|
40
48
|
|
|
41
49
|
[project.entry-points]
|
|
42
50
|
"sphinx.html_themes" = { sunpy = "sunpy_sphinx_theme" }
|
|
@@ -46,6 +54,13 @@ include-package-data = true
|
|
|
46
54
|
|
|
47
55
|
[tool.setuptools_scm]
|
|
48
56
|
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
markers = "a11y: mark a test as an accessibility test"
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
addopts = ["--durations=10", "-m not mpl_image_compare and not a11y"]
|
|
61
|
+
mpl-results-path = "figure_test_images"
|
|
62
|
+
mpl-use-full-test-name = true
|
|
63
|
+
|
|
49
64
|
[ tool.gilesbot ]
|
|
50
65
|
[ tool.gilesbot.circleci_artifacts.giles ]
|
|
51
66
|
enabled = false
|
|
@@ -65,6 +80,11 @@ include-package-data = true
|
|
|
65
80
|
message = "sunpy docs with devdeps preview"
|
|
66
81
|
report_on_fail = true
|
|
67
82
|
|
|
83
|
+
[ tool.gilesbot.circleci_artifacts.visual-tests-summary ]
|
|
84
|
+
url = "fig_comparison.html"
|
|
85
|
+
message = "screenshot test summary page"
|
|
86
|
+
report_on_fail = true
|
|
87
|
+
|
|
68
88
|
[ tool.gilesbot.circleci_artifacts.astropy_website ]
|
|
69
89
|
url = "astropy.org/_build/html/index.html"
|
|
70
90
|
message = "Astropy website preview"
|
|
@@ -49,17 +49,16 @@ lint.extend-ignore = [
|
|
|
49
49
|
"setup.py" = [
|
|
50
50
|
"D100", # Missing docstring in public module
|
|
51
51
|
]
|
|
52
|
-
"test_*.py" = [
|
|
52
|
+
"{src/sunpy_sphinx_theme/tests/*.py,conftest.py,test_*.py}" = [
|
|
53
53
|
"D100", # Missing docstring in public module
|
|
54
54
|
"D103", # Missing docstring in public function
|
|
55
|
-
"
|
|
55
|
+
"INP001", # tests are not part of an implicit namespace package
|
|
56
56
|
"S101", # Use of `assert` detected
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
"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)
|
|
60
|
+
"PLR0913", # Too many arguments in function definition
|
|
61
|
+
"PLR0917", # Too many positional arguments
|
|
63
62
|
]
|
|
64
63
|
|
|
65
64
|
[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))
|
|
@@ -188,28 +191,43 @@ def generate_search_config(app):
|
|
|
188
191
|
This function parses the config for the "Documentation" section of the theme config.
|
|
189
192
|
"""
|
|
190
193
|
theme_config = get_theme_options(app)
|
|
191
|
-
|
|
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 []
|
|
192
218
|
if not search_projects:
|
|
193
219
|
navbar_links = theme_config["navbar_links"]
|
|
194
|
-
doc_links = next(section[1] for section in navbar_links if section[0] == "Documentation")
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
out_links = []
|
|
198
|
-
for link in links:
|
|
199
|
-
if isinstance(link[1], list):
|
|
200
|
-
out_links += filter_doc_links(link[1])
|
|
201
|
-
elif isinstance(link[1], str) and link[1].startswith("http"):
|
|
202
|
-
out_links.append({"name": link[0], "link": link[1]})
|
|
203
|
-
else:
|
|
204
|
-
err = f"Unable to parse {link} in the nav tree. Try setting search_projects explicitly or fixing navbar_links."
|
|
205
|
-
raise ValueError(err)
|
|
206
|
-
return out_links
|
|
207
|
-
|
|
208
|
-
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)
|
|
209
223
|
|
|
210
224
|
if extra_search_projects := theme_config.get("rtd_extra_search_projects", None):
|
|
211
225
|
search_projects += filter_doc_links(extra_search_projects)
|
|
212
226
|
|
|
227
|
+
if not search_projects:
|
|
228
|
+
logger.info("No RTD search projects were found, disabling RTD search.")
|
|
229
|
+
return
|
|
230
|
+
|
|
213
231
|
load_more_label = theme_config.get("rtd_search_load_more_label", "Load more results")
|
|
214
232
|
no_results_label = theme_config.get("rtd_search_no_results_label", "There are no results for this search")
|
|
215
233
|
script = dedent(f"""
|
|
@@ -227,30 +245,24 @@ def generate_search_config(app):
|
|
|
227
245
|
app.add_js_file(None, body=script)
|
|
228
246
|
|
|
229
247
|
|
|
230
|
-
def
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
app.add_html_theme("sunpy", theme_dir)
|
|
236
|
-
app.add_css_file("sunpy_style.css", priority=600)
|
|
237
|
-
app.connect("builder-inited", update_config, priority=100)
|
|
238
|
-
app.connect("builder-inited", generate_search_config, priority=500)
|
|
239
|
-
app.connect("html-page-context", update_opengraph_context, priority=400)
|
|
240
|
-
app.connect("html-page-context", update_html_context)
|
|
241
|
-
# Conditionally include goat counter js
|
|
242
|
-
# We can't do this in update_config as that causes the scripts to be duplicated.
|
|
243
|
-
# Also in here none of the theme defaults have be applied by `update_config`
|
|
244
|
-
# TODO: Improve this mess
|
|
245
|
-
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)
|
|
246
253
|
# We want to default to the sunpy goat counter only if the sst_site_root is sunpy.org
|
|
247
254
|
root_domain = theme_options.get("sst_site_root", "https://sunpy.org")
|
|
248
255
|
sunpy_goat_url = "https://sunpy.goatcounter.com/count"
|
|
249
|
-
default_goat_url =
|
|
250
|
-
|
|
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:
|
|
251
263
|
root_domain = root_domain.removeprefix("https://").removeprefix("http://")
|
|
252
264
|
default_endpoint = theme_options.get("goatcounter_non_domain_endpoint", False)
|
|
253
|
-
if default_endpoint
|
|
265
|
+
if default_endpoint in (False, "False"): # sphinx config can sometimes be str
|
|
254
266
|
default_endpoint = ""
|
|
255
267
|
app.add_js_file(
|
|
256
268
|
None,
|
|
@@ -270,18 +282,25 @@ def setup(app: Sphinx):
|
|
|
270
282
|
"https://gc.zgo.at/count.js",
|
|
271
283
|
loading_method="async",
|
|
272
284
|
)
|
|
273
|
-
app.add_js_file(
|
|
274
|
-
"js/submenu-concertina-toggle.js",
|
|
275
|
-
loading_method="async",
|
|
276
|
-
)
|
|
277
285
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
+
)
|
|
285
304
|
|
|
286
305
|
return {
|
|
287
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
|
+
]
|