Sphinx 8.0.2__py3-none-any.whl → 8.1.0__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 +6 -3
- sphinx/_cli/__init__.py +40 -20
- sphinx/_cli/util/colour.py +5 -4
- sphinx/_cli/util/errors.py +28 -11
- sphinx/application.py +361 -38
- sphinx/builders/__init__.py +229 -83
- sphinx/builders/_epub_base.py +118 -71
- sphinx/builders/changes.py +39 -21
- sphinx/builders/dirhtml.py +4 -4
- sphinx/builders/dummy.py +2 -5
- sphinx/builders/epub3.py +43 -22
- sphinx/builders/gettext.py +43 -25
- sphinx/builders/html/__init__.py +284 -218
- sphinx/builders/html/_assets.py +62 -26
- sphinx/builders/html/_build_info.py +76 -0
- sphinx/builders/html/transforms.py +11 -9
- sphinx/builders/latex/__init__.py +139 -81
- sphinx/builders/latex/constants.py +7 -7
- sphinx/builders/latex/nodes.py +3 -2
- sphinx/builders/latex/theming.py +7 -5
- sphinx/builders/latex/transforms.py +27 -19
- sphinx/builders/linkcheck.py +146 -72
- sphinx/builders/manpage.py +30 -13
- sphinx/builders/singlehtml.py +22 -14
- sphinx/builders/texinfo.py +67 -37
- sphinx/builders/text.py +5 -5
- sphinx/builders/xml.py +6 -9
- sphinx/cmd/build.py +282 -103
- sphinx/cmd/make_mode.py +106 -63
- sphinx/cmd/quickstart.py +341 -145
- sphinx/config.py +45 -12
- sphinx/deprecation.py +8 -2
- sphinx/directives/__init__.py +28 -19
- sphinx/directives/code.py +86 -56
- sphinx/directives/other.py +50 -36
- sphinx/directives/patches.py +29 -19
- sphinx/domains/__init__.py +20 -120
- sphinx/domains/_domains_container.py +281 -0
- sphinx/domains/_index.py +110 -0
- sphinx/domains/c/__init__.py +3 -3
- sphinx/domains/c/_parser.py +10 -6
- sphinx/domains/changeset.py +5 -3
- sphinx/domains/citation.py +5 -3
- sphinx/domains/cpp/__init__.py +9 -11
- sphinx/domains/cpp/_parser.py +8 -7
- sphinx/domains/index.py +3 -3
- sphinx/domains/javascript.py +12 -7
- sphinx/domains/math.py +2 -2
- sphinx/domains/python/__init__.py +10 -5
- sphinx/domains/python/_object.py +1 -1
- sphinx/domains/rst.py +5 -5
- sphinx/domains/std/__init__.py +16 -11
- sphinx/environment/__init__.py +206 -146
- sphinx/environment/adapters/asset.py +3 -2
- sphinx/environment/adapters/indexentries.py +74 -33
- sphinx/environment/adapters/toctree.py +100 -43
- sphinx/environment/collectors/__init__.py +19 -8
- sphinx/environment/collectors/asset.py +47 -15
- sphinx/environment/collectors/dependencies.py +8 -4
- sphinx/environment/collectors/metadata.py +7 -2
- sphinx/environment/collectors/title.py +7 -2
- sphinx/environment/collectors/toctree.py +54 -22
- sphinx/errors.py +4 -1
- sphinx/events.py +314 -7
- sphinx/ext/apidoc.py +42 -18
- sphinx/ext/autodoc/__init__.py +52 -24
- sphinx/ext/autodoc/importer.py +6 -9
- sphinx/ext/autosectionlabel.py +1 -2
- sphinx/ext/autosummary/__init__.py +3 -1
- sphinx/ext/autosummary/generate.py +28 -14
- sphinx/ext/coverage.py +7 -7
- sphinx/ext/doctest.py +4 -8
- sphinx/ext/duration.py +6 -5
- sphinx/ext/inheritance_diagram.py +1 -1
- sphinx/ext/intersphinx/_cli.py +6 -4
- sphinx/ext/intersphinx/_load.py +77 -32
- sphinx/ext/intersphinx/_resolve.py +173 -79
- sphinx/ext/intersphinx/_shared.py +7 -5
- sphinx/ext/linkcode.py +7 -1
- sphinx/ext/mathjax.py +1 -2
- sphinx/ext/napoleon/__init__.py +37 -24
- sphinx/ext/napoleon/docstring.py +202 -134
- sphinx/ext/todo.py +5 -3
- sphinx/highlighting.py +9 -2
- sphinx/io.py +1 -1
- sphinx/jinja2glue.py +27 -6
- sphinx/locale/__init__.py +6 -2
- sphinx/locale/ar/LC_MESSAGES/sphinx.js +8 -1
- sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ar/LC_MESSAGES/sphinx.po +2246 -2288
- sphinx/locale/bg/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bg/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/bn/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bn/LC_MESSAGES/sphinx.po +2349 -2395
- sphinx/locale/ca/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ca/LC_MESSAGES/sphinx.po +2846 -2892
- sphinx/locale/cak/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cak/LC_MESSAGES/sphinx.po +2213 -2259
- sphinx/locale/cs/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cs/LC_MESSAGES/sphinx.po +2225 -2269
- sphinx/locale/cy/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cy/LC_MESSAGES/sphinx.po +2403 -2447
- sphinx/locale/da/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/da/LC_MESSAGES/sphinx.po +2214 -2260
- sphinx/locale/de/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de/LC_MESSAGES/sphinx.po +2230 -2276
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/el/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/el/LC_MESSAGES/sphinx.po +2619 -2665
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2519 -2565
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/eo/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eo/LC_MESSAGES/sphinx.po +2232 -2278
- sphinx/locale/es/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es/LC_MESSAGES/sphinx.po +2516 -2561
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2114 -2159
- sphinx/locale/et/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/et/LC_MESSAGES/sphinx.po +2317 -2363
- sphinx/locale/eu/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eu/LC_MESSAGES/sphinx.po +2218 -2264
- sphinx/locale/fa/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fa/LC_MESSAGES/sphinx.po +2505 -2551
- sphinx/locale/fi/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fi/LC_MESSAGES/sphinx.po +2303 -2349
- sphinx/locale/fr/LC_MESSAGES/sphinx.js +6 -2
- sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr/LC_MESSAGES/sphinx.po +2863 -2908
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +2114 -2159
- sphinx/locale/gl/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.po +2571 -2617
- sphinx/locale/he/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.po +2307 -2352
- sphinx/locale/hi/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi/LC_MESSAGES/sphinx.po +2580 -2626
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/hr/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hr/LC_MESSAGES/sphinx.po +2238 -2283
- sphinx/locale/hu/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hu/LC_MESSAGES/sphinx.po +2228 -2274
- sphinx/locale/id/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/id/LC_MESSAGES/sphinx.po +2787 -2834
- sphinx/locale/is/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/is/LC_MESSAGES/sphinx.po +2224 -2270
- sphinx/locale/it/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/it/LC_MESSAGES/sphinx.po +2231 -2276
- sphinx/locale/ja/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ja/LC_MESSAGES/sphinx.po +2507 -2554
- sphinx/locale/ka/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ka/LC_MESSAGES/sphinx.po +2428 -2474
- sphinx/locale/ko/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ko/LC_MESSAGES/sphinx.po +2516 -2563
- sphinx/locale/lt/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lt/LC_MESSAGES/sphinx.po +2425 -2469
- sphinx/locale/lv/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lv/LC_MESSAGES/sphinx.po +2362 -2407
- sphinx/locale/mk/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/mk/LC_MESSAGES/sphinx.po +2121 -2167
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2220 -2266
- sphinx/locale/ne/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ne/LC_MESSAGES/sphinx.po +2221 -2267
- sphinx/locale/nl/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nl/LC_MESSAGES/sphinx.po +2240 -2286
- sphinx/locale/pl/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pl/LC_MESSAGES/sphinx.po +2319 -2363
- sphinx/locale/pt/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt/LC_MESSAGES/sphinx.po +2114 -2159
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2854 -2899
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2224 -2269
- sphinx/locale/ro/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ro/LC_MESSAGES/sphinx.po +2226 -2271
- sphinx/locale/ru/LC_MESSAGES/sphinx.js +8 -3
- sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ru/LC_MESSAGES/sphinx.po +2841 -2885
- sphinx/locale/si/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/si/LC_MESSAGES/sphinx.po +2294 -2340
- sphinx/locale/sk/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sk/LC_MESSAGES/sphinx.po +2497 -2541
- sphinx/locale/sl/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sl/LC_MESSAGES/sphinx.po +2331 -2375
- sphinx/locale/sphinx.pot +2121 -2167
- sphinx/locale/sq/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sq/LC_MESSAGES/sphinx.po +2855 -2901
- sphinx/locale/sr/LC_MESSAGES/sphinx.js +5 -1
- sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr/LC_MESSAGES/sphinx.po +2203 -2248
- 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.js +4 -1
- sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sv/LC_MESSAGES/sphinx.po +2423 -2469
- sphinx/locale/te/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/te/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/tr/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/tr/LC_MESSAGES/sphinx.po +2443 -2489
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.js +6 -1
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2329 -2373
- sphinx/locale/ur/LC_MESSAGES/sphinx.js +4 -1
- sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ur/LC_MESSAGES/sphinx.po +2113 -2159
- sphinx/locale/vi/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/vi/LC_MESSAGES/sphinx.po +2199 -2246
- sphinx/locale/yue/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/yue/LC_MESSAGES/sphinx.po +2112 -2159
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2112 -2159
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +2845 -2892
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.js +3 -1
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2112 -2159
- sphinx/parsers.py +3 -1
- sphinx/project.py +6 -2
- sphinx/pycode/__init__.py +11 -4
- sphinx/pycode/ast.py +58 -58
- sphinx/pycode/parser.py +49 -28
- sphinx/pygments_styles.py +49 -49
- sphinx/registry.py +8 -3
- sphinx/roles.py +133 -13
- sphinx/search/__init__.py +146 -87
- sphinx/search/da.py +2 -4
- sphinx/search/de.py +2 -4
- sphinx/search/en.py +4 -4
- sphinx/search/es.py +2 -4
- sphinx/search/fi.py +2 -4
- sphinx/search/fr.py +2 -4
- sphinx/search/hu.py +2 -4
- sphinx/search/it.py +2 -4
- sphinx/search/ja.py +55 -32
- sphinx/search/nl.py +2 -4
- sphinx/search/no.py +2 -4
- sphinx/search/pt.py +2 -4
- sphinx/search/ro.py +0 -2
- sphinx/search/ru.py +2 -4
- sphinx/search/sv.py +2 -4
- sphinx/search/tr.py +0 -2
- sphinx/search/zh.py +18 -13
- sphinx/templates/graphviz/graphviz.css +0 -7
- sphinx/testing/fixtures.py +6 -5
- sphinx/testing/path.py +7 -5
- sphinx/testing/util.py +63 -29
- sphinx/texinputs/sphinx.sty +107 -39
- sphinx/texinputs/sphinxlatexadmonitions.sty +51 -35
- sphinx/texinputs/sphinxlatexcontainers.sty +1 -1
- sphinx/texinputs/sphinxlatexgraphics.sty +3 -2
- sphinx/texinputs/sphinxlatexindbibtoc.sty +1 -1
- sphinx/texinputs/sphinxlatexlists.sty +1 -1
- sphinx/texinputs/sphinxlatexliterals.sty +4 -1
- sphinx/texinputs/sphinxlatexnumfig.sty +22 -9
- sphinx/texinputs/sphinxlatexobjects.sty +1 -1
- sphinx/texinputs/sphinxlatexshadowbox.sty +72 -10
- sphinx/texinputs/sphinxlatexstyleheadings.sty +7 -2
- sphinx/texinputs/sphinxlatexstylepage.sty +2 -8
- sphinx/texinputs/sphinxlatexstyletext.sty +2 -4
- sphinx/texinputs/sphinxlatextables.sty +1 -1
- sphinx/texinputs/sphinxoptionsgeometry.sty +1 -1
- sphinx/texinputs/sphinxoptionshyperref.sty +1 -1
- sphinx/themes/agogo/layout.html +1 -10
- sphinx/themes/agogo/static/agogo.css.jinja +0 -7
- sphinx/themes/basic/defindex.html +1 -8
- sphinx/themes/basic/domainindex.html +1 -9
- sphinx/themes/basic/genindex-single.html +1 -9
- sphinx/themes/basic/genindex-split.html +1 -9
- sphinx/themes/basic/genindex.html +1 -9
- sphinx/themes/basic/globaltoc.html +1 -9
- sphinx/themes/basic/layout.html +1 -9
- sphinx/themes/basic/localtoc.html +1 -9
- sphinx/themes/basic/page.html +1 -9
- sphinx/themes/basic/relations.html +1 -9
- sphinx/themes/basic/search.html +1 -9
- sphinx/themes/basic/searchbox.html +1 -9
- sphinx/themes/basic/searchfield.html +4 -10
- sphinx/themes/basic/sourcelink.html +1 -9
- sphinx/themes/basic/static/basic.css.jinja +2 -13
- sphinx/themes/basic/static/doctools.js +0 -7
- sphinx/themes/basic/static/language_data.js.jinja +0 -7
- sphinx/themes/basic/static/searchtools.js +25 -13
- sphinx/themes/bizstyle/layout.html +1 -9
- sphinx/themes/bizstyle/static/bizstyle.css.jinja +0 -7
- sphinx/themes/bizstyle/static/bizstyle.js.jinja +5 -11
- sphinx/themes/classic/layout.html +1 -9
- sphinx/themes/classic/static/classic.css.jinja +0 -7
- sphinx/themes/classic/static/sidebar.js.jinja +0 -6
- sphinx/themes/epub/epub-cover.html +1 -9
- sphinx/themes/epub/layout.html +1 -9
- sphinx/themes/epub/static/epub.css.jinja +0 -7
- sphinx/themes/haiku/layout.html +1 -9
- sphinx/themes/haiku/static/haiku.css.jinja +0 -6
- sphinx/themes/nature/static/nature.css.jinja +0 -7
- sphinx/themes/nonav/layout.html +1 -9
- sphinx/themes/nonav/static/nonav.css.jinja +0 -7
- sphinx/themes/pyramid/static/epub.css.jinja +0 -7
- sphinx/themes/pyramid/static/pyramid.css.jinja +0 -7
- sphinx/themes/scrolls/layout.html +1 -10
- sphinx/themes/scrolls/static/scrolls.css.jinja +0 -7
- sphinx/themes/sphinxdoc/static/sphinxdoc.css.jinja +2 -7
- sphinx/themes/traditional/static/traditional.css.jinja +0 -7
- sphinx/theming.py +18 -6
- sphinx/transforms/__init__.py +56 -35
- sphinx/transforms/compact_bullet_list.py +3 -2
- sphinx/transforms/i18n.py +132 -50
- sphinx/transforms/post_transforms/__init__.py +94 -43
- sphinx/transforms/post_transforms/code.py +7 -6
- sphinx/transforms/post_transforms/images.py +71 -54
- sphinx/transforms/references.py +1 -2
- sphinx/util/__init__.py +23 -194
- sphinx/util/_files.py +80 -0
- sphinx/util/_importer.py +27 -0
- sphinx/util/_io.py +1 -2
- sphinx/util/_lines.py +26 -0
- sphinx/util/_pathlib.py +5 -2
- sphinx/util/_serialise.py +53 -0
- sphinx/util/_timestamps.py +2 -1
- sphinx/util/_uri.py +16 -0
- sphinx/util/cfamily.py +48 -25
- sphinx/util/console.py +1 -0
- sphinx/util/display.py +1 -1
- sphinx/util/docfields.py +125 -45
- sphinx/util/docstrings.py +1 -1
- sphinx/util/docutils.py +118 -44
- sphinx/util/exceptions.py +11 -5
- sphinx/util/fileutil.py +53 -32
- sphinx/util/http_date.py +9 -7
- sphinx/util/i18n.py +49 -16
- sphinx/util/images.py +7 -6
- sphinx/util/inspect.py +29 -12
- sphinx/util/inventory.py +47 -29
- sphinx/util/logging.py +58 -85
- sphinx/util/matching.py +3 -3
- sphinx/util/math.py +1 -1
- sphinx/util/nodes.py +176 -108
- sphinx/util/osutil.py +13 -10
- sphinx/util/parallel.py +5 -4
- sphinx/util/parsing.py +5 -3
- sphinx/util/png.py +3 -3
- sphinx/util/requests.py +8 -4
- sphinx/util/rst.py +5 -3
- sphinx/util/tags.py +5 -2
- sphinx/util/template.py +26 -11
- sphinx/util/texescape.py +2 -2
- sphinx/util/typing.py +89 -38
- sphinx/versioning.py +3 -1
- sphinx/writers/html.py +22 -7
- sphinx/writers/html5.py +113 -64
- sphinx/writers/latex.py +408 -221
- sphinx/writers/manpage.py +25 -15
- sphinx/writers/texinfo.py +94 -82
- sphinx/writers/text.py +87 -53
- sphinx/writers/xml.py +5 -4
- sphinx-8.1.0.dist-info/LICENSE.rst +31 -0
- {sphinx-8.0.2.dist-info → sphinx-8.1.0.dist-info}/METADATA +13 -11
- sphinx-8.1.0.dist-info/RECORD +598 -0
- sphinx-8.0.2.dist-info/LICENSE.rst +0 -67
- sphinx-8.0.2.dist-info/RECORD +0 -590
- {sphinx-8.0.2.dist-info → sphinx-8.1.0.dist-info}/WHEEL +0 -0
- {sphinx-8.0.2.dist-info → sphinx-8.1.0.dist-info}/entry_points.txt +0 -0
sphinx/util/__init__.py
CHANGED
|
@@ -6,15 +6,14 @@ import hashlib
|
|
|
6
6
|
import os
|
|
7
7
|
import posixpath
|
|
8
8
|
import re
|
|
9
|
-
from
|
|
10
|
-
from os import path
|
|
11
|
-
from typing import IO, Any
|
|
12
|
-
from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
|
|
9
|
+
from typing import Any
|
|
13
10
|
|
|
14
|
-
from sphinx.errors import
|
|
15
|
-
from sphinx.
|
|
11
|
+
from sphinx.errors import FiletypeNotFoundError
|
|
12
|
+
from sphinx.util import _files, _importer, logging
|
|
16
13
|
from sphinx.util import index_entries as _index_entries
|
|
17
|
-
from sphinx.util import
|
|
14
|
+
from sphinx.util._lines import parse_line_num_spec as parselinenos # NoQA: F401
|
|
15
|
+
from sphinx.util._uri import encode_uri # NoQA: F401
|
|
16
|
+
from sphinx.util._uri import is_url as isurl # NoQA: F401
|
|
18
17
|
from sphinx.util.console import strip_colors # NoQA: F401
|
|
19
18
|
from sphinx.util.matching import patfilter # NoQA: F401
|
|
20
19
|
from sphinx.util.nodes import ( # NoQA: F401
|
|
@@ -44,11 +43,14 @@ url_re: re.Pattern[str] = re.compile(r'(?P<schema>.+)://.*')
|
|
|
44
43
|
|
|
45
44
|
# High-level utility functions.
|
|
46
45
|
|
|
46
|
+
|
|
47
47
|
def docname_join(basedocname: str, docname: str) -> str:
|
|
48
48
|
return posixpath.normpath(posixpath.join('/' + basedocname, '..', docname))[1:]
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def get_filetype(
|
|
51
|
+
def get_filetype(
|
|
52
|
+
source_suffix: dict[str, str], filename: str | os.PathLike[str]
|
|
53
|
+
) -> str:
|
|
52
54
|
for suffix, filetype in source_suffix.items():
|
|
53
55
|
if os.fspath(filename).endswith(suffix):
|
|
54
56
|
# If default filetype (None), considered as restructuredtext.
|
|
@@ -56,49 +58,6 @@ def get_filetype(source_suffix: dict[str, str], filename: str | os.PathLike) ->
|
|
|
56
58
|
raise FiletypeNotFoundError
|
|
57
59
|
|
|
58
60
|
|
|
59
|
-
class FilenameUniqDict(dict):
|
|
60
|
-
"""
|
|
61
|
-
A dictionary that automatically generates unique names for its keys,
|
|
62
|
-
interpreted as filenames, and keeps track of a set of docnames they
|
|
63
|
-
appear in. Used for images and downloadable files in the environment.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
def __init__(self) -> None:
|
|
67
|
-
self._existing: set[str] = set()
|
|
68
|
-
|
|
69
|
-
def add_file(self, docname: str, newfile: str) -> str:
|
|
70
|
-
if newfile in self:
|
|
71
|
-
self[newfile][0].add(docname)
|
|
72
|
-
return self[newfile][1]
|
|
73
|
-
uniquename = path.basename(newfile)
|
|
74
|
-
base, ext = path.splitext(uniquename)
|
|
75
|
-
i = 0
|
|
76
|
-
while uniquename in self._existing:
|
|
77
|
-
i += 1
|
|
78
|
-
uniquename = f'{base}{i}{ext}'
|
|
79
|
-
self[newfile] = ({docname}, uniquename)
|
|
80
|
-
self._existing.add(uniquename)
|
|
81
|
-
return uniquename
|
|
82
|
-
|
|
83
|
-
def purge_doc(self, docname: str) -> None:
|
|
84
|
-
for filename, (docs, unique) in list(self.items()):
|
|
85
|
-
docs.discard(docname)
|
|
86
|
-
if not docs:
|
|
87
|
-
del self[filename]
|
|
88
|
-
self._existing.discard(unique)
|
|
89
|
-
|
|
90
|
-
def merge_other(self, docnames: set[str], other: dict[str, tuple[set[str], Any]]) -> None:
|
|
91
|
-
for filename, (docs, _unique) in other.items():
|
|
92
|
-
for doc in docs & set(docnames):
|
|
93
|
-
self.add_file(doc, filename)
|
|
94
|
-
|
|
95
|
-
def __getstate__(self) -> set[str]:
|
|
96
|
-
return self._existing
|
|
97
|
-
|
|
98
|
-
def __setstate__(self, state: set[str]) -> None:
|
|
99
|
-
self._existing = state
|
|
100
|
-
|
|
101
|
-
|
|
102
61
|
def _md5(data: bytes = b'', **_kw: Any) -> hashlib._Hash:
|
|
103
62
|
"""Deprecated wrapper around hashlib.md5
|
|
104
63
|
|
|
@@ -115,153 +74,23 @@ def _sha1(data: bytes = b'', **_kw: Any) -> hashlib._Hash:
|
|
|
115
74
|
return hashlib.sha1(data, usedforsecurity=False)
|
|
116
75
|
|
|
117
76
|
|
|
118
|
-
class DownloadFiles(dict):
|
|
119
|
-
"""A special dictionary for download files.
|
|
120
|
-
|
|
121
|
-
.. important:: This class would be refactored in nearly future.
|
|
122
|
-
Hence don't hack this directly.
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
def add_file(self, docname: str, filename: str) -> str:
|
|
126
|
-
if filename not in self:
|
|
127
|
-
digest = hashlib.md5(filename.encode(), usedforsecurity=False).hexdigest()
|
|
128
|
-
dest = f'{digest}/{os.path.basename(filename)}'
|
|
129
|
-
self[filename] = (set(), dest)
|
|
130
|
-
|
|
131
|
-
self[filename][0].add(docname)
|
|
132
|
-
return self[filename][1]
|
|
133
|
-
|
|
134
|
-
def purge_doc(self, docname: str) -> None:
|
|
135
|
-
for filename, (docs, _dest) in list(self.items()):
|
|
136
|
-
docs.discard(docname)
|
|
137
|
-
if not docs:
|
|
138
|
-
del self[filename]
|
|
139
|
-
|
|
140
|
-
def merge_other(self, docnames: set[str], other: dict[str, tuple[set[str], Any]]) -> None:
|
|
141
|
-
for filename, (docs, _dest) in other.items():
|
|
142
|
-
for docname in docs & set(docnames):
|
|
143
|
-
self.add_file(docname, filename)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# a regex to recognize coding cookies
|
|
147
|
-
_coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
class UnicodeDecodeErrorHandler:
|
|
151
|
-
"""Custom error handler for open() that warns and replaces."""
|
|
152
|
-
|
|
153
|
-
def __init__(self, docname: str) -> None:
|
|
154
|
-
self.docname = docname
|
|
155
|
-
|
|
156
|
-
def __call__(self, error: UnicodeDecodeError) -> tuple[str, int]:
|
|
157
|
-
linestart = error.object.rfind(b'\n', 0, error.start)
|
|
158
|
-
lineend = error.object.find(b'\n', error.start)
|
|
159
|
-
if lineend == -1:
|
|
160
|
-
lineend = len(error.object)
|
|
161
|
-
lineno = error.object.count(b'\n', 0, error.start) + 1
|
|
162
|
-
logger.warning(__('undecodable source characters, replacing with "?": %r'),
|
|
163
|
-
(error.object[linestart + 1:error.start] + b'>>>' +
|
|
164
|
-
error.object[error.start:error.end] + b'<<<' +
|
|
165
|
-
error.object[error.end:lineend]),
|
|
166
|
-
location=(self.docname, lineno))
|
|
167
|
-
return ('?', error.end)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
# Low-level utility functions and classes.
|
|
171
|
-
|
|
172
|
-
class Tee:
|
|
173
|
-
"""
|
|
174
|
-
File-like object writing to two streams.
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
def __init__(self, stream1: IO, stream2: IO) -> None:
|
|
178
|
-
self.stream1 = stream1
|
|
179
|
-
self.stream2 = stream2
|
|
180
|
-
|
|
181
|
-
def write(self, text: str) -> None:
|
|
182
|
-
self.stream1.write(text)
|
|
183
|
-
self.stream2.write(text)
|
|
184
|
-
|
|
185
|
-
def flush(self) -> None:
|
|
186
|
-
if hasattr(self.stream1, 'flush'):
|
|
187
|
-
self.stream1.flush()
|
|
188
|
-
if hasattr(self.stream2, 'flush'):
|
|
189
|
-
self.stream2.flush()
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def parselinenos(spec: str, total: int) -> list[int]:
|
|
193
|
-
"""Parse a line number spec (such as "1,2,4-6") and return a list of
|
|
194
|
-
wanted line numbers.
|
|
195
|
-
"""
|
|
196
|
-
items = []
|
|
197
|
-
parts = spec.split(',')
|
|
198
|
-
for part in parts:
|
|
199
|
-
try:
|
|
200
|
-
begend = part.strip().split('-')
|
|
201
|
-
if begend == ['', '']:
|
|
202
|
-
raise ValueError
|
|
203
|
-
if len(begend) == 1:
|
|
204
|
-
items.append(int(begend[0]) - 1)
|
|
205
|
-
elif len(begend) == 2:
|
|
206
|
-
start = int(begend[0] or 1) # left half open (cf. -10)
|
|
207
|
-
end = int(begend[1] or max(start, total)) # right half open (cf. 10-)
|
|
208
|
-
if start > end: # invalid range (cf. 10-1)
|
|
209
|
-
raise ValueError
|
|
210
|
-
items.extend(range(start - 1, end))
|
|
211
|
-
else:
|
|
212
|
-
raise ValueError
|
|
213
|
-
except ValueError as exc:
|
|
214
|
-
msg = f'invalid line number spec: {spec!r}'
|
|
215
|
-
raise ValueError(msg) from exc
|
|
216
|
-
|
|
217
|
-
return items
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def import_object(objname: str, source: str | None = None) -> Any:
|
|
221
|
-
"""Import python object by qualname."""
|
|
222
|
-
try:
|
|
223
|
-
objpath = objname.split('.')
|
|
224
|
-
modname = objpath.pop(0)
|
|
225
|
-
obj = import_module(modname)
|
|
226
|
-
for name in objpath:
|
|
227
|
-
modname += '.' + name
|
|
228
|
-
try:
|
|
229
|
-
obj = getattr(obj, name)
|
|
230
|
-
except AttributeError:
|
|
231
|
-
obj = import_module(modname)
|
|
232
|
-
|
|
233
|
-
return obj
|
|
234
|
-
except (AttributeError, ImportError) as exc:
|
|
235
|
-
if source:
|
|
236
|
-
raise ExtensionError('Could not import %s (needed for %s)' %
|
|
237
|
-
(objname, source), exc) from exc
|
|
238
|
-
raise ExtensionError('Could not import %s' % objname, exc) from exc
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def encode_uri(uri: str) -> str:
|
|
242
|
-
split = list(urlsplit(uri))
|
|
243
|
-
split[1] = split[1].encode('idna').decode('ascii')
|
|
244
|
-
split[2] = quote_plus(split[2].encode(), '/')
|
|
245
|
-
query = [(q, v.encode()) for (q, v) in parse_qsl(split[3])]
|
|
246
|
-
split[3] = urlencode(query)
|
|
247
|
-
return urlunsplit(split)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def isurl(url: str) -> bool:
|
|
251
|
-
"""Check *url* is URL or not."""
|
|
252
|
-
return bool(url) and '://' in url
|
|
253
|
-
|
|
254
|
-
|
|
255
77
|
# deprecated name -> (object to return, canonical path or empty string)
|
|
256
78
|
_DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
|
|
257
|
-
'split_index_msg': (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
79
|
+
'split_index_msg': (
|
|
80
|
+
_index_entries.split_index_msg,
|
|
81
|
+
'sphinx.util.index_entries.split_index_msg',
|
|
82
|
+
(9, 0),
|
|
83
|
+
),
|
|
84
|
+
'split_into': (
|
|
85
|
+
_index_entries.split_index_msg,
|
|
86
|
+
'sphinx.util.index_entries.split_into',
|
|
87
|
+
(9, 0),
|
|
88
|
+
),
|
|
263
89
|
'md5': (_md5, '', (9, 0)),
|
|
264
90
|
'sha1': (_sha1, '', (9, 0)),
|
|
91
|
+
'import_object': (_importer.import_object, '', (10, 0)),
|
|
92
|
+
'FilenameUniqDict': (_files.FilenameUniqDict, '', (10, 0)),
|
|
93
|
+
'DownloadFiles': (_files.DownloadFiles, '', (10, 0)),
|
|
265
94
|
}
|
|
266
95
|
|
|
267
96
|
|
sphinx/util/_files.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os.path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FilenameUniqDict(dict[str, tuple[set[str], str]]):
|
|
9
|
+
"""
|
|
10
|
+
A dictionary that automatically generates unique names for its keys,
|
|
11
|
+
interpreted as filenames, and keeps track of a set of docnames they
|
|
12
|
+
appear in. Used for images and downloadable files in the environment.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self._existing: set[str] = set()
|
|
17
|
+
|
|
18
|
+
def add_file(self, docname: str, newfile: str) -> str:
|
|
19
|
+
if newfile in self:
|
|
20
|
+
self[newfile][0].add(docname)
|
|
21
|
+
return self[newfile][1]
|
|
22
|
+
uniquename = os.path.basename(newfile)
|
|
23
|
+
base, ext = os.path.splitext(uniquename)
|
|
24
|
+
i = 0
|
|
25
|
+
while uniquename in self._existing:
|
|
26
|
+
i += 1
|
|
27
|
+
uniquename = f'{base}{i}{ext}'
|
|
28
|
+
self[newfile] = ({docname}, uniquename)
|
|
29
|
+
self._existing.add(uniquename)
|
|
30
|
+
return uniquename
|
|
31
|
+
|
|
32
|
+
def purge_doc(self, docname: str) -> None:
|
|
33
|
+
for filename, (docs, unique) in list(self.items()):
|
|
34
|
+
docs.discard(docname)
|
|
35
|
+
if not docs:
|
|
36
|
+
del self[filename]
|
|
37
|
+
self._existing.discard(unique)
|
|
38
|
+
|
|
39
|
+
def merge_other(
|
|
40
|
+
self, docnames: set[str], other: dict[str, tuple[set[str], Any]]
|
|
41
|
+
) -> None:
|
|
42
|
+
for filename, (docs, _unique) in other.items():
|
|
43
|
+
for doc in docs & set(docnames):
|
|
44
|
+
self.add_file(doc, filename)
|
|
45
|
+
|
|
46
|
+
def __getstate__(self) -> set[str]:
|
|
47
|
+
return self._existing
|
|
48
|
+
|
|
49
|
+
def __setstate__(self, state: set[str]) -> None:
|
|
50
|
+
self._existing = state
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DownloadFiles(dict[str, tuple[set[str], str]]):
|
|
54
|
+
"""A special dictionary for download files.
|
|
55
|
+
|
|
56
|
+
.. important:: This class would be refactored in nearly future.
|
|
57
|
+
Hence don't hack this directly.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def add_file(self, docname: str, filename: str) -> str:
|
|
61
|
+
if filename not in self:
|
|
62
|
+
digest = hashlib.md5(filename.encode(), usedforsecurity=False).hexdigest()
|
|
63
|
+
dest = f'{digest}/{os.path.basename(filename)}'
|
|
64
|
+
self[filename] = (set(), dest)
|
|
65
|
+
|
|
66
|
+
self[filename][0].add(docname)
|
|
67
|
+
return self[filename][1]
|
|
68
|
+
|
|
69
|
+
def purge_doc(self, docname: str) -> None:
|
|
70
|
+
for filename, (docs, _dest) in list(self.items()):
|
|
71
|
+
docs.discard(docname)
|
|
72
|
+
if not docs:
|
|
73
|
+
del self[filename]
|
|
74
|
+
|
|
75
|
+
def merge_other(
|
|
76
|
+
self, docnames: set[str], other: dict[str, tuple[set[str], Any]]
|
|
77
|
+
) -> None:
|
|
78
|
+
for filename, (docs, _dest) in other.items():
|
|
79
|
+
for docname in docs & set(docnames):
|
|
80
|
+
self.add_file(docname, filename)
|
sphinx/util/_importer.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from sphinx.errors import ExtensionError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def import_object(object_name: str, /, source: str = '') -> Any:
|
|
10
|
+
"""Import python object by qualname."""
|
|
11
|
+
obj_path = object_name.split('.')
|
|
12
|
+
module_name = obj_path.pop(0)
|
|
13
|
+
try:
|
|
14
|
+
obj = import_module(module_name)
|
|
15
|
+
for name in obj_path:
|
|
16
|
+
module_name += '.' + name
|
|
17
|
+
try:
|
|
18
|
+
obj = getattr(obj, name)
|
|
19
|
+
except AttributeError:
|
|
20
|
+
obj = import_module(module_name)
|
|
21
|
+
except (AttributeError, ImportError) as exc:
|
|
22
|
+
if source:
|
|
23
|
+
msg = f'Could not import {object_name} (needed for {source})'
|
|
24
|
+
raise ExtensionError(msg, exc) from exc
|
|
25
|
+
msg = f'Could not import {object_name}'
|
|
26
|
+
raise ExtensionError(msg, exc) from exc
|
|
27
|
+
return obj
|
sphinx/util/_io.py
CHANGED
sphinx/util/_lines.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
def parse_line_num_spec(spec: str, total: int) -> list[int]:
|
|
2
|
+
"""Parse a line number spec (such as "1,2,4-6") and return a list of
|
|
3
|
+
wanted line numbers.
|
|
4
|
+
"""
|
|
5
|
+
items = []
|
|
6
|
+
parts = spec.split(',')
|
|
7
|
+
for part in parts:
|
|
8
|
+
try:
|
|
9
|
+
begend = part.strip().split('-')
|
|
10
|
+
if begend == ['', '']:
|
|
11
|
+
raise ValueError
|
|
12
|
+
if len(begend) == 1:
|
|
13
|
+
items.append(int(begend[0]) - 1)
|
|
14
|
+
elif len(begend) == 2:
|
|
15
|
+
start = int(begend[0] or 1) # left half open (cf. -10)
|
|
16
|
+
end = int(begend[1] or max(start, total)) # right half open (cf. 10-)
|
|
17
|
+
if start > end: # invalid range (cf. 10-1)
|
|
18
|
+
raise ValueError
|
|
19
|
+
items.extend(range(start - 1, end))
|
|
20
|
+
else:
|
|
21
|
+
raise ValueError
|
|
22
|
+
except ValueError as exc:
|
|
23
|
+
msg = f'invalid line number spec: {spec!r}'
|
|
24
|
+
raise ValueError(msg) from exc
|
|
25
|
+
|
|
26
|
+
return items
|
sphinx/util/_pathlib.py
CHANGED
|
@@ -33,9 +33,10 @@ _MSG = (
|
|
|
33
33
|
# https://docs.python.org/3/library/stdtypes.html#string-methods
|
|
34
34
|
|
|
35
35
|
if sys.platform == 'win32':
|
|
36
|
+
|
|
36
37
|
class _StrPath(WindowsPath):
|
|
37
38
|
def replace( # type: ignore[override]
|
|
38
|
-
self, old: str, new: str, count: int = -1,
|
|
39
|
+
self, old: str, new: str, count: int = -1, /
|
|
39
40
|
) -> str:
|
|
40
41
|
# replace exists in both Path and str;
|
|
41
42
|
# in Path it makes filesystem changes, so we use the safer str version
|
|
@@ -81,10 +82,12 @@ if sys.platform == 'win32':
|
|
|
81
82
|
def __len__(self) -> int:
|
|
82
83
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
|
83
84
|
return len(self.__str__())
|
|
85
|
+
|
|
84
86
|
else:
|
|
87
|
+
|
|
85
88
|
class _StrPath(PosixPath):
|
|
86
89
|
def replace( # type: ignore[override]
|
|
87
|
-
self, old: str, new: str, count: int = -1,
|
|
90
|
+
self, old: str, new: str, count: int = -1, /
|
|
88
91
|
) -> str:
|
|
89
92
|
# replace exists in both Path and str;
|
|
90
93
|
# in Path it makes filesystem changes, so we use the safer str version
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Serialise objects to a stable representation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import types
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def stable_hash(obj: Any) -> str:
|
|
15
|
+
"""Return a stable hash for a Python data structure.
|
|
16
|
+
|
|
17
|
+
We can't just use the md5 of str(obj) as the order of collections
|
|
18
|
+
may be random.
|
|
19
|
+
"""
|
|
20
|
+
if isinstance(obj, dict):
|
|
21
|
+
obj = sorted(map(stable_hash, obj.items()))
|
|
22
|
+
if isinstance(obj, list | tuple | set | frozenset):
|
|
23
|
+
obj = sorted(map(stable_hash, obj))
|
|
24
|
+
elif isinstance(obj, type | types.FunctionType):
|
|
25
|
+
# The default repr() of functions includes the ID, which is not ideal.
|
|
26
|
+
# We use the fully qualified name instead.
|
|
27
|
+
obj = f'{obj.__module__}.{obj.__qualname__}'
|
|
28
|
+
return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def stable_str(obj: Any, *, indent: int | None = None) -> str:
|
|
32
|
+
"""Return a stable string representation of a Python data structure.
|
|
33
|
+
|
|
34
|
+
We can't just use ``str(obj)`` as the order of collections may be random.
|
|
35
|
+
"""
|
|
36
|
+
return json.dumps(_stable_str_prep(obj), indent=indent)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _stable_str_prep(obj: Any) -> dict[str, Any] | list[Any] | str:
|
|
40
|
+
if isinstance(obj, dict):
|
|
41
|
+
# Convert to a sorted dict
|
|
42
|
+
obj = [(_stable_str_prep(k), _stable_str_prep(v)) for k, v in obj.items()]
|
|
43
|
+
obj.sort()
|
|
44
|
+
return dict(obj)
|
|
45
|
+
if isinstance(obj, list | tuple | set | frozenset):
|
|
46
|
+
# Convert to a sorted list
|
|
47
|
+
return sorted(map(_stable_str_prep, obj), key=str)
|
|
48
|
+
if isinstance(obj, type | types.FunctionType):
|
|
49
|
+
# The default repr() of functions includes the ID, which is not ideal.
|
|
50
|
+
# We use the fully qualified name instead.
|
|
51
|
+
return f'{obj.__module__}.{obj.__qualname__}'
|
|
52
|
+
# We can't do any better, just use the string representation
|
|
53
|
+
return str(obj)
|
sphinx/util/_timestamps.py
CHANGED
|
@@ -9,4 +9,5 @@ def _format_rfc3339_microseconds(timestamp: int, /) -> str:
|
|
|
9
9
|
:param timestamp: The timestamp to format, in microseconds.
|
|
10
10
|
"""
|
|
11
11
|
seconds, fraction = divmod(timestamp, 10**6)
|
|
12
|
-
|
|
12
|
+
time_tuple = time.gmtime(seconds)
|
|
13
|
+
return time.strftime('%Y-%m-%d %H:%M:%S', time_tuple) + f'.{fraction // 1_000}'
|
sphinx/util/_uri.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def encode_uri(uri: str) -> str:
|
|
7
|
+
scheme, netloc, url, query, fragment = urlsplit(uri)
|
|
8
|
+
netloc = netloc.encode('idna').decode('ascii')
|
|
9
|
+
url = quote_plus(url.encode(), '/')
|
|
10
|
+
query = urlencode([(q, v.encode()) for (q, v) in parse_qsl(query)])
|
|
11
|
+
return urlunsplit((scheme, netloc, url, query, fragment))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_url(url: str) -> bool:
|
|
15
|
+
"""Check *url* is URL or not."""
|
|
16
|
+
return bool(url) and '://' in url
|