Sphinx 7.2.6__py3-none-any.whl → 7.3.1__py3-none-any.whl
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.
Potentially problematic release.
This version of Sphinx might be problematic. Click here for more details.
- sphinx/__init__.py +8 -9
- sphinx/addnodes.py +31 -28
- sphinx/application.py +9 -15
- sphinx/builders/__init__.py +5 -6
- sphinx/builders/_epub_base.py +17 -9
- sphinx/builders/changes.py +10 -5
- sphinx/builders/dirhtml.py +4 -2
- sphinx/builders/dummy.py +3 -2
- sphinx/builders/epub3.py +5 -3
- sphinx/builders/gettext.py +24 -7
- sphinx/builders/html/__init__.py +88 -96
- sphinx/builders/html/_assets.py +16 -16
- sphinx/builders/html/transforms.py +4 -2
- sphinx/builders/latex/__init__.py +40 -33
- sphinx/builders/latex/nodes.py +6 -2
- sphinx/builders/latex/transforms.py +17 -8
- sphinx/builders/latex/util.py +1 -1
- sphinx/builders/linkcheck.py +86 -27
- sphinx/builders/manpage.py +8 -6
- sphinx/builders/singlehtml.py +5 -4
- sphinx/builders/texinfo.py +18 -14
- sphinx/builders/text.py +3 -2
- sphinx/builders/xml.py +5 -2
- sphinx/cmd/build.py +119 -76
- sphinx/cmd/make_mode.py +4 -9
- sphinx/cmd/quickstart.py +13 -16
- sphinx/config.py +432 -250
- sphinx/deprecation.py +23 -13
- sphinx/directives/__init__.py +8 -8
- sphinx/directives/code.py +7 -7
- sphinx/directives/other.py +23 -13
- sphinx/directives/patches.py +7 -6
- sphinx/domains/__init__.py +2 -2
- sphinx/domains/c/__init__.py +796 -0
- sphinx/domains/c/_ast.py +1421 -0
- sphinx/domains/c/_ids.py +65 -0
- sphinx/domains/c/_parser.py +1048 -0
- sphinx/domains/c/_symbol.py +700 -0
- sphinx/domains/changeset.py +11 -7
- sphinx/domains/citation.py +5 -2
- sphinx/domains/cpp/__init__.py +1089 -0
- sphinx/domains/cpp/_ast.py +3635 -0
- sphinx/domains/cpp/_ids.py +537 -0
- sphinx/domains/cpp/_parser.py +2117 -0
- sphinx/domains/cpp/_symbol.py +1092 -0
- sphinx/domains/index.py +6 -4
- sphinx/domains/javascript.py +16 -13
- sphinx/domains/math.py +9 -4
- sphinx/domains/python/__init__.py +890 -0
- sphinx/domains/python/_annotations.py +507 -0
- sphinx/domains/python/_object.py +426 -0
- sphinx/domains/rst.py +12 -7
- sphinx/domains/{std.py → std/__init__.py} +19 -16
- sphinx/environment/__init__.py +21 -19
- sphinx/environment/adapters/indexentries.py +2 -2
- sphinx/environment/adapters/toctree.py +10 -9
- sphinx/environment/collectors/__init__.py +6 -3
- sphinx/environment/collectors/asset.py +4 -3
- sphinx/environment/collectors/dependencies.py +3 -2
- sphinx/environment/collectors/metadata.py +6 -5
- sphinx/environment/collectors/title.py +3 -2
- sphinx/environment/collectors/toctree.py +5 -4
- sphinx/errors.py +13 -2
- sphinx/events.py +14 -9
- sphinx/ext/apidoc.py +9 -11
- sphinx/ext/autodoc/__init__.py +105 -71
- sphinx/ext/autodoc/directive.py +7 -6
- sphinx/ext/autodoc/importer.py +102 -36
- sphinx/ext/autodoc/mock.py +7 -5
- sphinx/ext/autodoc/preserve_defaults.py +4 -3
- sphinx/ext/autodoc/type_comment.py +2 -1
- sphinx/ext/autodoc/typehints.py +5 -4
- sphinx/ext/autosectionlabel.py +3 -2
- sphinx/ext/autosummary/__init__.py +21 -17
- sphinx/ext/autosummary/generate.py +9 -9
- sphinx/ext/coverage.py +26 -20
- sphinx/ext/doctest.py +38 -33
- sphinx/ext/duration.py +1 -0
- sphinx/ext/extlinks.py +4 -3
- sphinx/ext/githubpages.py +3 -2
- sphinx/ext/graphviz.py +10 -7
- sphinx/ext/ifconfig.py +5 -5
- sphinx/ext/imgconverter.py +6 -5
- sphinx/ext/imgmath.py +9 -8
- sphinx/ext/inheritance_diagram.py +31 -31
- sphinx/ext/intersphinx.py +140 -23
- sphinx/ext/linkcode.py +3 -2
- sphinx/ext/mathjax.py +2 -1
- sphinx/ext/napoleon/__init__.py +12 -7
- sphinx/ext/napoleon/docstring.py +34 -32
- sphinx/ext/todo.py +10 -7
- sphinx/ext/viewcode.py +12 -11
- sphinx/extension.py +18 -8
- sphinx/highlighting.py +39 -20
- sphinx/io.py +17 -8
- sphinx/jinja2glue.py +16 -15
- sphinx/locale/__init__.py +30 -23
- sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ar/LC_MESSAGES/sphinx.po +818 -761
- sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bg/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bn/LC_MESSAGES/sphinx.po +835 -778
- sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ca/LC_MESSAGES/sphinx.po +864 -807
- sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cak/LC_MESSAGES/sphinx.po +816 -759
- sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cs/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cy/LC_MESSAGES/sphinx.po +819 -762
- sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/da/LC_MESSAGES/sphinx.po +838 -781
- sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de/LC_MESSAGES/sphinx.po +838 -781
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/el/LC_MESSAGES/sphinx.po +853 -796
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +856 -799
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eo/LC_MESSAGES/sphinx.po +820 -763
- sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es/LC_MESSAGES/sphinx.po +856 -799
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/et/LC_MESSAGES/sphinx.po +845 -788
- sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eu/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fa/LC_MESSAGES/sphinx.po +854 -797
- sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fi/LC_MESSAGES/sphinx.po +816 -759
- sphinx/locale/fr/LC_MESSAGES/sphinx.js +1 -1
- sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr/LC_MESSAGES/sphinx.po +904 -847
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/gl/LC_MESSAGES/sphinx.js +54 -54
- sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.po +1506 -1449
- sphinx/locale/he/LC_MESSAGES/sphinx.js +1 -1
- sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.po +823 -766
- sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi/LC_MESSAGES/sphinx.po +853 -796
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hr/LC_MESSAGES/sphinx.po +844 -787
- sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hu/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/id/LC_MESSAGES/sphinx.po +854 -797
- sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/is/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/it/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ja/LC_MESSAGES/sphinx.po +853 -796
- sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ka/LC_MESSAGES/sphinx.po +848 -791
- sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ko/LC_MESSAGES/sphinx.po +855 -798
- sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lt/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lv/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/mk/LC_MESSAGES/sphinx.po +825 -768
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.js +27 -27
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +876 -818
- sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ne/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nl/LC_MESSAGES/sphinx.po +844 -787
- sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pl/LC_MESSAGES/sphinx.po +845 -788
- sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +908 -851
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ro/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ru/LC_MESSAGES/sphinx.po +838 -781
- sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/si/LC_MESSAGES/sphinx.po +823 -766
- sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sk/LC_MESSAGES/sphinx.po +854 -797
- sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sl/LC_MESSAGES/sphinx.po +832 -775
- sphinx/locale/sphinx.pot +813 -755
- sphinx/locale/sq/LC_MESSAGES/sphinx.js +1 -1
- sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sq/LC_MESSAGES/sphinx.po +865 -808
- sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr/LC_MESSAGES/sphinx.po +835 -778
- sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sv/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
- sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ta/LC_MESSAGES/sphinx.po +1530 -1473
- sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/te/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/tr/LC_MESSAGES/sphinx.po +853 -796
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +833 -776
- sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ur/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/vi/LC_MESSAGES/sphinx.po +837 -780
- sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/yue/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +855 -798
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +811 -754
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.js +1 -1
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +879 -822
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +811 -754
- sphinx/parsers.py +7 -5
- sphinx/project.py +18 -11
- sphinx/pycode/__init__.py +6 -5
- sphinx/pycode/ast.py +23 -8
- sphinx/pycode/parser.py +6 -5
- sphinx/registry.py +12 -6
- sphinx/roles.py +103 -57
- sphinx/search/__init__.py +17 -18
- sphinx/search/da.py +2 -2
- sphinx/search/de.py +2 -2
- sphinx/search/en.py +1 -1
- sphinx/search/es.py +2 -2
- sphinx/search/fi.py +2 -2
- sphinx/search/fr.py +2 -2
- sphinx/search/hu.py +2 -2
- sphinx/search/it.py +2 -2
- sphinx/search/ja.py +13 -22
- sphinx/search/nl.py +2 -2
- sphinx/search/no.py +2 -2
- sphinx/search/pt.py +2 -2
- sphinx/search/ro.py +1 -1
- sphinx/search/ru.py +2 -2
- sphinx/search/sv.py +2 -2
- sphinx/search/tr.py +1 -1
- sphinx/search/zh.py +2 -3
- sphinx/templates/graphviz/graphviz.css +1 -1
- sphinx/testing/fixtures.py +41 -24
- sphinx/testing/path.py +1 -1
- sphinx/testing/util.py +142 -53
- sphinx/texinputs/sphinx.xdy +1 -1
- sphinx/texinputs/sphinxlatextables.sty +1 -1
- sphinx/texinputs/sphinxpackagesubstitutefont.sty +21 -0
- sphinx/themes/agogo/layout.html +4 -4
- sphinx/themes/agogo/static/agogo.css_t +1 -1
- sphinx/themes/agogo/theme.toml +22 -0
- sphinx/themes/basic/defindex.html +1 -1
- sphinx/themes/basic/domainindex.html +1 -1
- sphinx/themes/basic/genindex-single.html +1 -1
- sphinx/themes/basic/genindex-split.html +1 -1
- sphinx/themes/basic/genindex.html +1 -1
- sphinx/themes/basic/globaltoc.html +1 -1
- sphinx/themes/basic/layout.html +1 -1
- sphinx/themes/basic/localtoc.html +1 -1
- sphinx/themes/basic/page.html +1 -1
- sphinx/themes/basic/relations.html +1 -1
- sphinx/themes/basic/search.html +5 -20
- sphinx/themes/basic/searchbox.html +3 -3
- sphinx/themes/basic/searchfield.html +3 -3
- sphinx/themes/basic/sourcelink.html +1 -1
- sphinx/themes/basic/static/basic.css_t +1 -1
- sphinx/themes/basic/static/doctools.js +1 -1
- sphinx/themes/basic/static/language_data.js_t +2 -2
- sphinx/themes/basic/static/searchtools.js +105 -60
- sphinx/themes/basic/theme.toml +23 -0
- sphinx/themes/bizstyle/layout.html +1 -6
- sphinx/themes/bizstyle/static/bizstyle.css_t +1 -1
- sphinx/themes/bizstyle/static/bizstyle.js_t +1 -1
- sphinx/themes/bizstyle/static/css3-mediaqueries_src.js +3 -3
- sphinx/themes/bizstyle/theme.toml +12 -0
- sphinx/themes/classic/layout.html +1 -1
- sphinx/themes/classic/static/classic.css_t +1 -1
- sphinx/themes/classic/static/sidebar.js_t +1 -1
- sphinx/themes/classic/theme.toml +34 -0
- sphinx/themes/default/theme.toml +2 -0
- sphinx/themes/epub/epub-cover.html +1 -1
- sphinx/themes/epub/layout.html +1 -1
- sphinx/themes/epub/static/epub.css_t +1 -1
- sphinx/themes/epub/theme.toml +10 -0
- sphinx/themes/haiku/layout.html +3 -3
- sphinx/themes/haiku/static/haiku.css_t +2 -2
- sphinx/themes/haiku/theme.toml +16 -0
- sphinx/themes/nature/static/nature.css_t +1 -1
- sphinx/themes/nature/theme.toml +6 -0
- sphinx/themes/nonav/layout.html +1 -1
- sphinx/themes/nonav/static/nonav.css_t +1 -1
- sphinx/themes/nonav/theme.toml +10 -0
- sphinx/themes/pyramid/static/epub.css_t +1 -1
- sphinx/themes/pyramid/static/pyramid.css_t +1 -1
- sphinx/themes/pyramid/theme.toml +6 -0
- sphinx/themes/scrolls/artwork/logo.svg +1 -1
- sphinx/themes/scrolls/layout.html +2 -2
- sphinx/themes/scrolls/static/scrolls.css_t +1 -1
- sphinx/themes/scrolls/theme.toml +15 -0
- sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +1 -1
- sphinx/themes/sphinxdoc/theme.toml +6 -0
- sphinx/themes/traditional/static/traditional.css_t +1 -1
- sphinx/themes/traditional/theme.toml +9 -0
- sphinx/theming.py +427 -131
- sphinx/transforms/__init__.py +21 -24
- sphinx/transforms/compact_bullet_list.py +5 -5
- sphinx/transforms/i18n.py +30 -28
- sphinx/transforms/post_transforms/__init__.py +9 -7
- sphinx/transforms/post_transforms/code.py +4 -1
- sphinx/transforms/post_transforms/images.py +17 -13
- sphinx/transforms/references.py +3 -1
- sphinx/util/__init__.py +15 -11
- sphinx/util/_io.py +34 -0
- sphinx/util/_pathlib.py +23 -18
- sphinx/util/build_phase.py +1 -0
- sphinx/util/cfamily.py +19 -11
- sphinx/util/console.py +101 -21
- sphinx/util/display.py +3 -2
- sphinx/util/docfields.py +12 -8
- sphinx/util/docutils.py +21 -35
- sphinx/util/exceptions.py +3 -2
- sphinx/util/fileutil.py +5 -5
- sphinx/util/http_date.py +9 -2
- sphinx/util/i18n.py +40 -9
- sphinx/util/inspect.py +317 -245
- sphinx/util/inventory.py +22 -5
- sphinx/util/logging.py +81 -7
- sphinx/util/matching.py +2 -1
- sphinx/util/math.py +1 -2
- sphinx/util/nodes.py +39 -29
- sphinx/util/osutil.py +25 -6
- sphinx/util/parallel.py +6 -1
- sphinx/util/requests.py +8 -5
- sphinx/util/rst.py +8 -6
- sphinx/util/tags.py +3 -3
- sphinx/util/template.py +8 -3
- sphinx/util/typing.py +76 -42
- sphinx/versioning.py +6 -2
- sphinx/writers/html.py +1 -1
- sphinx/writers/html5.py +17 -13
- sphinx/writers/latex.py +12 -12
- sphinx/writers/manpage.py +13 -7
- sphinx/writers/texinfo.py +13 -10
- sphinx/writers/text.py +13 -23
- sphinx/writers/xml.py +1 -1
- sphinx-7.2.6.dist-info/LICENSE → sphinx-7.3.1.dist-info/LICENSE.rst +1 -1
- {sphinx-7.2.6.dist-info → sphinx-7.3.1.dist-info}/METADATA +14 -12
- sphinx-7.3.1.dist-info/RECORD +581 -0
- sphinx/domains/c.py +0 -3906
- sphinx/domains/cpp.py +0 -8233
- sphinx/domains/python.py +0 -1769
- sphinx/themes/agogo/theme.conf +0 -20
- sphinx/themes/basic/theme.conf +0 -16
- sphinx/themes/bizstyle/theme.conf +0 -10
- sphinx/themes/classic/theme.conf +0 -32
- sphinx/themes/default/theme.conf +0 -2
- sphinx/themes/epub/theme.conf +0 -8
- sphinx/themes/haiku/theme.conf +0 -14
- sphinx/themes/nature/theme.conf +0 -4
- sphinx/themes/nonav/theme.conf +0 -8
- sphinx/themes/pyramid/theme.conf +0 -4
- sphinx/themes/scrolls/theme.conf +0 -13
- sphinx/themes/sphinxdoc/theme.conf +0 -4
- sphinx/themes/traditional/theme.conf +0 -7
- sphinx-7.2.6.dist-info/RECORD +0 -569
- {sphinx-7.2.6.dist-info → sphinx-7.3.1.dist-info}/WHEEL +0 -0
- {sphinx-7.2.6.dist-info → sphinx-7.3.1.dist-info}/entry_points.txt +0 -0
sphinx/theming.py
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
__all__ = ('Theme', 'HTMLThemeFactory')
|
|
6
|
+
|
|
5
7
|
import configparser
|
|
8
|
+
import contextlib
|
|
6
9
|
import os
|
|
7
10
|
import shutil
|
|
8
11
|
import sys
|
|
@@ -11,117 +14,127 @@ from os import path
|
|
|
11
14
|
from typing import TYPE_CHECKING, Any
|
|
12
15
|
from zipfile import ZipFile
|
|
13
16
|
|
|
14
|
-
if sys.version_info >= (3, 10):
|
|
15
|
-
from importlib.metadata import entry_points
|
|
16
|
-
else:
|
|
17
|
-
from importlib_metadata import entry_points
|
|
18
|
-
|
|
19
|
-
import contextlib
|
|
20
|
-
|
|
21
17
|
from sphinx import package_dir
|
|
18
|
+
from sphinx.config import check_confval_types as _config_post_init
|
|
22
19
|
from sphinx.errors import ThemeError
|
|
23
20
|
from sphinx.locale import __
|
|
24
21
|
from sphinx.util import logging
|
|
25
22
|
from sphinx.util.osutil import ensuredir
|
|
26
23
|
|
|
24
|
+
if sys.version_info >= (3, 11):
|
|
25
|
+
import tomllib
|
|
26
|
+
else:
|
|
27
|
+
import tomli as tomllib
|
|
28
|
+
|
|
29
|
+
if sys.version_info >= (3, 10):
|
|
30
|
+
from importlib.metadata import entry_points
|
|
31
|
+
else:
|
|
32
|
+
from importlib_metadata import entry_points
|
|
33
|
+
|
|
27
34
|
if TYPE_CHECKING:
|
|
35
|
+
from typing import TypedDict
|
|
36
|
+
|
|
37
|
+
from typing_extensions import Required
|
|
38
|
+
|
|
28
39
|
from sphinx.application import Sphinx
|
|
29
40
|
|
|
41
|
+
class _ThemeToml(TypedDict, total=False):
|
|
42
|
+
theme: Required[_ThemeTomlTheme]
|
|
43
|
+
options: dict[str, str]
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
class _ThemeTomlTheme(TypedDict, total=False):
|
|
46
|
+
inherit: Required[str]
|
|
47
|
+
stylesheets: list[str]
|
|
48
|
+
sidebars: list[str]
|
|
49
|
+
pygments_style: _ThemeTomlThemePygments
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
class _ThemeTomlThemePygments(TypedDict, total=False):
|
|
52
|
+
default: str
|
|
53
|
+
dark: str
|
|
35
54
|
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
"""Extract zip file to target directory."""
|
|
39
|
-
ensuredir(targetdir)
|
|
56
|
+
logger = logging.getLogger(__name__)
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
continue
|
|
45
|
-
entry = path.join(targetdir, name)
|
|
46
|
-
ensuredir(path.dirname(entry))
|
|
47
|
-
with open(path.join(entry), 'wb') as fp:
|
|
48
|
-
fp.write(archive.read(name))
|
|
58
|
+
_NO_DEFAULT = object()
|
|
59
|
+
_THEME_TOML = 'theme.toml'
|
|
60
|
+
_THEME_CONF = 'theme.conf'
|
|
49
61
|
|
|
50
62
|
|
|
51
63
|
class Theme:
|
|
52
64
|
"""A Theme is a set of HTML templates and configurations.
|
|
53
65
|
|
|
54
|
-
This class supports both theme directory and theme archive (zipped theme).
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
This class supports both theme directory and theme archive (zipped theme).
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
name: str,
|
|
72
|
+
*,
|
|
73
|
+
configs: dict[str, _ConfigFile],
|
|
74
|
+
paths: list[str],
|
|
75
|
+
tmp_dirs: list[str],
|
|
76
|
+
) -> None:
|
|
57
77
|
self.name = name
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
except configparser.NoOptionError as exc:
|
|
79
|
-
raise ThemeError(__('theme %r doesn\'t have "inherit" setting') % name) from exc
|
|
80
|
-
|
|
81
|
-
if inherit != 'none':
|
|
82
|
-
try:
|
|
83
|
-
self.base = factory.create(inherit)
|
|
84
|
-
except ThemeError as exc:
|
|
85
|
-
raise ThemeError(__('no theme named %r found, inherited by %r') %
|
|
86
|
-
(inherit, name)) from exc
|
|
78
|
+
self._dirs = tuple(paths)
|
|
79
|
+
self._tmp_dirs = tmp_dirs
|
|
80
|
+
|
|
81
|
+
options: dict[str, Any] = {}
|
|
82
|
+
self.stylesheets: tuple[str, ...] = ()
|
|
83
|
+
self.sidebar_templates: tuple[str, ...] = ()
|
|
84
|
+
self.pygments_style_default: str | None = None
|
|
85
|
+
self.pygments_style_dark: str | None = None
|
|
86
|
+
for config in reversed(configs.values()):
|
|
87
|
+
options |= config.options
|
|
88
|
+
if config.stylesheets is not None:
|
|
89
|
+
self.stylesheets = config.stylesheets
|
|
90
|
+
if config.sidebar_templates is not None:
|
|
91
|
+
self.sidebar_templates = config.sidebar_templates
|
|
92
|
+
if config.pygments_style_default is not None:
|
|
93
|
+
self.pygments_style_default = config.pygments_style_default
|
|
94
|
+
if config.pygments_style_dark is not None:
|
|
95
|
+
self.pygments_style_dark = config.pygments_style_dark
|
|
96
|
+
|
|
97
|
+
self._options = options
|
|
87
98
|
|
|
88
99
|
def get_theme_dirs(self) -> list[str]:
|
|
89
100
|
"""Return a list of theme directories, beginning with this theme's,
|
|
90
101
|
then the base theme's, then that one's base theme's, etc.
|
|
91
102
|
"""
|
|
92
|
-
|
|
93
|
-
return [self.themedir]
|
|
94
|
-
else:
|
|
95
|
-
return [self.themedir] + self.base.get_theme_dirs()
|
|
103
|
+
return list(self._dirs)
|
|
96
104
|
|
|
97
|
-
def get_config(self, section: str, name: str, default: Any =
|
|
105
|
+
def get_config(self, section: str, name: str, default: Any = _NO_DEFAULT) -> Any:
|
|
98
106
|
"""Return the value for a theme configuration setting, searching the
|
|
99
107
|
base theme chain.
|
|
100
108
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
if section == 'theme':
|
|
110
|
+
if name == 'stylesheet':
|
|
111
|
+
value = ', '.join(self.stylesheets) or default
|
|
112
|
+
elif name == 'sidebars':
|
|
113
|
+
value = ', '.join(self.sidebar_templates) or default
|
|
114
|
+
elif name == 'pygments_style':
|
|
115
|
+
value = self.pygments_style_default or default
|
|
116
|
+
elif name == 'pygments_dark_style':
|
|
117
|
+
value = self.pygments_style_dark or default
|
|
118
|
+
else:
|
|
119
|
+
value = default
|
|
120
|
+
elif section == 'options':
|
|
121
|
+
value = self._options.get(name, default)
|
|
122
|
+
else:
|
|
123
|
+
value = _NO_DEFAULT
|
|
124
|
+
if value is _NO_DEFAULT:
|
|
125
|
+
msg = __('setting %s.%s occurs in none of the searched theme configs') % (
|
|
126
|
+
section,
|
|
127
|
+
name,
|
|
128
|
+
)
|
|
129
|
+
raise ThemeError(msg)
|
|
130
|
+
return value
|
|
111
131
|
|
|
112
132
|
def get_options(self, overrides: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
113
133
|
"""Return a dictionary of theme options and their values."""
|
|
114
134
|
if overrides is None:
|
|
115
135
|
overrides = {}
|
|
116
136
|
|
|
117
|
-
|
|
118
|
-
options = self.base.get_options()
|
|
119
|
-
else:
|
|
120
|
-
options = {}
|
|
121
|
-
|
|
122
|
-
with contextlib.suppress(configparser.NoSectionError):
|
|
123
|
-
options.update(self.config.items('options'))
|
|
124
|
-
|
|
137
|
+
options = self._options.copy()
|
|
125
138
|
for option, value in overrides.items():
|
|
126
139
|
if option not in options:
|
|
127
140
|
logger.warning(__('unsupported theme option %r given') % option)
|
|
@@ -130,77 +143,53 @@ class Theme:
|
|
|
130
143
|
|
|
131
144
|
return options
|
|
132
145
|
|
|
133
|
-
def
|
|
146
|
+
def _cleanup(self) -> None:
|
|
134
147
|
"""Remove temporary directories."""
|
|
135
|
-
|
|
148
|
+
for tmp_dir in self._tmp_dirs:
|
|
136
149
|
with contextlib.suppress(Exception):
|
|
137
|
-
shutil.rmtree(
|
|
138
|
-
|
|
139
|
-
if self.base:
|
|
140
|
-
self.base.cleanup()
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def is_archived_theme(filename: str) -> bool:
|
|
144
|
-
"""Check whether the specified file is an archived theme file or not."""
|
|
145
|
-
try:
|
|
146
|
-
with ZipFile(filename) as f:
|
|
147
|
-
return THEMECONF in f.namelist()
|
|
148
|
-
except Exception:
|
|
149
|
-
return False
|
|
150
|
+
shutil.rmtree(tmp_dir)
|
|
150
151
|
|
|
151
152
|
|
|
152
153
|
class HTMLThemeFactory:
|
|
153
154
|
"""A factory class for HTML Themes."""
|
|
154
155
|
|
|
155
156
|
def __init__(self, app: Sphinx) -> None:
|
|
156
|
-
self.
|
|
157
|
-
self.
|
|
158
|
-
self.
|
|
157
|
+
self._app = app
|
|
158
|
+
self._themes = app.registry.html_themes
|
|
159
|
+
self._load_builtin_themes()
|
|
159
160
|
if getattr(app.config, 'html_theme_path', None):
|
|
160
|
-
self.
|
|
161
|
+
self._load_additional_themes(app.config.html_theme_path)
|
|
161
162
|
|
|
162
|
-
def
|
|
163
|
+
def _load_builtin_themes(self) -> None:
|
|
163
164
|
"""Load built-in themes."""
|
|
164
|
-
themes = self.
|
|
165
|
+
themes = self._find_themes(path.join(package_dir, 'themes'))
|
|
165
166
|
for name, theme in themes.items():
|
|
166
|
-
self.
|
|
167
|
+
self._themes[name] = theme
|
|
167
168
|
|
|
168
|
-
def
|
|
169
|
+
def _load_additional_themes(self, theme_paths: str) -> None:
|
|
169
170
|
"""Load additional themes placed at specified directories."""
|
|
170
171
|
for theme_path in theme_paths:
|
|
171
|
-
abs_theme_path = path.abspath(path.join(self.
|
|
172
|
-
themes = self.
|
|
172
|
+
abs_theme_path = path.abspath(path.join(self._app.confdir, theme_path))
|
|
173
|
+
themes = self._find_themes(abs_theme_path)
|
|
173
174
|
for name, theme in themes.items():
|
|
174
|
-
self.
|
|
175
|
-
|
|
176
|
-
def load_extra_theme(self, name: str) -> None:
|
|
177
|
-
"""Try to load a theme with the specified name."""
|
|
178
|
-
if name == 'alabaster':
|
|
179
|
-
self.load_alabaster_theme()
|
|
180
|
-
else:
|
|
181
|
-
self.load_external_theme(name)
|
|
175
|
+
self._themes[name] = theme
|
|
182
176
|
|
|
183
|
-
def
|
|
184
|
-
"""
|
|
185
|
-
import alabaster
|
|
186
|
-
self.themes['alabaster'] = path.join(alabaster.get_path(), 'alabaster')
|
|
177
|
+
def _load_extra_theme(self, name: str) -> None:
|
|
178
|
+
"""Try to load a theme with the specified name.
|
|
187
179
|
|
|
188
|
-
|
|
189
|
-
"""Try to load a theme using entry_points.
|
|
190
|
-
|
|
191
|
-
Sphinx refers to ``sphinx_themes`` entry_points.
|
|
180
|
+
This uses the ``sphinx.html_themes`` entry point from package metadata.
|
|
192
181
|
"""
|
|
193
|
-
# look up for new styled entry_points at first
|
|
194
182
|
theme_entry_points = entry_points(group='sphinx.html_themes')
|
|
195
183
|
try:
|
|
196
184
|
entry_point = theme_entry_points[name]
|
|
197
|
-
self.app.registry.load_extension(self.app, entry_point.module)
|
|
198
|
-
self.app.config.post_init_values()
|
|
199
|
-
return
|
|
200
185
|
except KeyError:
|
|
201
186
|
pass
|
|
187
|
+
else:
|
|
188
|
+
self._app.registry.load_extension(self._app, entry_point.module)
|
|
189
|
+
_config_post_init(self._app, self._app.config)
|
|
202
190
|
|
|
203
|
-
|
|
191
|
+
@staticmethod
|
|
192
|
+
def _find_themes(theme_path: str) -> dict[str, str]:
|
|
204
193
|
"""Search themes from specified directory."""
|
|
205
194
|
themes: dict[str, str] = {}
|
|
206
195
|
if not path.isdir(theme_path):
|
|
@@ -209,24 +198,331 @@ class HTMLThemeFactory:
|
|
|
209
198
|
for entry in os.listdir(theme_path):
|
|
210
199
|
pathname = path.join(theme_path, entry)
|
|
211
200
|
if path.isfile(pathname) and entry.lower().endswith('.zip'):
|
|
212
|
-
if
|
|
201
|
+
if _is_archived_theme(pathname):
|
|
213
202
|
name = entry[:-4]
|
|
214
203
|
themes[name] = pathname
|
|
215
204
|
else:
|
|
216
|
-
logger.warning(
|
|
217
|
-
|
|
205
|
+
logger.warning(
|
|
206
|
+
__(
|
|
207
|
+
'file %r on theme path is not a valid '
|
|
208
|
+
'zipfile or contains no theme'
|
|
209
|
+
),
|
|
210
|
+
entry,
|
|
211
|
+
)
|
|
218
212
|
else:
|
|
219
|
-
|
|
213
|
+
toml_path = path.join(pathname, _THEME_TOML)
|
|
214
|
+
conf_path = path.join(pathname, _THEME_CONF)
|
|
215
|
+
if path.isfile(toml_path) or path.isfile(conf_path):
|
|
220
216
|
themes[entry] = pathname
|
|
221
217
|
|
|
222
218
|
return themes
|
|
223
219
|
|
|
224
220
|
def create(self, name: str) -> Theme:
|
|
225
221
|
"""Create an instance of theme."""
|
|
226
|
-
if name not in self.
|
|
227
|
-
self.
|
|
222
|
+
if name not in self._themes:
|
|
223
|
+
self._load_extra_theme(name)
|
|
224
|
+
|
|
225
|
+
if name not in self._themes:
|
|
226
|
+
raise ThemeError(__('no theme named %r found (missing theme.toml?)') % name)
|
|
227
|
+
|
|
228
|
+
themes, theme_dirs, tmp_dirs = _load_theme_with_ancestors(self._themes, name)
|
|
229
|
+
return Theme(name, configs=themes, paths=theme_dirs, tmp_dirs=tmp_dirs)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _is_archived_theme(filename: str, /) -> bool:
|
|
233
|
+
"""Check whether the specified file is an archived theme file or not."""
|
|
234
|
+
try:
|
|
235
|
+
with ZipFile(filename) as f:
|
|
236
|
+
namelist = frozenset(f.namelist())
|
|
237
|
+
return _THEME_TOML in namelist or _THEME_CONF in namelist
|
|
238
|
+
except Exception:
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _load_theme_with_ancestors(
|
|
243
|
+
theme_paths: dict[str, str], name: str, /
|
|
244
|
+
) -> tuple[dict[str, _ConfigFile], list[str], list[str]]:
|
|
245
|
+
themes: dict[str, _ConfigFile] = {}
|
|
246
|
+
theme_dirs: list[str] = []
|
|
247
|
+
tmp_dirs: list[str] = []
|
|
248
|
+
|
|
249
|
+
# having 10+ theme ancestors is ludicrous
|
|
250
|
+
for _ in range(10):
|
|
251
|
+
inherit, theme_dir, tmp_dir, config = _load_theme(name, theme_paths[name])
|
|
252
|
+
theme_dirs.append(theme_dir)
|
|
253
|
+
if tmp_dir is not None:
|
|
254
|
+
tmp_dirs.append(tmp_dir)
|
|
255
|
+
themes[name] = config
|
|
256
|
+
if inherit == 'none':
|
|
257
|
+
break
|
|
258
|
+
if inherit in themes:
|
|
259
|
+
msg = __('The %r theme has circular inheritance') % name
|
|
260
|
+
raise ThemeError(msg)
|
|
261
|
+
if inherit not in theme_paths:
|
|
262
|
+
msg = __(
|
|
263
|
+
'The %r theme inherits from %r, which is not a loaded theme. '
|
|
264
|
+
'Loaded themes are: %s'
|
|
265
|
+
) % (name, inherit, ', '.join(sorted(theme_paths)))
|
|
266
|
+
raise ThemeError(msg)
|
|
267
|
+
name = inherit
|
|
268
|
+
else:
|
|
269
|
+
msg = __('The %r theme has too many ancestors') % name
|
|
270
|
+
raise ThemeError(msg)
|
|
271
|
+
|
|
272
|
+
return themes, theme_dirs, tmp_dirs
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _load_theme(name: str, theme_path: str, /) -> tuple[str, str, str | None, _ConfigFile]:
|
|
276
|
+
if path.isdir(theme_path):
|
|
277
|
+
# already a directory, do nothing
|
|
278
|
+
tmp_dir = None
|
|
279
|
+
theme_dir = theme_path
|
|
280
|
+
else:
|
|
281
|
+
# extract the theme to a temp directory
|
|
282
|
+
tmp_dir = tempfile.mkdtemp('sxt')
|
|
283
|
+
theme_dir = path.join(tmp_dir, name)
|
|
284
|
+
_extract_zip(theme_path, theme_dir)
|
|
285
|
+
|
|
286
|
+
if path.isfile(toml_path := path.join(theme_dir, _THEME_TOML)):
|
|
287
|
+
_cfg_table = _load_theme_toml(toml_path)
|
|
288
|
+
inherit = _validate_theme_toml(_cfg_table, name)
|
|
289
|
+
config = _convert_theme_toml(_cfg_table)
|
|
290
|
+
elif path.isfile(conf_path := path.join(theme_dir, _THEME_CONF)):
|
|
291
|
+
_cfg_parser = _load_theme_conf(conf_path)
|
|
292
|
+
inherit = _validate_theme_conf(_cfg_parser, name)
|
|
293
|
+
config = _convert_theme_conf(_cfg_parser)
|
|
294
|
+
else:
|
|
295
|
+
raise ThemeError(__('no theme configuration file found in %r') % theme_dir)
|
|
296
|
+
|
|
297
|
+
return inherit, theme_dir, tmp_dir, config
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _extract_zip(filename: str, target_dir: str, /) -> None:
|
|
301
|
+
"""Extract zip file to target directory."""
|
|
302
|
+
ensuredir(target_dir)
|
|
303
|
+
|
|
304
|
+
with ZipFile(filename) as archive:
|
|
305
|
+
for name in archive.namelist():
|
|
306
|
+
if name.endswith('/'):
|
|
307
|
+
continue
|
|
308
|
+
entry = path.join(target_dir, name)
|
|
309
|
+
ensuredir(path.dirname(entry))
|
|
310
|
+
with open(path.join(entry), 'wb') as fp:
|
|
311
|
+
fp.write(archive.read(name))
|
|
228
312
|
|
|
229
|
-
if name not in self.themes:
|
|
230
|
-
raise ThemeError(__('no theme named %r found (missing theme.conf?)') % name)
|
|
231
313
|
|
|
232
|
-
|
|
314
|
+
def _load_theme_toml(config_file_path: str, /) -> _ThemeToml:
|
|
315
|
+
with open(config_file_path, encoding='utf-8') as f:
|
|
316
|
+
config_text = f.read()
|
|
317
|
+
c = tomllib.loads(config_text)
|
|
318
|
+
return {s: c[s] for s in ('theme', 'options') if s in c} # type: ignore[return-value]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _validate_theme_toml(cfg: _ThemeToml, name: str) -> str:
|
|
322
|
+
if 'theme' not in cfg:
|
|
323
|
+
msg = __('theme %r doesn\'t have the "theme" table') % name
|
|
324
|
+
raise ThemeError(msg)
|
|
325
|
+
theme = cfg['theme']
|
|
326
|
+
if not isinstance(theme, dict):
|
|
327
|
+
msg = __('The %r theme "[theme]" table is not a table') % name
|
|
328
|
+
raise ThemeError(msg)
|
|
329
|
+
inherit = theme.get('inherit', '')
|
|
330
|
+
if not inherit:
|
|
331
|
+
msg = __('The %r theme must define the "theme.inherit" setting') % name
|
|
332
|
+
raise ThemeError(msg)
|
|
333
|
+
if 'options' in cfg:
|
|
334
|
+
if not isinstance(cfg['options'], dict):
|
|
335
|
+
msg = __('The %r theme "[options]" table is not a table') % name
|
|
336
|
+
raise ThemeError(msg)
|
|
337
|
+
return inherit
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _convert_theme_toml(cfg: _ThemeToml, /) -> _ConfigFile:
|
|
341
|
+
theme = cfg['theme']
|
|
342
|
+
if 'stylesheets' in theme:
|
|
343
|
+
stylesheets: tuple[str, ...] | None = tuple(theme['stylesheets'])
|
|
344
|
+
else:
|
|
345
|
+
stylesheets = None
|
|
346
|
+
if 'sidebars' in theme:
|
|
347
|
+
sidebar_templates: tuple[str, ...] | None = tuple(theme['sidebars'])
|
|
348
|
+
else:
|
|
349
|
+
sidebar_templates = None
|
|
350
|
+
pygments_table = theme.get('pygments_style', {})
|
|
351
|
+
if isinstance(pygments_table, str):
|
|
352
|
+
hint = f'pygments_style = {{ default = "{pygments_table}" }}'
|
|
353
|
+
msg = __('The "theme.pygments_style" setting must be a table. Hint: "%s"') % hint
|
|
354
|
+
raise ThemeError(msg)
|
|
355
|
+
pygments_style_default: str | None = pygments_table.get('default')
|
|
356
|
+
pygments_style_dark: str | None = pygments_table.get('dark')
|
|
357
|
+
return _ConfigFile(
|
|
358
|
+
stylesheets=stylesheets,
|
|
359
|
+
sidebar_templates=sidebar_templates,
|
|
360
|
+
pygments_style_default=pygments_style_default,
|
|
361
|
+
pygments_style_dark=pygments_style_dark,
|
|
362
|
+
options=cfg.get('options', {}),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _load_theme_conf(config_file_path: str, /) -> configparser.RawConfigParser:
|
|
367
|
+
c = configparser.RawConfigParser()
|
|
368
|
+
c.read(config_file_path, encoding='utf-8')
|
|
369
|
+
return c
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _validate_theme_conf(cfg: configparser.RawConfigParser, name: str) -> str:
|
|
373
|
+
if not cfg.has_section('theme'):
|
|
374
|
+
raise ThemeError(__('theme %r doesn\'t have the "theme" table') % name)
|
|
375
|
+
if inherit := cfg.get('theme', 'inherit', fallback=None):
|
|
376
|
+
return inherit
|
|
377
|
+
msg = __('The %r theme must define the "theme.inherit" setting') % name
|
|
378
|
+
raise ThemeError(msg)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _convert_theme_conf(cfg: configparser.RawConfigParser, /) -> _ConfigFile:
|
|
382
|
+
if stylesheet := cfg.get('theme', 'stylesheet', fallback=''):
|
|
383
|
+
stylesheets: tuple[str, ...] | None = tuple(map(str.strip, stylesheet.split(',')))
|
|
384
|
+
else:
|
|
385
|
+
stylesheets = None
|
|
386
|
+
if sidebar := cfg.get('theme', 'sidebars', fallback=''):
|
|
387
|
+
sidebar_templates: tuple[str, ...] | None = tuple(map(str.strip, sidebar.split(',')))
|
|
388
|
+
else:
|
|
389
|
+
sidebar_templates = None
|
|
390
|
+
pygments_style_default: str | None = cfg.get('theme', 'pygments_style', fallback=None)
|
|
391
|
+
pygments_style_dark: str | None = cfg.get('theme', 'pygments_dark_style', fallback=None)
|
|
392
|
+
options = dict(cfg.items('options')) if cfg.has_section('options') else {}
|
|
393
|
+
return _ConfigFile(
|
|
394
|
+
stylesheets=stylesheets,
|
|
395
|
+
sidebar_templates=sidebar_templates,
|
|
396
|
+
pygments_style_default=pygments_style_default,
|
|
397
|
+
pygments_style_dark=pygments_style_dark,
|
|
398
|
+
options=options,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class _ConfigFile:
|
|
403
|
+
__slots__ = (
|
|
404
|
+
'stylesheets',
|
|
405
|
+
'sidebar_templates',
|
|
406
|
+
'pygments_style_default',
|
|
407
|
+
'pygments_style_dark',
|
|
408
|
+
'options',
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def __init__(
|
|
412
|
+
self,
|
|
413
|
+
stylesheets: tuple[str, ...] | None,
|
|
414
|
+
sidebar_templates: tuple[str, ...] | None,
|
|
415
|
+
pygments_style_default: str | None,
|
|
416
|
+
pygments_style_dark: str | None,
|
|
417
|
+
options: dict[str, str],
|
|
418
|
+
) -> None:
|
|
419
|
+
self.stylesheets: tuple[str, ...] | None = stylesheets
|
|
420
|
+
self.sidebar_templates: tuple[str, ...] | None = sidebar_templates
|
|
421
|
+
self.pygments_style_default: str | None = pygments_style_default
|
|
422
|
+
self.pygments_style_dark: str | None = pygments_style_dark
|
|
423
|
+
self.options: dict[str, str] = options.copy()
|
|
424
|
+
|
|
425
|
+
def __repr__(self) -> str:
|
|
426
|
+
return (
|
|
427
|
+
f'{self.__class__.__qualname__}('
|
|
428
|
+
f'stylesheets={self.stylesheets!r}, '
|
|
429
|
+
f'sidebar_templates={self.sidebar_templates!r}, '
|
|
430
|
+
f'pygments_style_default={self.pygments_style_default!r}, '
|
|
431
|
+
f'pygments_style_dark={self.pygments_style_dark!r}, '
|
|
432
|
+
f'options={self.options!r})'
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def __eq__(self, other: object) -> bool:
|
|
436
|
+
if isinstance(other, _ConfigFile):
|
|
437
|
+
return (
|
|
438
|
+
self.stylesheets == other.stylesheets
|
|
439
|
+
and self.sidebar_templates == other.sidebar_templates
|
|
440
|
+
and self.pygments_style_default == other.pygments_style_default
|
|
441
|
+
and self.pygments_style_dark == other.pygments_style_dark
|
|
442
|
+
and self.options == other.options
|
|
443
|
+
)
|
|
444
|
+
return NotImplemented
|
|
445
|
+
|
|
446
|
+
def __hash__(self) -> int:
|
|
447
|
+
return hash((
|
|
448
|
+
self.__class__.__qualname__,
|
|
449
|
+
self.stylesheets,
|
|
450
|
+
self.sidebar_templates,
|
|
451
|
+
self.pygments_style_default,
|
|
452
|
+
self.pygments_style_dark,
|
|
453
|
+
self.options,
|
|
454
|
+
))
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _migrate_conf_to_toml(argv: list[str]) -> int:
|
|
458
|
+
if argv[:1] != ['conf_to_toml']:
|
|
459
|
+
raise SystemExit(0)
|
|
460
|
+
argv = argv[1:]
|
|
461
|
+
if len(argv) != 1:
|
|
462
|
+
print('Usage: python -m sphinx.theming conf_to_toml <theme path>') # NoQA: T201
|
|
463
|
+
raise SystemExit(1)
|
|
464
|
+
theme_dir = path.realpath(argv[0])
|
|
465
|
+
conf_path = path.join(theme_dir, _THEME_CONF)
|
|
466
|
+
if not path.isdir(theme_dir) or not path.isfile(conf_path):
|
|
467
|
+
print( # NoQA: T201
|
|
468
|
+
f'{theme_dir!r} must be a path to a theme directory containing a "theme.conf" file'
|
|
469
|
+
)
|
|
470
|
+
return 1
|
|
471
|
+
_cfg_parser = _load_theme_conf(conf_path)
|
|
472
|
+
if not _cfg_parser.has_section('theme'):
|
|
473
|
+
print('The "theme" table is missing.') # NoQA: T201
|
|
474
|
+
return 1
|
|
475
|
+
inherit = _cfg_parser.get('theme', 'inherit', fallback=None)
|
|
476
|
+
if not inherit:
|
|
477
|
+
print('The "theme.inherit" setting is missing.') # NoQA: T201
|
|
478
|
+
return 1
|
|
479
|
+
|
|
480
|
+
toml_lines = [
|
|
481
|
+
'[theme]',
|
|
482
|
+
f'inherit = "{inherit}"',
|
|
483
|
+
]
|
|
484
|
+
|
|
485
|
+
stylesheet = _cfg_parser.get('theme', 'stylesheet', fallback=...)
|
|
486
|
+
if stylesheet == '':
|
|
487
|
+
toml_lines.append('stylesheets = []')
|
|
488
|
+
elif stylesheet is not ...:
|
|
489
|
+
toml_lines.append('stylesheets = [')
|
|
490
|
+
toml_lines.extend(f' "{s}",' for s in map(str.strip, stylesheet.split(',')))
|
|
491
|
+
toml_lines.append(']')
|
|
492
|
+
|
|
493
|
+
sidebar = _cfg_parser.get('theme', 'sidebars', fallback=...)
|
|
494
|
+
if sidebar == '':
|
|
495
|
+
toml_lines.append('sidebars = []')
|
|
496
|
+
elif sidebar is not ...:
|
|
497
|
+
toml_lines.append('sidebars = [')
|
|
498
|
+
toml_lines += [f' "{s}",' for s in map(str.strip, sidebar.split(','))]
|
|
499
|
+
toml_lines.append(']')
|
|
500
|
+
|
|
501
|
+
styles = []
|
|
502
|
+
default = _cfg_parser.get('theme', 'pygments_style', fallback=...)
|
|
503
|
+
if default is not ...:
|
|
504
|
+
styles.append(f'default = "{default}"')
|
|
505
|
+
dark = _cfg_parser.get('theme', 'pygments_dark_style', fallback=...)
|
|
506
|
+
if dark is not ...:
|
|
507
|
+
styles.append(f'dark = "{dark}"')
|
|
508
|
+
if styles:
|
|
509
|
+
toml_lines.append('pygments_style = { ' + ', '.join(styles) + ' }')
|
|
510
|
+
|
|
511
|
+
if _cfg_parser.has_section('options'):
|
|
512
|
+
toml_lines.append('')
|
|
513
|
+
toml_lines.append('[options]')
|
|
514
|
+
toml_lines += [
|
|
515
|
+
f'{key} = "{d}"'
|
|
516
|
+
for key, default in _cfg_parser.items('options')
|
|
517
|
+
if (d := default.replace('"', r'\"')) or True
|
|
518
|
+
]
|
|
519
|
+
|
|
520
|
+
toml_path = path.join(theme_dir, _THEME_TOML)
|
|
521
|
+
with open(toml_path, 'w', encoding='utf-8') as f:
|
|
522
|
+
f.write('\n'.join(toml_lines) + '\n')
|
|
523
|
+
print(f'Written converted settings to {toml_path!r}') # NoQA: T201
|
|
524
|
+
return 0
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
if __name__ == '__main__':
|
|
528
|
+
raise SystemExit(_migrate_conf_to_toml(sys.argv[1:]))
|