mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'master' into doc-enhance_extension_dev_i18n
This commit is contained in:
@@ -13,13 +13,14 @@ matrix:
|
||||
include:
|
||||
- python: '3.5'
|
||||
env:
|
||||
- TOXENV=du13
|
||||
- TOXENV=du12
|
||||
- python: '3.6'
|
||||
env:
|
||||
- TOXENV=py36
|
||||
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
|
||||
- TOXENV=du13
|
||||
- python: '3.7'
|
||||
env: TOXENV=py37
|
||||
env:
|
||||
- TOXENV=py37
|
||||
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
|
||||
- python: 'nightly'
|
||||
env: TOXENV=py38
|
||||
- python: '3.6'
|
||||
|
||||
132
CHANGES
132
CHANGES
@@ -1,9 +1,69 @@
|
||||
Release 2.0.0 (in development)
|
||||
Release 2.1.0 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
* Ignore filenames without file extension given to ``Builder.build_specific()``
|
||||
API directly
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
* ``sphinx.ext.autodoc.importer.MockFinder``
|
||||
* ``sphinx.ext.autodoc.importer.MockLoader``
|
||||
* ``sphinx.ext.autodoc.importer.mock()``
|
||||
* ``sphinx.ext.autosummary.autolink_role()``
|
||||
* ``sphinx.util.i18n.find_catalog()``
|
||||
* ``sphinx.util.i18n.find_catalog_files()``
|
||||
* ``sphinx.util.i18n.find_catalog_source_files()``
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 2.0.0 beta2 (in development)
|
||||
====================================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #6096: html: Anchor links are not added to figures
|
||||
* #3620: html: Defer searchindex.js rather than loading it via ajax
|
||||
* #6113: html: Table cells and list items have large margins
|
||||
* #5508: ``linenothreshold`` option for ``highlight`` directive was ignored
|
||||
* texinfo: ``make install-info`` causes syntax error
|
||||
* texinfo: ``make install-info`` fails on macOS
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 2.0.0 beta1 (in development)
|
||||
====================================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
* LaTeX builder now depends on TeX Live 2015 or above.
|
||||
* LaTeX builder (with ``'pdflatex'`` :confval:`latex_engine`) will process
|
||||
Unicode Greek letters in text (not in math mark-up) via the text font and
|
||||
@@ -21,8 +81,11 @@ Dependencies
|
||||
* The sphinxcontrib-websupport package is no longer a dependency
|
||||
* Some packages are separated to sub packages:
|
||||
|
||||
- sphinxcontrib.applehelp
|
||||
- sphinxcontrib.devhelp
|
||||
- sphinxcontrib.htmlhelp
|
||||
- sphinxcontrib.jsmath
|
||||
- sphinxcontrib.serializinghtml
|
||||
- sphinxcontrib.qthelp
|
||||
|
||||
Incompatible changes
|
||||
@@ -58,9 +121,17 @@ Incompatible changes
|
||||
from LaTeX preamble now get overwritten. Use ``\sphinxtableofcontentshook``
|
||||
to insert custom user definitions. See :ref:`latex-macros`.
|
||||
* quickstart: Simplify generated ``conf.py``
|
||||
* #4148: quickstart: some questions are removed. They are still able to specify
|
||||
via command line options
|
||||
* websupport: unbundled from sphinx core. Please use sphinxcontrib-websupport
|
||||
* C++, the visibility of base classes is now always rendered as present in the
|
||||
input. That is, ``private`` is now shown, where it was ellided before.
|
||||
* LaTeX: graphics inclusion of oversized images rescales to not exceed
|
||||
the text width and height, even if width and/or height option were used.
|
||||
(refs: #5956)
|
||||
* epub: ``epub_title`` defaults to the :confval:`project` option
|
||||
* #4550: All tables and figures without ``align`` option are displayed to center
|
||||
* #4587: html: Output HTML5 by default
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
@@ -71,6 +142,10 @@ Deprecated
|
||||
``EpubBuilder.build_container()``, ``EpubBuilder.bulid_content()``,
|
||||
``EpubBuilder.build_toc()`` and ``EpubBuilder.build_epub()``
|
||||
* The arguments of ``Epub3Builder.build_navigation_doc()``
|
||||
* The config variables
|
||||
|
||||
- :confval:`html_experimental_html5_writer`
|
||||
|
||||
* The ``encoding`` argument of ``autodoc.Documenter.get_doc()``,
|
||||
``autodoc.DocstringSignatureMixin.get_doc()``,
|
||||
``autodoc.DocstringSignatureMixin._find_signature()``, and
|
||||
@@ -104,21 +179,30 @@ Deprecated
|
||||
* ``sphinx.io.SphinxFileInput.supported``
|
||||
* ``sphinx.io.SphinxRSTFileInput``
|
||||
* ``sphinx.registry.SphinxComponentRegistry.add_source_input()``
|
||||
* ``sphinx.roles.abbr_role()``
|
||||
* ``sphinx.roles.emph_literal_role()``
|
||||
* ``sphinx.roles.menusel_role()``
|
||||
* ``sphinx.roles.index_role()``
|
||||
* ``sphinx.roles.indexmarkup_role()``
|
||||
* ``sphinx.testing.util.remove_unicode_literal()``
|
||||
* ``sphinx.util.attrdict``
|
||||
* ``sphinx.util.force_decode()``
|
||||
* ``sphinx.util.get_matching_docs()``
|
||||
* ``sphinx.util.inspect.Parameter``
|
||||
* ``sphinx.util.jsonimpl``
|
||||
* ``sphinx.util.osutil.EEXIST``
|
||||
* ``sphinx.util.osutil.EINVAL``
|
||||
* ``sphinx.util.osutil.ENOENT``
|
||||
* ``sphinx.util.osutil.EPIPE``
|
||||
* ``sphinx.util.osutil.walk()``
|
||||
* ``sphinx.util.PeekableIterator``
|
||||
* ``sphinx.util.pycompat.NoneType``
|
||||
* ``sphinx.util.pycompat.TextIOWrapper``
|
||||
* ``sphinx.util.pycompat.UnicodeMixin``
|
||||
* ``sphinx.util.pycompat.htmlescape``
|
||||
* ``sphinx.util.pycompat.indent``
|
||||
* ``sphinx.util.pycompat.sys_encoding``
|
||||
* ``sphinx.util.pycompat.terminal_safe()``
|
||||
* ``sphinx.util.pycompat.u``
|
||||
* ``sphinx.writers.latex.ExtBabel``
|
||||
* ``sphinx.writers.latex.LaTeXTranslator._make_visit_admonition()``
|
||||
@@ -150,6 +234,11 @@ Features added
|
||||
|
||||
* #4182: autodoc: Support :confval:`suppress_warnings`
|
||||
* #5533: autodoc: :confval:`autodoc_default_options` supports ``member-order``
|
||||
* #5394: autodoc: Display readable names in type annotations for mocked objects
|
||||
* #5459: autodoc: :confval:`autodoc_default_options` accepts ``True`` as a value
|
||||
* #1148: autodoc: Add :rst:dir:`autodecorator` directive for decorators
|
||||
* #5635: autosummary: Add :confval:`autosummary_mock_imports` to mock external
|
||||
libraries on importing targets
|
||||
* #4018: htmlhelp: Add :confval:`htmlhelp_file_suffix` and
|
||||
:confval:`htmlhelp_link_suffix`
|
||||
* #5559: text: Support complex tables (colspan and rowspan)
|
||||
@@ -166,6 +255,19 @@ Features added
|
||||
* C++: add ``cpp:struct`` to complement ``cpp:class``.
|
||||
* #1341 the HTML search considers words that contain a search term of length
|
||||
three or longer a match.
|
||||
* #4611: epub: Show warning for duplicated ToC entries
|
||||
* #1851: Allow to omit an argument for :rst:dir:`code-block` directive. If
|
||||
omitted, it follows :rst:dir:`highlight` or :confval:`highlight_language`
|
||||
* #4587: html: Add :confval:`html4_writer` to use old HTML4 writer
|
||||
* #6016: HTML search: A placeholder for the search summary prevents search
|
||||
result links from changing their position when the search terminates. This
|
||||
makes navigating search results easier.
|
||||
* #5196: linkcheck also checks remote images exist
|
||||
* #5924: githubpages: create CNAME file for custom domains when
|
||||
:confval:`html_baseurl` set
|
||||
* #4261: autosectionlabel: restrict the labeled sections by new config value;
|
||||
:confval:`autosectionlabel_maxdepth`
|
||||
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
@@ -196,7 +298,7 @@ Testing
|
||||
|
||||
* Stop to use ``SPHINX_TEST_TEMPDIR`` envvar
|
||||
|
||||
Release 1.8.4 (in development)
|
||||
Release 1.8.5 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
@@ -213,6 +315,26 @@ Features added
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* LaTeX: Remove extraneous space after author names on PDF title page (refs: #6004)
|
||||
* #6026: LaTeX: A cross reference to definition list does not work
|
||||
* #6046: LaTeX: ``TypeError`` is raised when invalid latex_elements given
|
||||
* #6067: LaTeX: images having a target are concatenated to next line
|
||||
* #6067: LaTeX: images having a target are not aligned even if specified
|
||||
* #6019: imgconverter: Including multipage PDF fails
|
||||
* #6047: autodoc: ``autofunction`` emits a warning for method objects
|
||||
* #6028: graphviz: Ensure the graphviz filenames are reproducible
|
||||
* #6068: doctest: ``skipif`` option may remove the code block from documentation
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 1.8.4 (released Feb 03, 2019)
|
||||
=====================================
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #3707: latex: no bold checkmark (✔) available.
|
||||
* #5605: with the documentation language set to Chinese, English words could not
|
||||
be searched.
|
||||
@@ -231,10 +353,10 @@ Bugs fixed
|
||||
* #5966: mathjax has not been loaded on incremental build
|
||||
* #5960: LaTeX: modified PDF layout since September 2018 TeXLive update of
|
||||
:file:`parskip.sty`
|
||||
* #5948: LaTeX: duplicated labels are generated for sections
|
||||
* #5958: versionadded directive causes crash with Python 3.5.0
|
||||
|
||||
Testing
|
||||
--------
|
||||
* #5995: autodoc: autodoc_mock_imports conflict with metaclass on Python 3.7
|
||||
* #5871: texinfo: a section title ``.`` is not allowed
|
||||
|
||||
Release 1.8.3 (released Dec 26, 2018)
|
||||
=====================================
|
||||
|
||||
13
EXAMPLES
13
EXAMPLES
@@ -18,6 +18,7 @@ Documentation using the alabaster theme
|
||||
* `Click <http://click.pocoo.org/>`__ (customized)
|
||||
* `coala <https://docs.coala.io/>`__ (customized)
|
||||
* `CodePy <https://documen.tician.de/codepy/>`__
|
||||
* `Eve <https://docs.python-eve.org/>`__ (Python REST API framework)
|
||||
* `Fabric <https://docs.fabfile.org/>`__
|
||||
* `Fityk <https://fityk.nieto.pl/>`__
|
||||
* `Flask <http://flask.pocoo.org/docs/>`__
|
||||
@@ -110,6 +111,7 @@ Documentation using the classic theme
|
||||
Documentation using the sphinxdoc theme
|
||||
---------------------------------------
|
||||
|
||||
* `ABRT <https://abrt.readthedocs.io/>`__
|
||||
* `cartopy <https://scitools.org.uk/cartopy/docs/latest/>`__
|
||||
* `Jython <http://www.jython.org/docs/>`__
|
||||
* `Matplotlib <https://matplotlib.org/>`__
|
||||
@@ -183,11 +185,14 @@ Documentation using sphinx_rtd_theme
|
||||
* `Elemental <http://libelemental.org/documentation/dev/>`__
|
||||
* `ESWP3 <https://eswp3.readthedocs.io/>`__
|
||||
* `Ethereum Homestead <http://www.ethdocs.org/>`__
|
||||
* `Faker <https://faker.readthedocs.io/>`__
|
||||
* `Fidimag <https://fidimag.readthedocs.io/>`__
|
||||
* `Flake8 <http://flake8.pycqa.org/>`__
|
||||
* `Flatpak <http://docs.flatpak.org/>`__
|
||||
* `FluidDyn <https://fluiddyn.readthedocs.io/>`__
|
||||
* `Fluidsim <https://fluidsim.readthedocs.io/>`__
|
||||
* `GeoNode <http://docs.geonode.org/>`__
|
||||
* `Glances <https://glances.readthedocs.io/>`__
|
||||
* `Godot <https://godot.readthedocs.io/>`__
|
||||
* `Graylog <http://docs.graylog.org/>`__
|
||||
* `GPAW <https://wiki.fysik.dtu.dk/gpaw/>`__ (customized)
|
||||
@@ -225,6 +230,7 @@ Documentation using sphinx_rtd_theme
|
||||
* `Phinx <http://docs.phinx.org/>`__
|
||||
* `phpMyAdmin <https://docs.phpmyadmin.net/>`__
|
||||
* `PROS <https://pros.cs.purdue.edu/v5/>`__ (customized)
|
||||
* `Pushkin <http://docs.pushkin.io/>`__
|
||||
* `Pweave <http://mpastell.com/pweave/>`__
|
||||
* `PyPy <http://doc.pypy.org/>`__
|
||||
* `python-sqlparse <https://sqlparse.readthedocs.io/>`__
|
||||
@@ -234,8 +240,10 @@ Documentation using sphinx_rtd_theme
|
||||
* `Releases Sphinx extension <https://releases.readthedocs.io/>`__
|
||||
* `Qtile <http://docs.qtile.org/>`__
|
||||
* `Quex <http://quex.sourceforge.net/doc/html/main.html>`__
|
||||
* `QuTiP <http://qutip.org/docs/latest/>`__
|
||||
* `Satchmo <http://docs.satchmoproject.com/>`__
|
||||
* `Scapy <https://scapy.readthedocs.io/>`__
|
||||
* `SimGrid <http://simgrid.gforge.inria.fr/simgrid/latest/doc/>`__
|
||||
* `SimPy <https://simpy.readthedocs.io/>`__
|
||||
* `six <https://six.readthedocs.io/>`__
|
||||
* `SlamData <https://newdocs.slamdata.com>`__
|
||||
@@ -249,12 +257,14 @@ Documentation using sphinx_rtd_theme
|
||||
* `Sublime Text Unofficial Documentation <http://docs.sublimetext.info/>`__
|
||||
* `SunPy <https://docs.sunpy.org/>`__
|
||||
* `Sylius <http://docs.sylius.org/>`__
|
||||
* `Syncthing <https://docs.syncthing.net/>`__
|
||||
* `Tango Controls <https://tango-controls.readthedocs.io/>`__ (customized)
|
||||
* `Topshelf <http://docs.topshelf-project.com/>`__
|
||||
* `Theano <http://www.deeplearning.net/software/theano/>`__
|
||||
* `ThreatConnect <https://docs.threatconnect.com/>`__
|
||||
* `Tuleap <https://tuleap.net/doc/en/>`__
|
||||
* `TYPO3 <https://docs.typo3.org/>`__ (customized)
|
||||
* `Veyon <https://docs.veyon.io/>`__
|
||||
* `uWSGI <https://uwsgi-docs.readthedocs.io/>`__
|
||||
* `virtualenv <https://virtualenv.readthedocs.io/>`__
|
||||
* `Wagtail <https://docs.wagtail.io/>`__
|
||||
@@ -262,6 +272,7 @@ Documentation using sphinx_rtd_theme
|
||||
* `Weblate <https://docs.weblate.org/>`__
|
||||
* `x265 <https://x265.readthedocs.io/>`__
|
||||
* `ZeroNet <https://zeronet.readthedocs.io/>`__
|
||||
* `Zulip <https://zulip.readthedocs.io/>`__
|
||||
|
||||
Documentation using sphinx_bootstrap_theme
|
||||
------------------------------------------
|
||||
@@ -301,6 +312,7 @@ Documentation using a custom theme or integrated in a website
|
||||
* `GHC - Glasgow Haskell Compiler <https://downloads.haskell.org/~ghc/master/users-guide/>`__
|
||||
* `Guzzle <http://docs.guzzlephp.org/>`__
|
||||
* `H2O.ai <http://docs.h2o.ai/>`__
|
||||
* `Heka <https://hekad.readthedocs.io/>`__
|
||||
* `Istihza (Turkish Python documentation project) <https://belgeler.yazbel.com/python-istihza/>`__
|
||||
* `Kombu <http://docs.kombu.me/>`__
|
||||
* `Lasso <http://lassoguide.com/>`__
|
||||
@@ -378,6 +390,7 @@ Books produced using Sphinx
|
||||
* `"Mithril -- The fastest clientside MVC (Japanese)" <https://www.oreilly.co.jp/books/9784873117447/>`__
|
||||
* `"Pioneers and Prominent Men of Utah" <http://pioneers.rstebbing.com/>`__
|
||||
* `"Pomodoro Technique Illustrated" (Japanese translation) <https://www.amazon.co.jp/dp/4048689525/>`__
|
||||
* `"Professional Software Development" <https://mixmastamyk.bitbucket.io/pro_soft_dev/>`__
|
||||
* `"Python Professional Programming" (in Japanese) <http://www.amazon.co.jp/dp/4798032948/>`__
|
||||
* `"Python Professional Programming 2nd Edition" (in Japanese) <https://www.amazon.co.jp/dp/479804315X/>`__
|
||||
* `"Python Professional Programming 3rd Edition" (in Japanese) <https://www.amazon.co.jp/dp/4798053821/>`__
|
||||
|
||||
15
bindep.txt
Normal file
15
bindep.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
texlive [platform:rpm]
|
||||
texlive-fncychap [platform:rpm]
|
||||
texlive-titlesec [platform:rpm]
|
||||
texlive-tabulary [platform:rpm]
|
||||
texlive-framed [platform:rpm]
|
||||
texlive-wrapfig [platform:rpm]
|
||||
texlive-upquote [platform:rpm]
|
||||
texlive-capt-of [platform:rpm]
|
||||
texlive-needspace [platform:rpm]
|
||||
texlive-polyglossia [platform:rpm]
|
||||
texlive-luatex85 [platform:rpm]
|
||||
texlive-anyfontsize [platform:rpm]
|
||||
texlive-ctablestack [platform:rpm]
|
||||
texlive-gnu-freefont [platform:rpm]
|
||||
latexmk [platform:rpm]
|
||||
23
doc/_themes/sphinx13/static/sphinx13.css
vendored
23
doc/_themes/sphinx13/static/sphinx13.css
vendored
@@ -337,7 +337,7 @@ a tt:hover {
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'Consolas', 'DejaVu Sans Mono',
|
||||
font-family: 'Consolas', 'Courier New', 'DejaVu Sans Mono',
|
||||
'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.015em;
|
||||
@@ -388,32 +388,29 @@ div.admonition, div.warning {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.admonition p, div.warning p {
|
||||
div.admonition > p, div.warning > p {
|
||||
margin: 0.5em 1em 0.5em 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.admonition pre, div.warning pre {
|
||||
div.admonition > pre, div.warning > pre {
|
||||
margin: 0.4em 1em 0.4em 1em;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title,
|
||||
div.warning p.admonition-title {
|
||||
margin-top: 1em;
|
||||
padding-top: 0.5em;
|
||||
div.admonition > p.admonition-title,
|
||||
div.warning > p.admonition-title {
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
border: 1px solid #940000;
|
||||
/* background-color: #FFCCCF;*/
|
||||
}
|
||||
|
||||
div.warning p.admonition-title {
|
||||
}
|
||||
|
||||
div.admonition ul, div.admonition ol,
|
||||
div.warning ul, div.warning ol {
|
||||
div.admonition > ul,
|
||||
div.admonition > ol,
|
||||
div.warning > ul,
|
||||
div.warning > ol {
|
||||
margin: 0.1em 0.5em 0.5em 3em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
11
doc/development/tutorials/examples/README.rst
Normal file
11
doc/development/tutorials/examples/README.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
:orphan:
|
||||
|
||||
Tutorial examples
|
||||
=================
|
||||
|
||||
This directory contains a number of examples used in the tutorials. These are
|
||||
intended to be increasingly complex to demonstrate the various features of
|
||||
Sphinx, but should aim to be as complicated as necessary but no more.
|
||||
Individual sections are referenced by line numbers, meaning if you make changes
|
||||
to the source files, you should update the references in the documentation
|
||||
accordingly.
|
||||
19
doc/development/tutorials/examples/helloworld.py
Normal file
19
doc/development/tutorials/examples/helloworld.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
|
||||
|
||||
class HelloWorld(Directive):
|
||||
|
||||
def run(self):
|
||||
paragraph_node = nodes.paragraph(text='Hello World!')
|
||||
return [paragraph_node]
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive("helloworld", HelloWorld)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
161
doc/development/tutorials/examples/recipe.py
Normal file
161
doc/development/tutorials/examples/recipe.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from docutils.parsers.rst import directives
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.domains import Domain
|
||||
from sphinx.domains import Index
|
||||
from sphinx.roles import XRefRole
|
||||
from sphinx.util.nodes import make_refnode
|
||||
|
||||
|
||||
class RecipeDirective(ObjectDescription):
|
||||
"""A custom directive that describes a recipe."""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
option_spec = {
|
||||
'contains': directives.unchanged_required,
|
||||
}
|
||||
|
||||
def handle_signature(self, sig, signode):
|
||||
signode += addnodes.desc_name(text=sig)
|
||||
return sig
|
||||
|
||||
def add_target_and_index(self, name_cls, sig, signode):
|
||||
signode['ids'].append('recipe' + '-' + sig)
|
||||
if 'noindex' not in self.options:
|
||||
ingredients = [
|
||||
x.strip() for x in self.options.get('contains').split(',')]
|
||||
|
||||
recipes = self.env.get_domain('recipe')
|
||||
recipes.add_recipe(sig, ingredients)
|
||||
|
||||
|
||||
class IngredientIndex(Index):
|
||||
"""A custom index that creates an ingredient matrix."""
|
||||
|
||||
name = 'ingredient'
|
||||
localname = 'Ingredient Index'
|
||||
shortname = 'Ingredient'
|
||||
|
||||
def generate(self, docnames=None):
|
||||
content = defaultdict(list)
|
||||
|
||||
recipes = {name: (dispname, typ, docname, anchor)
|
||||
for name, dispname, typ, docname, anchor, _
|
||||
in self.domain.get_objects()}
|
||||
recipe_ingredients = self.domain.data['recipe_ingredients']
|
||||
ingredient_recipes = defaultdict(list)
|
||||
|
||||
# flip from recipe_ingredients to ingredient_recipes
|
||||
for recipe_name, ingredients in recipe_ingredients.items():
|
||||
for ingredient in ingredients:
|
||||
ingredient_recipes[ingredient].append(recipe_name)
|
||||
|
||||
# convert the mapping of ingredient to recipes to produce the expected
|
||||
# output, shown below, using the ingredient name as a key to group
|
||||
#
|
||||
# name, subtype, docname, anchor, extra, qualifier, description
|
||||
for ingredient, recipe_names in ingredient_recipes.items():
|
||||
for recipe_name in recipe_names:
|
||||
dispname, typ, docname, anchor = recipes[recipe_name]
|
||||
content[ingredient].append(
|
||||
(dispname, 0, docname, anchor, docname, '', typ))
|
||||
|
||||
# convert the dict to the sorted list of tuples expected
|
||||
content = sorted(content.items())
|
||||
|
||||
return content, True
|
||||
|
||||
|
||||
class RecipeIndex(Index):
|
||||
"""A custom index that creates an recipe matrix."""
|
||||
|
||||
name = 'recipe'
|
||||
localname = 'Recipe Index'
|
||||
shortname = 'Recipe'
|
||||
|
||||
def generate(self, docnames=None):
|
||||
content = defaultdict(list)
|
||||
|
||||
# sort the list of recipes in alphabetical order
|
||||
recipes = self.domain.get_objects()
|
||||
recipes = sorted(recipes, key=lambda recipe: recipe[0])
|
||||
|
||||
# generate the expected output, shown below, from the above using the
|
||||
# first letter of the recipe as a key to group thing
|
||||
#
|
||||
# name, subtype, docname, anchor, extra, qualifier, description
|
||||
for name, dispname, typ, docname, anchor, _ in recipes:
|
||||
content[dispname[0].lower()].append(
|
||||
(dispname, 0, docname, anchor, docname, '', typ))
|
||||
|
||||
# convert the dict to the sorted list of tuples expected
|
||||
content = sorted(content.items())
|
||||
|
||||
return content, True
|
||||
|
||||
|
||||
class RecipeDomain(Domain):
|
||||
|
||||
name = 'recipe'
|
||||
label = 'Recipe Sample'
|
||||
roles = {
|
||||
'ref': XRefRole()
|
||||
}
|
||||
directives = {
|
||||
'recipe': RecipeDirective,
|
||||
}
|
||||
indices = {
|
||||
RecipeIndex,
|
||||
IngredientIndex
|
||||
}
|
||||
initial_data = {
|
||||
'recipes': [], # object list
|
||||
'recipe_ingredients': {}, # name -> object
|
||||
}
|
||||
|
||||
def get_full_qualified_name(self, node):
|
||||
return '{}.{}'.format('recipe', node.arguments[0])
|
||||
|
||||
def get_objects(self):
|
||||
for obj in self.data['recipes']:
|
||||
yield(obj)
|
||||
|
||||
def resolve_xref(self, env, fromdocname, builder, typ, target, node,
|
||||
contnode):
|
||||
match = [(docname, anchor)
|
||||
for name, sig, typ, docname, anchor, prio
|
||||
in self.get_objects() if sig == target]
|
||||
|
||||
if len(match) > 0:
|
||||
todocname = match[0][0]
|
||||
targ = match[0][1]
|
||||
|
||||
return make_refnode(builder, fromdocname, todocname, targ,
|
||||
contnode, targ)
|
||||
else:
|
||||
print('Awww, found nothing')
|
||||
return None
|
||||
|
||||
def add_recipe(self, signature, ingredients):
|
||||
"""Add a new recipe to the domain."""
|
||||
name = '{}.{}'.format('recipe', signature)
|
||||
anchor = 'recipe-{}'.format(signature)
|
||||
|
||||
self.data['recipe_ingredients'][name] = ingredients
|
||||
# name, dispname, type, docname, anchor, priority
|
||||
self.data['recipes'].append(
|
||||
(name, signature, 'Recipe', self.env.docname, anchor, 0))
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_domain(RecipeDomain)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
124
doc/development/tutorials/examples/todo.py
Normal file
124
doc/development/tutorials/examples/todo.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
|
||||
from sphinx.locale import _
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
|
||||
|
||||
class todo(nodes.Admonition, nodes.Element):
|
||||
pass
|
||||
|
||||
|
||||
class todolist(nodes.General, nodes.Element):
|
||||
pass
|
||||
|
||||
|
||||
def visit_todo_node(self, node):
|
||||
self.visit_admonition(node)
|
||||
|
||||
|
||||
def depart_todo_node(self, node):
|
||||
self.depart_admonition(node)
|
||||
|
||||
|
||||
class TodolistDirective(Directive):
|
||||
|
||||
def run(self):
|
||||
return [todolist('')]
|
||||
|
||||
|
||||
class TodoDirective(SphinxDirective):
|
||||
|
||||
# this enables content in the directive
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
targetid = 'todo-%d' % self.env.new_serialno('todo')
|
||||
targetnode = nodes.target('', '', ids=[targetid])
|
||||
|
||||
todo_node = todo('\n'.join(self.content))
|
||||
todo_node += nodes.title(_('Todo'), _('Todo'))
|
||||
self.state.nested_parse(self.content, self.content_offset, todo_node)
|
||||
|
||||
if not hasattr(self.env, 'todo_all_todos'):
|
||||
self.env.todo_all_todos = []
|
||||
|
||||
self.env.todo_all_todos.append({
|
||||
'docname': self.env.docname,
|
||||
'lineno': self.lineno,
|
||||
'todo': todo_node.deepcopy(),
|
||||
'target': targetnode,
|
||||
})
|
||||
|
||||
return [targetnode, todo_node]
|
||||
|
||||
|
||||
def purge_todos(app, env, docname):
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
return
|
||||
|
||||
env.todo_all_todos = [todo for todo in env.todo_all_todos
|
||||
if todo['docname'] != docname]
|
||||
|
||||
|
||||
def process_todo_nodes(app, doctree, fromdocname):
|
||||
if not app.config.todo_include_todos:
|
||||
for node in doctree.traverse(todo):
|
||||
node.parent.remove(node)
|
||||
|
||||
# Replace all todolist nodes with a list of the collected todos.
|
||||
# Augment each todo with a backlink to the original location.
|
||||
env = app.builder.env
|
||||
|
||||
for node in doctree.traverse(todolist):
|
||||
if not app.config.todo_include_todos:
|
||||
node.replace_self([])
|
||||
continue
|
||||
|
||||
content = []
|
||||
|
||||
for todo_info in env.todo_all_todos:
|
||||
para = nodes.paragraph()
|
||||
filename = env.doc2path(todo_info['docname'], base=None)
|
||||
description = (
|
||||
_('(The original entry is located in %s, line %d and can be found ') %
|
||||
(filename, todo_info['lineno']))
|
||||
para += nodes.Text(description, description)
|
||||
|
||||
# Create a reference
|
||||
newnode = nodes.reference('', '')
|
||||
innernode = nodes.emphasis(_('here'), _('here'))
|
||||
newnode['refdocname'] = todo_info['docname']
|
||||
newnode['refuri'] = app.builder.get_relative_uri(
|
||||
fromdocname, todo_info['docname'])
|
||||
newnode['refuri'] += '#' + todo_info['target']['refid']
|
||||
newnode.append(innernode)
|
||||
para += newnode
|
||||
para += nodes.Text('.)', '.)')
|
||||
|
||||
# Insert into the todolist
|
||||
content.append(todo_info['todo'])
|
||||
content.append(para)
|
||||
|
||||
node.replace_self(content)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value('todo_include_todos', False, 'html')
|
||||
|
||||
app.add_node(todolist)
|
||||
app.add_node(todo,
|
||||
html=(visit_todo_node, depart_todo_node),
|
||||
latex=(visit_todo_node, depart_todo_node),
|
||||
text=(visit_todo_node, depart_todo_node))
|
||||
|
||||
app.add_directive('todo', TodoDirective)
|
||||
app.add_directive('todolist', TodolistDirective)
|
||||
app.connect('doctree-resolved', process_todo_nodes)
|
||||
app.connect('env-purge-doc', purge_todos)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
@@ -1,85 +1,89 @@
|
||||
Developing a "Hello world" directive
|
||||
Developing a "Hello world" extension
|
||||
====================================
|
||||
|
||||
The objective of this tutorial is to create a very basic extension that adds a new
|
||||
directive that outputs a paragraph containing `hello world`.
|
||||
The objective of this tutorial is to create a very basic extension that adds a
|
||||
new directive. This directive will output a paragraph containing "hello world".
|
||||
|
||||
Only basic information is provided in this tutorial. For more information,
|
||||
refer to the :doc:`other tutorials <index>` that go into more
|
||||
details.
|
||||
Only basic information is provided in this tutorial. For more information, refer
|
||||
to the :doc:`other tutorials <index>` that go into more details.
|
||||
|
||||
.. warning:: For this extension, you will need some basic understanding of docutils_
|
||||
.. warning::
|
||||
|
||||
For this extension, you will need some basic understanding of docutils_
|
||||
and Python.
|
||||
|
||||
Creating a new extension file
|
||||
-----------------------------
|
||||
|
||||
Your extension file could be in any folder of your project. In our case,
|
||||
let's do the following:
|
||||
Overview
|
||||
--------
|
||||
|
||||
#. Create an :file:`_ext` folder in :file:`source`.
|
||||
We want the extension to add the following to Sphinx:
|
||||
|
||||
* A ``helloworld`` directive, that will simply output the text "hello world".
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
We will not be distributing this plugin via `PyPI`_ and will instead include it
|
||||
as part of an existing project. This means you will need to use an existing
|
||||
project or create a new one using :program:`sphinx-quickstart`.
|
||||
|
||||
We assume you are using separate source (:file:`source`) and build
|
||||
(:file:`build`) folders. Your extension file could be in any folder of your
|
||||
project. In our case, let's do the following:
|
||||
|
||||
#. Create an :file:`_ext` folder in :file:`source`
|
||||
#. Create a new Python file in the :file:`_ext` folder called
|
||||
:file:`helloworld.py`.
|
||||
:file:`helloworld.py`
|
||||
|
||||
Here is an example of the folder structure you might obtain:
|
||||
Here is an example of the folder structure you might obtain:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: text
|
||||
|
||||
└── source
|
||||
├── _ext
|
||||
│ └── helloworld.py
|
||||
├── _static
|
||||
├── conf.py
|
||||
├── somefolder
|
||||
├── index.rst
|
||||
├── somefile.rst
|
||||
└── someotherfile.rst
|
||||
|
||||
└── source
|
||||
├── _ext
|
||||
│ └── helloworld.py
|
||||
├── _static
|
||||
├── _themes
|
||||
├── conf.py
|
||||
├── somefolder
|
||||
├── somefile.rst
|
||||
└── someotherfile.rst
|
||||
|
||||
Writing the extension
|
||||
---------------------
|
||||
|
||||
Open :file:`helloworld.py` and paste the following code in it:
|
||||
|
||||
.. code-block:: python
|
||||
.. literalinclude:: examples/helloworld.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive
|
||||
Some essential things are happening in this example, and you will see them for
|
||||
all directives.
|
||||
|
||||
.. rubric:: The directive class
|
||||
|
||||
class HelloWorld(Directive):
|
||||
def run(self):
|
||||
paragraph_node = nodes.paragraph(text='Hello World!')
|
||||
return [paragraph_node]
|
||||
Our new directive is declared in the ``HelloWorld`` class.
|
||||
|
||||
.. literalinclude:: examples/helloworld.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 5-9
|
||||
|
||||
def setup(app):
|
||||
app.add_directive("helloworld", HelloWorld)
|
||||
|
||||
|
||||
Some essential things are happening in this example, and you will see them
|
||||
in all directives:
|
||||
|
||||
.. rubric:: Directive declaration
|
||||
|
||||
Our new directive is declared in the ``HelloWorld`` class, it extends
|
||||
docutils_' ``Directive`` class. All extensions that create directives
|
||||
should extend this class.
|
||||
|
||||
.. rubric:: ``run`` method
|
||||
|
||||
This method is a requirement and it is part of every directive. It contains
|
||||
the main logic of the directive and it returns a list of docutils nodes to
|
||||
be processed by Sphinx.
|
||||
This class extends the docutils_' ``Directive`` class. All extensions that
|
||||
create directives should extend this class.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`todo`
|
||||
`The docutils documentation on creating directives <docutils directives>`_
|
||||
|
||||
.. rubric:: docutils nodes
|
||||
|
||||
The ``run`` method returns a list of nodes. Nodes are docutils' way of
|
||||
representing the content of a document. There are many types of nodes
|
||||
available: text, paragraph, reference, table, etc.
|
||||
This class contains a ``run`` method. This method is a requirement and it is
|
||||
part of every directive. It contains the main logic of the directive and it
|
||||
returns a list of docutils nodes to be processed by Sphinx. These nodes are
|
||||
docutils' way of representing the content of a document. There are many types of
|
||||
nodes available: text, paragraph, reference, table, etc.
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -89,74 +93,97 @@ The ``nodes.paragraph`` class creates a new paragraph node. A paragraph
|
||||
node typically contains some text that we can set during instantiation using
|
||||
the ``text`` parameter.
|
||||
|
||||
.. rubric:: ``setup`` function
|
||||
.. rubric:: The ``setup`` function
|
||||
|
||||
.. currentmodule:: sphinx.application
|
||||
|
||||
This function is a requirement. We use it to plug our new directive into
|
||||
Sphinx.
|
||||
The simplest thing you can do it call the ``app.add_directive`` method.
|
||||
|
||||
.. note::
|
||||
.. literalinclude:: examples/helloworld.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 12-
|
||||
|
||||
The first argument is the name of the directive itself as used in an rST file.
|
||||
The simplest thing you can do it call the :meth:`~Sphinx.add_directive` method,
|
||||
which is what we've done here. For this particular call, the first argument is
|
||||
the name of the directive itself as used in a reST file. In this case, we would
|
||||
use ``helloworld``. For example:
|
||||
|
||||
In our case, we would use ``helloworld``:
|
||||
.. code-block:: rst
|
||||
|
||||
.. code-block:: rst
|
||||
Some intro text here...
|
||||
|
||||
Some intro text here...
|
||||
.. helloworld::
|
||||
|
||||
.. helloworld::
|
||||
Some more text here...
|
||||
|
||||
Some more text here...
|
||||
We also return the :ref:`extension metadata <ext-metadata>` that indicates the
|
||||
version of our extension, along with the fact that it is safe to use the
|
||||
extension for both parallel reading and writing.
|
||||
|
||||
|
||||
Updating the conf.py file
|
||||
-------------------------
|
||||
Using the extension
|
||||
-------------------
|
||||
|
||||
The extension file has to be declared in your :file:`conf.py` file to make
|
||||
Sphinx aware of it:
|
||||
The extension has to be declared in your :file:`conf.py` file to make Sphinx
|
||||
aware of it. There are two steps necessary here:
|
||||
|
||||
#. Open :file:`conf.py`. It is in the :file:`source` folder by default.
|
||||
#. Add ``sys.path.append(os.path.abspath("./_ext"))`` before
|
||||
the ``extensions`` variable declaration (if it exists).
|
||||
#. Update or create the ``extensions`` list and add the
|
||||
extension file name to the list:
|
||||
#. Add the :file:`_ext` directory to the `Python path`_ using
|
||||
``sys.path.append``. This should be placed at the top of the file.
|
||||
|
||||
.. code-block:: python
|
||||
#. Update or create the :confval:`extensions` list and add the extension file
|
||||
name to the list
|
||||
|
||||
extensions.append('helloworld')
|
||||
For example:
|
||||
|
||||
You can now use the extension.
|
||||
.. code-block:: python
|
||||
|
||||
.. admonition:: Example
|
||||
import os
|
||||
import sys
|
||||
|
||||
.. code-block:: rst
|
||||
sys.path.append(os.path.abspath("./_ext"))
|
||||
|
||||
Some intro text here...
|
||||
extensions = ['helloworld']
|
||||
|
||||
.. helloworld::
|
||||
.. tip::
|
||||
|
||||
Some more text here...
|
||||
We're not distributing this extension as a `Python package`_, we need to
|
||||
modify the `Python path`_ so Sphinx can find our extension. This is why we
|
||||
need the call to ``sys.path.append``.
|
||||
|
||||
The sample above would generate:
|
||||
You can now use the extension in a file. For example:
|
||||
|
||||
.. code-block:: text
|
||||
.. code-block:: rst
|
||||
|
||||
Some intro text here...
|
||||
Some intro text here...
|
||||
|
||||
Hello World!
|
||||
.. helloworld::
|
||||
|
||||
Some more text here...
|
||||
Some more text here...
|
||||
|
||||
The sample above would generate:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Some intro text here...
|
||||
|
||||
Hello World!
|
||||
|
||||
Some more text here...
|
||||
|
||||
|
||||
Further reading
|
||||
---------------
|
||||
|
||||
This is the very basic principle of an extension that creates a new directive.
|
||||
|
||||
For a more advanced example, refer to :doc:`todo`.
|
||||
|
||||
Further reading
|
||||
---------------
|
||||
|
||||
You can create your own nodes if needed, refer to the
|
||||
:doc:`todo` for more information.
|
||||
|
||||
.. _docutils: http://docutils.sourceforge.net/
|
||||
.. _`docutils nodes`: http://docutils.sourceforge.net/docs/ref/doctree.html
|
||||
.. _docutils directives: http://docutils.sourceforge.net/docs/howto/rst-directives.html
|
||||
.. _docutils nodes: http://docutils.sourceforge.net/docs/ref/doctree.html
|
||||
.. _PyPI: https://pypi.org/
|
||||
.. _Python package: https://packaging.python.org/
|
||||
.. _Python path: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
|
||||
|
||||
@@ -9,3 +9,4 @@ Refer to the following tutorials to get started with extension development.
|
||||
|
||||
helloworld
|
||||
todo
|
||||
recipe
|
||||
|
||||
217
doc/development/tutorials/recipe.rst
Normal file
217
doc/development/tutorials/recipe.rst
Normal file
@@ -0,0 +1,217 @@
|
||||
Developing a "recipe" extension
|
||||
===============================
|
||||
|
||||
The objective of this tutorial is to illustrate roles, directives and domains.
|
||||
Once complete, we will be able to use this extension to describe a recipe and
|
||||
reference that recipe from elsewhere in our documentation.
|
||||
|
||||
.. note::
|
||||
|
||||
This tutorial is based on a guide first published on `opensource.com`_ and
|
||||
is provided here with the original author's permission.
|
||||
|
||||
.. _opensource.com: https://opensource.com/article/18/11/building-custom-workflows-sphinx
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
We want the extension to add the following to Sphinx:
|
||||
|
||||
* A ``recipe`` :term:`directive`, containing some content describing the recipe
|
||||
steps, along with a ``:contains:`` option highlighting the main ingredients
|
||||
of the recipe.
|
||||
|
||||
* A ``ref`` :term:`role`, which provides a cross-reference to the recipe
|
||||
itself.
|
||||
|
||||
* A ``recipe`` :term:`domain`, which allows us to tie together the above role
|
||||
and domain, along with things like indices.
|
||||
|
||||
For that, we will need to add the following elements to Sphinx:
|
||||
|
||||
* A new directive called ``recipe``
|
||||
|
||||
* New indexes to allow us to reference ingredient and recipes
|
||||
|
||||
* A new domain called ``recipe``, which will contain the ``recipe`` directive
|
||||
and ``ref`` role
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
We need the same setup as in :doc:`the previous extensions <todo>`. This time,
|
||||
we will be putting out extension in a file called :file:`recipe.py`.
|
||||
|
||||
Here is an example of the folder structure you might obtain:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
└── source
|
||||
├── _ext
|
||||
│ └── recipe.py
|
||||
├── conf.py
|
||||
└── index.rst
|
||||
|
||||
|
||||
Writing the extension
|
||||
---------------------
|
||||
|
||||
Open :file:`recipe.py` and paste the following code in it, all of which we will
|
||||
explain in detail shortly:
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
Let's look at each piece of this extension step-by-step to explain what's going
|
||||
on.
|
||||
|
||||
.. rubric:: The directive class
|
||||
|
||||
The first thing to examine is the ``RecipeDirective`` directive:
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 17-37
|
||||
|
||||
Unlike :doc:`helloworld` and :doc:`todo`, this directive doesn't derive from
|
||||
:class:`docutils.parsers.rst.Directive` and doesn't define a ``run`` method.
|
||||
Instead, it derives from :class:`sphinx.directives.ObjectDescription` and
|
||||
defines ``handle_signature`` and ``add_target_and_index`` methods. This is
|
||||
because ``ObjectDescription`` is a special-purpose directive that's intended
|
||||
for describing things like classes, functions, or, in our case, recipes. More
|
||||
specifically, ``handle_signature`` implements parsing the signature of the
|
||||
directive and passes on the object's name and type to its superclass, while
|
||||
``add_taget_and_index`` adds a target (to link to) and an entry to the index
|
||||
for this node.
|
||||
|
||||
We also see that this directive defines ``has_content``, ``required_arguments``
|
||||
and ``option_spec``. Unlike the ``TodoDirective`` directive added in the
|
||||
:doc:`previous tutorial <todo>`, this directive takes a single argument, the
|
||||
recipe name, and an option, ``contains``, in addition to the nested
|
||||
reStructuredText in the body.
|
||||
|
||||
.. rubric:: The index classes
|
||||
|
||||
.. currentmodule:: sphinx.domains
|
||||
|
||||
.. todo:: Add brief overview of indices
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 40-102
|
||||
|
||||
Both ``IngredientIndex`` and ``RecipeIndex`` are derived from :class:`Index`.
|
||||
They implement custom logic to generate a tuple of values that define the
|
||||
index. Note that ``RecipeIndex`` is a simple index that has only one entry.
|
||||
Extending it to cover more object types is not yet part of the code.
|
||||
|
||||
Both indices use the method :meth:`Index.generate` to do their work. This
|
||||
method combines the information from our domain, sorts it, and returns it in a
|
||||
list structure that will be accepted by Sphinx. This might look complicated but
|
||||
all it really is is a list of tuples like ``('tomato', 'TomatoSoup', 'test',
|
||||
'rec-TomatoSoup',...)``. Refer to the :doc:`domain API guide
|
||||
</extdev/domainapi>` for more information on this API.
|
||||
|
||||
.. rubric:: The domain
|
||||
|
||||
A Sphinx domain is a specialized container that ties together roles,
|
||||
directives, and indices, among other things. Let's look at the domain we're
|
||||
creating here.
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 105-155
|
||||
|
||||
There are some interesting things to note about this ``recipe`` domain and domains
|
||||
in general. Firstly, we actually register our directives, roles and indices
|
||||
here, via the ``directives``, ``roles`` and ``indices`` attributes, rather than
|
||||
via calls later on in ``setup``. We can also note that we aren't actually
|
||||
defining a custom role and are instead reusing the
|
||||
:class:`sphinx.roles.XRefRole` role and defining the
|
||||
:class:`sphinx.domains.Domain.resolve_xref` method. This method takes two
|
||||
arguments, ``typ`` and ``target``, which refer to the cross-reference type and
|
||||
its target name. We'll use ``target`` to resolve our destination from our
|
||||
domain's ``recipes`` because we currently have only one type of node.
|
||||
|
||||
Moving on, we can see that we've defined ``initial_data``. The values defined in
|
||||
``initial_data`` will be copied to ``env.domaindata[domain_name]`` as the
|
||||
initial data of the domain, and domain instances can access it via
|
||||
``self.data``. We see that we have defined two items in ``initial_data``:
|
||||
``recipes`` and ``recipe2ingredient``. These contain a list of all objects
|
||||
defined (i.e. all recipes) and a hash that maps a canonical ingredient name to
|
||||
the list of objects. The way we name objects is common across our extension and
|
||||
is defined in the ``get_full_qualified_name`` method. For each object created,
|
||||
the canonical name is ``recipe.<recipename>``, where ``<recipename>`` is the
|
||||
name the documentation writer gives the object (a recipe). This enables the
|
||||
extension to use different object types that share the same name. Having a
|
||||
canonical name and central place for our objects is a huge advantage. Both our
|
||||
indices and our cross-referencing code use this feature.
|
||||
|
||||
.. rubric:: The ``setup`` function
|
||||
|
||||
.. currentmodule:: sphinx.application
|
||||
|
||||
:doc:`As always <todo>`, the ``setup`` function is a requirement and is used to
|
||||
hook the various parts of our extension into Sphinx. Let's look at the
|
||||
``setup`` function for this extension.
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 158-
|
||||
|
||||
This looks a little different to what we're used to seeing. There are no calls
|
||||
to :meth:`~Sphinx.add_directive` or even :meth:`~Sphinx.add_role`. Instead, we
|
||||
have a single call to :meth:`~Sphinx.add_domain` followed by some
|
||||
initialization of the :ref:`standard domain <domains-std>`. This is because we
|
||||
had already registered our directives, roles and indexes as part of the
|
||||
directive itself.
|
||||
|
||||
|
||||
Using the extension
|
||||
-------------------
|
||||
|
||||
You can now use the extension throughout your project. For example:
|
||||
|
||||
.. code-block:: rst
|
||||
:caption: index.rst
|
||||
|
||||
Joe's Recipes
|
||||
=============
|
||||
|
||||
Below are a collection of my favourite recipes. I highly recommend the
|
||||
:recipe:ref:`TomatoSoup` recipe in particular!
|
||||
|
||||
.. toctree::
|
||||
|
||||
tomato-soup
|
||||
|
||||
.. code-block:: rst
|
||||
:caption: tomato-soup.rst
|
||||
|
||||
The recipe contains `tomato` and `cilantro`.
|
||||
|
||||
.. recipe:recipe:: TomatoSoup
|
||||
:contains: tomato cilantro salt pepper
|
||||
|
||||
This recipe is a tasty tomato soup, combine all ingredients
|
||||
and cook.
|
||||
|
||||
The important things to note are the use of the ``:recipe:ref:`` role to
|
||||
cross-reference the recipe actually defined elsewhere (using the
|
||||
``:recipe:recipe:`` directive.
|
||||
|
||||
|
||||
Further reading
|
||||
---------------
|
||||
|
||||
For more information, refer to the `docutils`_ documentation and
|
||||
:doc:`/extdev/index`.
|
||||
|
||||
.. _docutils: http://docutils.sourceforge.net/docs/
|
||||
@@ -1,114 +1,99 @@
|
||||
Developing a "TODO" extension
|
||||
=============================
|
||||
|
||||
This section is intended as a walkthrough for the creation of custom extensions.
|
||||
It covers the basics of writing and activating an extension, as well as
|
||||
commonly used features of extensions.
|
||||
|
||||
As an example, we will cover a "todo" extension that adds capabilities to
|
||||
include todo entries in the documentation, and to collect these in a central
|
||||
place. (A similar "todo" extension is distributed with Sphinx.)
|
||||
The objective of this tutorial is to create a more comprehensive extension than
|
||||
that created in :doc:`helloworld`. Whereas that guide just covered writing a
|
||||
custom :term:`directive`, this guide adds multiple directives, along with custom
|
||||
nodes, additional config values and custom event handlers. To this end, we will
|
||||
cover a ``todo`` extension that adds capabilities to include todo entries in the
|
||||
documentation, and to collect these in a central place. This is similar the
|
||||
``sphinxext.todo`` extension distributed with Sphinx.
|
||||
|
||||
|
||||
Extension Design
|
||||
----------------
|
||||
Overview
|
||||
--------
|
||||
|
||||
.. note:: To understand the design this extension, refer to
|
||||
.. note::
|
||||
To understand the design of this extension, refer to
|
||||
:ref:`important-objects` and :ref:`build-phases`.
|
||||
|
||||
We want the extension to add the following to Sphinx:
|
||||
|
||||
* A "todo" directive, containing some content that is marked with "TODO", and
|
||||
only shown in the output if a new config value is set. (Todo entries should
|
||||
not be in the output by default.)
|
||||
* A ``todo`` directive, containing some content that is marked with "TODO" and
|
||||
only shown in the output if a new config value is set. Todo entries should not
|
||||
be in the output by default.
|
||||
|
||||
* A "todolist" directive that creates a list of all todo entries throughout the
|
||||
* A ``todolist`` directive that creates a list of all todo entries throughout the
|
||||
documentation.
|
||||
|
||||
For that, we will need to add the following elements to Sphinx:
|
||||
|
||||
* New directives, called ``todo`` and ``todolist``.
|
||||
|
||||
* New document tree nodes to represent these directives, conventionally also
|
||||
called ``todo`` and ``todolist``. We wouldn't need new nodes if the new
|
||||
directives only produced some content representable by existing nodes.
|
||||
|
||||
* A new config value ``todo_include_todos`` (config value names should start
|
||||
with the extension name, in order to stay unique) that controls whether todo
|
||||
entries make it into the output.
|
||||
|
||||
* New event handlers: one for the :event:`doctree-resolved` event, to replace
|
||||
the todo and todolist nodes, and one for :event:`env-purge-doc` (the reason
|
||||
for that will be covered later).
|
||||
|
||||
|
||||
The Setup Function
|
||||
------------------
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
.. currentmodule:: sphinx.application
|
||||
As with :doc:`helloworld`, we will not be distributing this plugin via PyPI so
|
||||
once again we need a Sphinx project to call this from. You can use an existing
|
||||
project or create a new one using :program:`sphinx-quickstart`.
|
||||
|
||||
The new elements are added in the extension's setup function. Let us create a
|
||||
new Python module called :file:`todo.py` and add the setup function::
|
||||
We assume you are using separate source (:file:`source`) and build
|
||||
(:file:`build`) folders. Your extension file could be in any folder of your
|
||||
project. In our case, let's do the following:
|
||||
|
||||
def setup(app):
|
||||
app.add_config_value('todo_include_todos', False, 'html')
|
||||
#. Create an :file:`_ext` folder in :file:`source`
|
||||
#. Create a new Python file in the :file:`_ext` folder called :file:`todo.py`
|
||||
|
||||
app.add_node(todolist)
|
||||
app.add_node(todo,
|
||||
html=(visit_todo_node, depart_todo_node),
|
||||
latex=(visit_todo_node, depart_todo_node),
|
||||
text=(visit_todo_node, depart_todo_node))
|
||||
Here is an example of the folder structure you might obtain:
|
||||
|
||||
app.add_directive('todo', TodoDirective)
|
||||
app.add_directive('todolist', TodolistDirective)
|
||||
app.connect('doctree-resolved', process_todo_nodes)
|
||||
app.connect('env-purge-doc', purge_todos)
|
||||
.. code-block:: text
|
||||
|
||||
return {'version': '0.1'} # identifies the version of our extension
|
||||
|
||||
The calls in this function refer to classes and functions not yet written. What
|
||||
the individual calls do is the following:
|
||||
|
||||
* :meth:`~Sphinx.add_config_value` lets Sphinx know that it should recognize the
|
||||
new *config value* ``todo_include_todos``, whose default value should be
|
||||
``False`` (this also tells Sphinx that it is a boolean value).
|
||||
|
||||
If the third argument was ``'html'``, HTML documents would be full rebuild if the
|
||||
config value changed its value. This is needed for config values that
|
||||
influence reading (build :ref:`phase 1 <build-phases>`).
|
||||
|
||||
* :meth:`~Sphinx.add_node` adds a new *node class* to the build system. It also
|
||||
can specify visitor functions for each supported output format. These visitor
|
||||
functions are needed when the new nodes stay until :ref:`phase 4 <build-phases>`
|
||||
-- since the ``todolist`` node is always replaced in :ref:`phase 3 <build-phases>`,
|
||||
it doesn't need any.
|
||||
|
||||
We need to create the two node classes ``todo`` and ``todolist`` later.
|
||||
|
||||
* :meth:`~Sphinx.add_directive` adds a new *directive*, given by name and class.
|
||||
|
||||
The handler functions are created later.
|
||||
|
||||
* Finally, :meth:`~Sphinx.connect` adds an *event handler* to the event whose
|
||||
name is given by the first argument. The event handler function is called
|
||||
with several arguments which are documented with the event.
|
||||
└── source
|
||||
├── _ext
|
||||
│ └── todo.py
|
||||
├── _static
|
||||
├── conf.py
|
||||
├── somefolder
|
||||
├── index.rst
|
||||
├── somefile.rst
|
||||
└── someotherfile.rst
|
||||
|
||||
|
||||
The Node Classes
|
||||
----------------
|
||||
Writing the extension
|
||||
---------------------
|
||||
|
||||
Let's start with the node classes::
|
||||
Open :file:`todo.py` and paste the following code in it, all of which we will
|
||||
explain in detail shortly:
|
||||
|
||||
from docutils import nodes
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
class todo(nodes.Admonition, nodes.Element):
|
||||
pass
|
||||
This is far more extensive extension than the one detailed in :doc:`helloworld`,
|
||||
however, we will will look at each piece step-by-step to explain what's
|
||||
happening.
|
||||
|
||||
class todolist(nodes.General, nodes.Element):
|
||||
pass
|
||||
.. rubric:: The node classes
|
||||
|
||||
def visit_todo_node(self, node):
|
||||
self.visit_admonition(node)
|
||||
Let's start with the node classes:
|
||||
|
||||
def depart_todo_node(self, node):
|
||||
self.depart_admonition(node)
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 8-21
|
||||
|
||||
Node classes usually don't have to do anything except inherit from the standard
|
||||
docutils classes defined in :mod:`docutils.nodes`. ``todo`` inherits from
|
||||
@@ -122,81 +107,54 @@ is just a "general" node.
|
||||
<http://docutils.sourceforge.net/docs/ref/doctree.html>`__ and :ref:`Sphinx
|
||||
<nodes>`.
|
||||
|
||||
|
||||
The Directive Classes
|
||||
---------------------
|
||||
.. rubric:: The directive classes
|
||||
|
||||
A directive class is a class deriving usually from
|
||||
:class:`docutils.parsers.rst.Directive`. The directive interface is also
|
||||
:class:`docutils.parsers.rst.Directive`. The directive interface is also
|
||||
covered in detail in the `docutils documentation`_; the important thing is that
|
||||
the class should have attributes that configure the allowed markup,
|
||||
and a ``run`` method that returns a list of nodes.
|
||||
the class should have attributes that configure the allowed markup, and a
|
||||
``run`` method that returns a list of nodes.
|
||||
|
||||
The ``todolist`` directive is quite simple::
|
||||
Looking first at the ``TodolistDirective`` directive:
|
||||
|
||||
from docutils.parsers.rst import Directive
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 24-27
|
||||
|
||||
class TodolistDirective(Directive):
|
||||
It's very simple, creating and returning an instance of our ``todolist`` node
|
||||
class. The ``TodolistDirective`` directive itself has neither content nor
|
||||
arguments that need to be handled. That brings us to the ``TodoDirective``
|
||||
directive:
|
||||
|
||||
def run(self):
|
||||
return [todolist('')]
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 30-53
|
||||
|
||||
An instance of our ``todolist`` node class is created and returned. The
|
||||
todolist directive has neither content nor arguments that need to be handled.
|
||||
|
||||
The ``todo`` directive function looks like this::
|
||||
|
||||
from sphinx.locale import _
|
||||
|
||||
class TodoDirective(Directive):
|
||||
|
||||
# this enables content in the directive
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
|
||||
targetid = "todo-%d" % env.new_serialno('todo')
|
||||
targetnode = nodes.target('', '', ids=[targetid])
|
||||
|
||||
todo_node = todo('\n'.join(self.content))
|
||||
todo_node += nodes.title(_('Todo'), _('Todo'))
|
||||
self.state.nested_parse(self.content, self.content_offset, todo_node)
|
||||
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
env.todo_all_todos = []
|
||||
env.todo_all_todos.append({
|
||||
'docname': env.docname,
|
||||
'lineno': self.lineno,
|
||||
'todo': todo_node.deepcopy(),
|
||||
'target': targetnode,
|
||||
})
|
||||
|
||||
return [targetnode, todo_node]
|
||||
|
||||
Several important things are covered here. First, as you can see, you can refer
|
||||
to the :ref:`build environment instance <important-objects>` using ``self.state.document.settings.env``.
|
||||
|
||||
Then, to act as a link target (from the todolist), the todo directive needs to
|
||||
return a target node in addition to the todo node. The target ID (in HTML, this
|
||||
will be the anchor name) is generated by using ``env.new_serialno`` which
|
||||
returns a new unique integer on each call and therefore leads to unique target
|
||||
names. The target node is instantiated without any text (the first two
|
||||
arguments).
|
||||
Several important things are covered here. First, as you can see, we're now
|
||||
subclassing the :class:`~sphinx.util.docutils.SphinxDirective` helper class
|
||||
instead of the usual :class:`~docutils.parsers.rst.Directive` class. This
|
||||
gives us access to the :ref:`build environment instance <important-objects>`
|
||||
using the ``self.env`` property. Without this, we'd have to use the rather
|
||||
convoluted ``self.state.document.settings.env``. Then, to act as a link target
|
||||
(from ``TodolistDirective``), the ``TodoDirective`` directive needs to return a
|
||||
target node in addition to the ``todo`` node. The target ID (in HTML, this will
|
||||
be the anchor name) is generated by using ``env.new_serialno`` which returns a
|
||||
new unique integer on each call and therefore leads to unique target names. The
|
||||
target node is instantiated without any text (the first two arguments).
|
||||
|
||||
On creating admonition node, the content body of the directive are parsed using
|
||||
``self.state.nested_parse``. The first argument gives the content body, and
|
||||
the second one gives content offset. The third argument gives the parent node
|
||||
of parsed result, in our case the ``todo`` node.
|
||||
|
||||
Then, the todo node is added to the environment. This is needed to be able to
|
||||
create a list of all todo entries throughout the documentation, in the place
|
||||
where the author puts a ``todolist`` directive. For this case, the environment
|
||||
attribute ``todo_all_todos`` is used (again, the name should be unique, so it is
|
||||
prefixed by the extension name). It does not exist when a new environment is
|
||||
created, so the directive must check and create it if necessary. Various
|
||||
information about the todo entry's location are stored along with a copy of the
|
||||
node.
|
||||
of parsed result, in our case the ``todo`` node. Following this, the ``todo``
|
||||
node is added to the environment. This is needed to be able to create a list of
|
||||
all todo entries throughout the documentation, in the place where the author
|
||||
puts a ``todolist`` directive. For this case, the environment attribute
|
||||
``todo_all_todos`` is used (again, the name should be unique, so it is prefixed
|
||||
by the extension name). It does not exist when a new environment is created, so
|
||||
the directive must check and create it if necessary. Various information about
|
||||
the todo entry's location are stored along with a copy of the node.
|
||||
|
||||
In the last line, the nodes that should be put into the doctree are returned:
|
||||
the target node and the admonition node.
|
||||
@@ -217,18 +175,20 @@ The node structure that the directive returns looks like this::
|
||||
| ... |
|
||||
+--------------------+
|
||||
|
||||
.. rubric:: The event handlers
|
||||
|
||||
The Event Handlers
|
||||
------------------
|
||||
Event handlers are one of Sphinx's most powerful features, providing a way to
|
||||
do hook into any part of the documentation process. There are many events
|
||||
provided by Sphinx itself, as detailed in :ref:`the API guide <events>`, and
|
||||
we're going to use a subset of them here.
|
||||
|
||||
Finally, let's look at the event handlers. First, the one for the
|
||||
:event:`env-purge-doc` event::
|
||||
Let's look at the event handlers used in the above example. First, the one for
|
||||
the :event:`env-purge-doc` event:
|
||||
|
||||
def purge_todos(app, env, docname):
|
||||
if not hasattr(env, 'todo_all_todos'):
|
||||
return
|
||||
env.todo_all_todos = [todo for todo in env.todo_all_todos
|
||||
if todo['docname'] != docname]
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 56-61
|
||||
|
||||
Since we store information from source files in the environment, which is
|
||||
persistent, it may become out of date when the source file changes. Therefore,
|
||||
@@ -238,62 +198,144 @@ Here we clear out all todos whose docname matches the given one from the
|
||||
``todo_all_todos`` list. If there are todos left in the document, they will be
|
||||
added again during parsing.
|
||||
|
||||
The other handler belongs to the :event:`doctree-resolved` event. This event is
|
||||
emitted at the end of :ref:`phase 3 <build-phases>` and allows custom resolving
|
||||
to be done::
|
||||
The other handler belongs to the :event:`doctree-resolved` event:
|
||||
|
||||
def process_todo_nodes(app, doctree, fromdocname):
|
||||
if not app.config.todo_include_todos:
|
||||
for node in doctree.traverse(todo):
|
||||
node.parent.remove(node)
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 64-103
|
||||
|
||||
# Replace all todolist nodes with a list of the collected todos.
|
||||
# Augment each todo with a backlink to the original location.
|
||||
env = app.builder.env
|
||||
|
||||
for node in doctree.traverse(todolist):
|
||||
if not app.config.todo_include_todos:
|
||||
node.replace_self([])
|
||||
continue
|
||||
|
||||
content = []
|
||||
|
||||
for todo_info in env.todo_all_todos:
|
||||
para = nodes.paragraph()
|
||||
filename = env.doc2path(todo_info['docname'], base=None)
|
||||
description = (
|
||||
_('(The original entry is located in %s, line %d and can be found ') %
|
||||
(filename, todo_info['lineno']))
|
||||
para += nodes.Text(description, description)
|
||||
|
||||
# Create a reference
|
||||
newnode = nodes.reference('', '')
|
||||
innernode = nodes.emphasis(_('here'), _('here'))
|
||||
newnode['refdocname'] = todo_info['docname']
|
||||
newnode['refuri'] = app.builder.get_relative_uri(
|
||||
fromdocname, todo_info['docname'])
|
||||
newnode['refuri'] += '#' + todo_info['target']['refid']
|
||||
newnode.append(innernode)
|
||||
para += newnode
|
||||
para += nodes.Text('.)', '.)')
|
||||
|
||||
# Insert into the todolist
|
||||
content.append(todo_info['todo'])
|
||||
content.append(para)
|
||||
|
||||
node.replace_self(content)
|
||||
|
||||
It is a bit more involved. If our new "todo_include_todos" config value is
|
||||
false, all todo and todolist nodes are removed from the documents.
|
||||
|
||||
If not, todo nodes just stay where and how they are. Todolist nodes are
|
||||
The :event:`doctree-resolved` event is emitted at the end of :ref:`phase 3
|
||||
(resolving) <build-phases>` and allows custom resolving to be done. The handler
|
||||
we have written for this event is a bit more involved. If the
|
||||
``todo_include_todos`` config value (which we'll describe shortly) is false,
|
||||
all ``todo`` and ``todolist`` nodes are removed from the documents. If not,
|
||||
``todo`` nodes just stay where and how they are. ``todolist`` nodes are
|
||||
replaced by a list of todo entries, complete with backlinks to the location
|
||||
where they come from. The list items are composed of the nodes from the todo
|
||||
entry and docutils nodes created on the fly: a paragraph for each entry,
|
||||
containing text that gives the location, and a link (reference node containing
|
||||
an italic node) with the backreference. The reference URI is built by
|
||||
``app.builder.get_relative_uri`` which creates a suitable URI depending on the
|
||||
used builder, and appending the todo node's (the target's) ID as the anchor
|
||||
name.
|
||||
where they come from. The list items are composed of the nodes from the
|
||||
``todo`` entry and docutils nodes created on the fly: a paragraph for each
|
||||
entry, containing text that gives the location, and a link (reference node
|
||||
containing an italic node) with the backreference. The reference URI is built
|
||||
by :meth:`sphinx.builders.Builder.get_relative_uri`` which creates a suitable
|
||||
URI depending on the used builder, and appending the todo node's (the target's)
|
||||
ID as the anchor name.
|
||||
|
||||
.. rubric:: The ``setup`` function
|
||||
|
||||
.. currentmodule:: sphinx.application
|
||||
|
||||
As noted :doc:`previously <helloworld>`, the ``setup`` function is a requirement
|
||||
and is used to plug directives into Sphinx. However, we also use it to hook up
|
||||
the other parts of our extension. Let's look at our ``setup`` function:
|
||||
|
||||
.. literalinclude:: examples/todo.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 106-
|
||||
|
||||
The calls in this function refer to the classes and functions we added earlier.
|
||||
What the individual calls do is the following:
|
||||
|
||||
* :meth:`~Sphinx.add_config_value` lets Sphinx know that it should recognize the
|
||||
new *config value* ``todo_include_todos``, whose default value should be
|
||||
``False`` (this also tells Sphinx that it is a boolean value).
|
||||
|
||||
If the third argument was ``'html'``, HTML documents would be full rebuild if the
|
||||
config value changed its value. This is needed for config values that
|
||||
influence reading (build :ref:`phase 1 (reading) <build-phases>`).
|
||||
|
||||
* :meth:`~Sphinx.add_node` adds a new *node class* to the build system. It also
|
||||
can specify visitor functions for each supported output format. These visitor
|
||||
functions are needed when the new nodes stay until :ref:`phase 4 (writing)
|
||||
<build-phases>`. Since the ``todolist`` node is always replaced in
|
||||
:ref:`phase 3 (resolving) <build-phases>`, it doesn't need any.
|
||||
|
||||
* :meth:`~Sphinx.add_directive` adds a new *directive*, given by name and class.
|
||||
|
||||
* Finally, :meth:`~Sphinx.connect` adds an *event handler* to the event whose
|
||||
name is given by the first argument. The event handler function is called
|
||||
with several arguments which are documented with the event.
|
||||
|
||||
With this, our extension is complete.
|
||||
|
||||
|
||||
Using the extension
|
||||
-------------------
|
||||
|
||||
As before, we need to enable the extension by declaring it in our
|
||||
:file:`conf.py` file. There are two steps necessary here:
|
||||
|
||||
#. Add the :file:`_ext` directory to the `Python path`_ using
|
||||
``sys.path.append``. This should be placed at the top of the file.
|
||||
|
||||
#. Update or create the :confval:`extensions` list and add the extension file
|
||||
name to the list
|
||||
|
||||
In addition, we may wish to set the ``todo_include_todos`` config value. As
|
||||
noted above, this defaults to ``False`` but we can set it explicitly.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.abspath("./_ext"))
|
||||
|
||||
extensions = ['todo']
|
||||
|
||||
todo_include_todos = False
|
||||
|
||||
You can now use the extension throughout your project. For example:
|
||||
|
||||
.. code-block:: rst
|
||||
:caption: index.rst
|
||||
|
||||
Hello, world
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
somefile.rst
|
||||
someotherfile.rst
|
||||
|
||||
Hello world. Below is the list of TODOs.
|
||||
|
||||
.. todolist::
|
||||
|
||||
.. code-block:: rst
|
||||
:caption: somefile.rst
|
||||
|
||||
foo
|
||||
===
|
||||
|
||||
Some intro text here...
|
||||
|
||||
.. todo:: Fix this
|
||||
|
||||
.. code-block:: rst
|
||||
:caption: someotherfile.rst
|
||||
|
||||
bar
|
||||
===
|
||||
|
||||
Some more text here...
|
||||
|
||||
.. todo:: Fix that
|
||||
|
||||
Because we have configured ``todo_include_todos`` to ``False``, we won't
|
||||
actually see anything rendered for the ``todo`` and ``todolist`` directives.
|
||||
However, if we toggle this to true, we will see the output described
|
||||
previously.
|
||||
|
||||
|
||||
Further reading
|
||||
---------------
|
||||
|
||||
For more information, refer to the `docutils`_ documentation and
|
||||
:doc:`/extdev/index`.
|
||||
|
||||
|
||||
.. _docutils: http://docutils.sourceforge.net/docs/
|
||||
.. _Python path: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
|
||||
.. _docutils documentation: http://docutils.sourceforge.net/docs/ref/rst/directives.html
|
||||
|
||||
@@ -312,6 +312,41 @@ The following is a list of deprecated interfaces.
|
||||
- (will be) Removed
|
||||
- Alternatives
|
||||
|
||||
* - ``sphinx.ext.autodoc.importer.MockFinder``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.ext.autodoc.mock.MockFinder``
|
||||
|
||||
* - ``sphinx.ext.autodoc.importer.MockLoader``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.ext.autodoc.mock.MockLoader``
|
||||
|
||||
* - ``sphinx.ext.autodoc.importer.mock()``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.ext.autodoc.mock.mock()``
|
||||
|
||||
* - ``sphinx.ext.autosummary.autolink_role()``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.ext.autosummary.AutoLink``
|
||||
|
||||
* - ``sphinx.util.i18n.find_catalog()``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.util.i18n.docname_to_domain()``
|
||||
|
||||
* - ``sphinx.util.i18n.find_catalog_files()``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.util.i18n.CatalogRepository``
|
||||
|
||||
* - ``sphinx.util.i18n.find_catalog_source_files()``
|
||||
- 2.1
|
||||
- 4.0
|
||||
- ``sphinx.util.i18n.CatalogRepository``
|
||||
|
||||
* - ``encoding`` argument of ``autodoc.Documenter.get_doc()``,
|
||||
``autodoc.DocstringSignatureMixin.get_doc()``,
|
||||
``autodoc.DocstringSignatureMixin._find_signature()``, and
|
||||
@@ -353,6 +388,11 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``docutils.nodes.abbreviation``
|
||||
|
||||
* - ``sphinx.builders.applehelp``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinxcontrib.applehelp``
|
||||
|
||||
* - ``sphinx.builders.devhelp``
|
||||
- 2.0
|
||||
- 4.0
|
||||
@@ -363,11 +403,36 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``sphinx.builders.epub3.validate_config_values()``
|
||||
|
||||
* - ``sphinx.builders.html.JSONHTMLBuilder``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.builders.serializinghtml.JSONHTMLBuilder``
|
||||
|
||||
* - ``sphinx.builders.html.PickleHTMLBuilder``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.builders.serializinghtml.PickleHTMLBuilder``
|
||||
|
||||
* - ``sphinx.builders.html.SerializingHTMLBuilder``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.builders.serializinghtml.SerializingHTMLBuilder``
|
||||
|
||||
* - ``sphinx.builders.html.SingleFileHTMLBuilder``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.builders.singlehtml.SingleFileHTMLBuilder``
|
||||
|
||||
* - ``sphinx.builders.html.WebHTMLBuilder``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.builders.serializinghtml.PickleHTMLBuilder``
|
||||
|
||||
* - ``sphinx.builders.htmlhelp``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinxcontrib.htmlhelp``
|
||||
|
||||
* - ``sphinx.builders.htmlhelp.HTMLHelpBuilder.open_file()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
@@ -443,6 +508,31 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``sphinxcontrib.jsmath``
|
||||
|
||||
* - ``sphinx.roles.abbr_role()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.roles.Abbreviation``
|
||||
|
||||
* - ``sphinx.roles.emph_literal_role()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.roles.EmphasizedLiteral``
|
||||
|
||||
* - ``sphinx.roles.menusel_role()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.roles.GUILabel`` or ``sphinx.roles.MenuSelection``
|
||||
|
||||
* - ``sphinx.roles.index_role()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.roles.Index``
|
||||
|
||||
* - ``sphinx.roles.indexmarkup_role()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.roles.PEP`` or ``sphinx.roles.RFC``
|
||||
|
||||
* - ``sphinx.testing.util.remove_unicode_literal()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
@@ -468,6 +558,11 @@ The following is a list of deprecated interfaces.
|
||||
- 3.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.util.jsonimpl``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinxcontrib.serializinghtml.jsonimpl``
|
||||
|
||||
* - ``sphinx.util.osutil.EEXIST``
|
||||
- 2.0
|
||||
- 4.0
|
||||
@@ -493,6 +588,11 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``os.walk()``
|
||||
|
||||
* - ``sphinx.util.pycompat.NoneType``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.util.typing.NoneType``
|
||||
|
||||
* - ``sphinx.util.pycompat.TextIOWrapper``
|
||||
- 2.0
|
||||
- 4.0
|
||||
@@ -513,6 +613,16 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``textwrap.indent()``
|
||||
|
||||
* - ``sphinx.util.pycompat.sys_encoding``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sys.getdefaultencoding()``
|
||||
|
||||
* - ``sphinx.util.pycompat.terminal_safe()``
|
||||
- 2.0
|
||||
- 4.0
|
||||
- ``sphinx.util.console.terminal_safe()``
|
||||
|
||||
* - ``sphinx.util.pycompat.u``
|
||||
- 2.0
|
||||
- 4.0
|
||||
|
||||
@@ -18,5 +18,11 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily.
|
||||
.. autoclass:: sphinx.util.docutils.SphinxDirective
|
||||
:members:
|
||||
|
||||
.. autoclass:: sphinx.util.docutils.SphinxRole
|
||||
:members:
|
||||
|
||||
.. autoclass:: sphinx.util.docutils.ReferenceRole
|
||||
:members:
|
||||
|
||||
.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter
|
||||
:members:
|
||||
|
||||
32
doc/faq.rst
32
doc/faq.rst
@@ -88,7 +88,7 @@ MediaWiki
|
||||
Google Analytics
|
||||
You can use a custom ``layout.html`` template, like this:
|
||||
|
||||
.. code-block:: html+django
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
@@ -119,6 +119,36 @@ Google Analytics
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Google Search
|
||||
To replace Sphinx's built-in search function with Google Search, proceed as
|
||||
follows:
|
||||
|
||||
1. Go to https://cse.google.com/cse/all to create the Google Search code
|
||||
snippet.
|
||||
|
||||
2. Copy the code snippet and paste it into ``_templates/searchbox.html`` in
|
||||
your Sphinx project:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
<div>
|
||||
<h3>{{ _('Quick search') }}</h3>
|
||||
<script>
|
||||
(function() {
|
||||
var cx = '......';
|
||||
var gcse = document.createElement('script');
|
||||
gcse.type = 'text/javascript';
|
||||
gcse.async = true;
|
||||
gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(gcse, s);
|
||||
})();
|
||||
</script>
|
||||
<gcse:search></gcse:search>
|
||||
</div>
|
||||
|
||||
3. Add ``searchbox.html`` to the :confval:`html_sidebars` configuration value.
|
||||
|
||||
.. _api role: https://git.savannah.gnu.org/cgit/kenozooid.git/tree/doc/extapi.py
|
||||
.. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py
|
||||
|
||||
|
||||
@@ -70,10 +70,6 @@ Options
|
||||
|
||||
Master document name. (see :confval:`master_doc`).
|
||||
|
||||
.. option:: --epub
|
||||
|
||||
Use epub.
|
||||
|
||||
.. rubric:: Extension Options
|
||||
|
||||
.. option:: --ext-autodoc
|
||||
|
||||
@@ -28,6 +28,7 @@ The builder's "name" must be given to the **-b** command-line option of
|
||||
|
||||
.. autoattribute:: supported_image_types
|
||||
|
||||
.. module:: sphinx.builders.dirhtml
|
||||
.. class:: DirectoryHTMLBuilder
|
||||
|
||||
This is a subclass of the standard HTML builder. Its output is a directory
|
||||
@@ -45,6 +46,7 @@ The builder's "name" must be given to the **-b** command-line option of
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
.. module:: sphinx.builders.singlehtml
|
||||
.. class:: SingleFileHTMLBuilder
|
||||
|
||||
This is an HTML builder that combines the whole project in one output file.
|
||||
@@ -91,7 +93,7 @@ The builder's "name" must be given to the **-b** command-line option of
|
||||
|
||||
.. _Qt help: https://doc.qt.io/qt-4.8/qthelp-framework.html
|
||||
|
||||
.. module:: sphinx.builders.applehelp
|
||||
.. module:: sphinxcontrib.applehelp
|
||||
.. class:: AppleHelpBuilder
|
||||
|
||||
This builder produces an Apple Help Book based on the same output as the
|
||||
@@ -117,6 +119,10 @@ The builder's "name" must be given to the **-b** command-line option of
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
|
||||
Moved to sphinxcontrib.applehelp from sphinx.builders package.
|
||||
|
||||
.. module:: sphinxcontrib.devhelp
|
||||
.. class:: DevhelpBuilder
|
||||
|
||||
@@ -293,7 +299,7 @@ name is ``rinoh``. Refer to the `rinohtype manual`_ for details.
|
||||
.. versionadded:: 1.1
|
||||
|
||||
|
||||
.. currentmodule:: sphinx.builders.html
|
||||
.. currentmodule:: sphinxcontrib.serializinghtml
|
||||
.. class:: SerializingHTMLBuilder
|
||||
|
||||
This builder uses a module that implements the Python serialization API
|
||||
|
||||
@@ -509,6 +509,13 @@ General configuration
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
.. tip:: Sphinx uses requests_ as a HTTP library internally.
|
||||
Therefore, Sphinx refers a certification file on the
|
||||
directory pointed ``REQUESTS_CA_BUNDLE`` environment
|
||||
variable if ``tls_cacerts`` not set.
|
||||
|
||||
.. _requests: http://docs.python-requests.org/en/master/
|
||||
|
||||
.. confval:: today
|
||||
today_fmt
|
||||
|
||||
@@ -1331,6 +1338,12 @@ that use Sphinx's HTMLWriter class.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. deprecated:: 2.0
|
||||
|
||||
.. confval:: html4_writer
|
||||
|
||||
Output is processed with HTML4 writer. Default is ``False``.
|
||||
|
||||
Options for Single HTML output
|
||||
-------------------------------
|
||||
|
||||
@@ -1561,7 +1574,11 @@ the `Dublin Core metadata <http://dublincore.org/>`_.
|
||||
.. confval:: epub_title
|
||||
|
||||
The title of the document. It defaults to the :confval:`html_title` option
|
||||
but can be set independently for epub creation.
|
||||
but can be set independently for epub creation. It defaults to the
|
||||
:confval:`project` option.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
It defaults to the ``project`` option.
|
||||
|
||||
.. confval:: epub_description
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
|
||||
|
||||
.. rst:directive:: autofunction
|
||||
autodecorator
|
||||
autodata
|
||||
automethod
|
||||
autoattribute
|
||||
@@ -293,10 +294,11 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
docstrings.
|
||||
.. versionchanged:: 1.1
|
||||
Comment docs are now allowed on the same line after an assignment.
|
||||
|
||||
.. versionchanged:: 1.2
|
||||
:rst:dir:`autodata` and :rst:dir:`autoattribute` have an ``annotation``
|
||||
option.
|
||||
.. versionchanged:: 2.0
|
||||
:rst:dir:`autodecorator` added.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -313,7 +315,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
There are also new config values that you can set:
|
||||
There are also config values that you can set:
|
||||
|
||||
.. confval:: autoclass_content
|
||||
|
||||
@@ -376,12 +378,12 @@ There are also new config values that you can set:
|
||||
'members': 'var1, var2',
|
||||
'member-order': 'bysource',
|
||||
'special-members': '__init__',
|
||||
'undoc-members': None,
|
||||
'undoc-members': True,
|
||||
'exclude-members': '__weakref__'
|
||||
}
|
||||
|
||||
Setting ``None`` is equivalent to giving the option name in the list format
|
||||
(i.e. it means "yes/true/on").
|
||||
Setting ``None`` or ``True`` to the value is equivalent to giving only the
|
||||
option name to the directives.
|
||||
|
||||
The supported options are ``'members'``, ``'member-order'``,
|
||||
``'undoc-members'``, ``'private-members'``, ``'special-members'``,
|
||||
@@ -390,6 +392,9 @@ There are also new config values that you can set:
|
||||
|
||||
.. versionadded:: 1.8
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Accepts ``True`` as a value.
|
||||
|
||||
.. confval:: autodoc_docstring_signature
|
||||
|
||||
Functions imported from C modules cannot be introspected, and therefore the
|
||||
|
||||
@@ -38,3 +38,10 @@ Configuration
|
||||
called ``Introduction`` that appears in document ``index.rst``. Useful for
|
||||
avoiding ambiguity when the same section heading appears in different
|
||||
documents.
|
||||
|
||||
.. confval:: autosectionlabel_maxdepth
|
||||
|
||||
If set, autosectionlabel chooses the sections for labeling by its depth. For
|
||||
example, when set 1 to ``autosectionlabel_maxdepth``, labels are generated
|
||||
only for top level sections, and deeper sections are not labeled. It
|
||||
defaults to ``None`` (disabled).
|
||||
|
||||
@@ -131,7 +131,7 @@ Generating stub pages automatically
|
||||
-----------------------------------
|
||||
|
||||
If you do not want to create stub pages with :program:`sphinx-autogen`, you can
|
||||
also use this new config value:
|
||||
also use these config values:
|
||||
|
||||
.. confval:: autosummary_generate
|
||||
|
||||
@@ -143,6 +143,11 @@ also use this new config value:
|
||||
The new files will be placed in the directories specified in the
|
||||
``:toctree:`` options of the directives.
|
||||
|
||||
.. confval:: autosummary_mock_imports
|
||||
|
||||
This value contains a list of modules to be mocked up. See
|
||||
:confval:`autodoc_mock_imports` for more details. It defaults to
|
||||
:confval:`autodoc_mock_imports`.
|
||||
|
||||
Customizing templates
|
||||
---------------------
|
||||
|
||||
@@ -13,7 +13,7 @@ This extension features one additional builder, the :class:`CoverageBuilder`.
|
||||
|
||||
.. todo:: Write this section.
|
||||
|
||||
Several new configuration values can be used to specify what the builder
|
||||
Several configuration values can be used to specify what the builder
|
||||
should check:
|
||||
|
||||
.. confval:: coverage_ignore_modules
|
||||
|
||||
@@ -18,7 +18,7 @@ tracker, at :samp:`https://github.com/sphinx-doc/sphinx/issues/{num}`. Typing
|
||||
this URL again and again is tedious, so you can use :mod:`~sphinx.ext.extlinks`
|
||||
to avoid repeating yourself.
|
||||
|
||||
The extension adds one new config value:
|
||||
The extension adds a config value:
|
||||
|
||||
.. confval:: extlinks
|
||||
|
||||
|
||||
@@ -6,5 +6,11 @@
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Support ``CNAME`` file
|
||||
|
||||
This extension creates ``.nojekyll`` file on generated HTML directory to publish
|
||||
the document on GitHub Pages.
|
||||
|
||||
It also creates a ``CNAME`` file for custom domains when :confval:`html_baseurl`
|
||||
set.
|
||||
|
||||
@@ -90,7 +90,7 @@ It adds these directives:
|
||||
.. versionadded:: 1.6
|
||||
All three directives support a ``name`` option to set the label to graph.
|
||||
|
||||
There are also these new config values:
|
||||
There are also these config values:
|
||||
|
||||
.. confval:: graphviz_dot
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Configuration
|
||||
-------------
|
||||
|
||||
To use Intersphinx linking, add ``'sphinx.ext.intersphinx'`` to your
|
||||
:confval:`extensions` config value, and use these new config values to activate
|
||||
:confval:`extensions` config value, and use these config values to activate
|
||||
linking:
|
||||
|
||||
.. confval:: intersphinx_mapping
|
||||
|
||||
@@ -463,7 +463,7 @@ __ http://pygments.org/docs/lexers/
|
||||
|
||||
This will produce line numbers for all code blocks longer than five lines.
|
||||
|
||||
.. rst:directive:: .. code-block:: language
|
||||
.. rst:directive:: .. code-block:: [language]
|
||||
|
||||
Example::
|
||||
|
||||
@@ -471,9 +471,11 @@ __ http://pygments.org/docs/lexers/
|
||||
|
||||
Some Ruby code.
|
||||
|
||||
The directive's alias name :rst:dir:`sourcecode` works as well. As with
|
||||
:rst:dir:`highlight`\ 's ``language`` option, ``language`` can be any lexer
|
||||
alias supported by Pygments.
|
||||
The directive's alias name :rst:dir:`sourcecode` works as well. This
|
||||
directive takes a language name as an argument. It can be any lexer alias
|
||||
supported by Pygments. If it is not given, the setting of
|
||||
:rst:dir:`highlight` directive will be used. If not set,
|
||||
:confval:`highlight_language` will be used.
|
||||
|
||||
**Additional options**
|
||||
|
||||
@@ -523,15 +525,18 @@ __ http://pygments.org/docs/lexers/
|
||||
|
||||
some ruby code
|
||||
|
||||
.. versionchanged:: 1.1
|
||||
The ``emphasize-lines`` option has been added.
|
||||
.. versionchanged:: 1.1
|
||||
The ``emphasize-lines`` option has been added.
|
||||
|
||||
.. versionchanged:: 1.3
|
||||
The ``lineno-start``, ``caption``, ``name`` and ``dedent`` options have
|
||||
been added.
|
||||
.. versionchanged:: 1.3
|
||||
The ``lineno-start``, ``caption``, ``name`` and ``dedent`` options have
|
||||
been added.
|
||||
|
||||
.. versionchanged:: 1.6.6
|
||||
LaTeX supports the ``emphasize-lines`` option.
|
||||
.. versionchanged:: 1.6.6
|
||||
LaTeX supports the ``emphasize-lines`` option.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The ``language`` argument becomes optional.
|
||||
|
||||
.. rst:directive:: .. literalinclude:: filename
|
||||
|
||||
|
||||
@@ -1195,6 +1195,7 @@ Configuration Variables
|
||||
|
||||
See :ref:`cpp-config`.
|
||||
|
||||
.. _domains-std:
|
||||
|
||||
The Standard Domain
|
||||
-------------------
|
||||
|
||||
3
setup.py
3
setup.py
@@ -15,8 +15,11 @@ if sys.version_info < (3, 5):
|
||||
sys.exit(1)
|
||||
|
||||
install_requires = [
|
||||
'sphinxcontrib-applehelp',
|
||||
'sphinxcontrib-devhelp',
|
||||
'sphinxcontrib-jsmath',
|
||||
'sphinxcontrib-htmlhelp',
|
||||
'sphinxcontrib-serializinghtml',
|
||||
'sphinxcontrib-qthelp',
|
||||
'Jinja2>=2.3',
|
||||
'Pygments>=2.0',
|
||||
|
||||
@@ -32,8 +32,8 @@ if 'PYTHONWARNINGS' not in os.environ:
|
||||
warnings.filterwarnings('ignore', "'U' mode is deprecated",
|
||||
DeprecationWarning, module='docutils.io')
|
||||
|
||||
__version__ = '2.0.0+'
|
||||
__released__ = '2.0.0' # used when Sphinx builds its own docs
|
||||
__version__ = '2.1.0+'
|
||||
__released__ = '2.1.0' # used when Sphinx builds its own docs
|
||||
|
||||
#: Version info for better programmatic use.
|
||||
#:
|
||||
@@ -43,7 +43,7 @@ __released__ = '2.0.0' # used when Sphinx builds its own docs
|
||||
#:
|
||||
#: .. versionadded:: 1.2
|
||||
#: Before version 1.2, check the string ``sphinx.__version__``.
|
||||
version_info = (2, 0, 0, 'beta', 0)
|
||||
version_info = (2, 1, 0, 'beta', 0)
|
||||
|
||||
package_dir = path.abspath(path.dirname(__file__))
|
||||
|
||||
|
||||
@@ -37,11 +37,10 @@ from sphinx.registry import SphinxComponentRegistry
|
||||
from sphinx.util import docutils
|
||||
from sphinx.util import import_object, progress_message
|
||||
from sphinx.util import logging
|
||||
from sphinx.util import pycompat # noqa: F401
|
||||
from sphinx.util.build_phase import BuildPhase
|
||||
from sphinx.util.console import bold # type: ignore
|
||||
from sphinx.util.docutils import directive_helper
|
||||
from sphinx.util.i18n import find_catalog_source_files
|
||||
from sphinx.util.i18n import CatalogRepository
|
||||
from sphinx.util.logging import prefixed_warnings
|
||||
from sphinx.util.osutil import abspath, ensuredir, relpath
|
||||
from sphinx.util.tags import Tags
|
||||
@@ -62,13 +61,12 @@ if False:
|
||||
|
||||
builtin_extensions = (
|
||||
'sphinx.addnodes',
|
||||
'sphinx.builders.applehelp',
|
||||
'sphinx.builders.changes',
|
||||
'sphinx.builders.epub3',
|
||||
'sphinx.builders.dirhtml',
|
||||
'sphinx.builders.dummy',
|
||||
'sphinx.builders.gettext',
|
||||
'sphinx.builders.html',
|
||||
'sphinx.builders.htmlhelp',
|
||||
'sphinx.builders.latex',
|
||||
'sphinx.builders.linkcheck',
|
||||
'sphinx.builders.manpage',
|
||||
@@ -106,7 +104,10 @@ builtin_extensions = (
|
||||
'sphinx.environment.collectors.toctree',
|
||||
'sphinx.environment.collectors.indexentries',
|
||||
# 1st party extensions
|
||||
'sphinxcontrib.applehelp',
|
||||
'sphinxcontrib.devhelp',
|
||||
'sphinxcontrib.htmlhelp',
|
||||
'sphinxcontrib.serializinghtml',
|
||||
'sphinxcontrib.qthelp',
|
||||
# Strictly, alabaster theme is not a builtin extension,
|
||||
# but it is loaded automatically to use it as default theme.
|
||||
@@ -264,21 +265,21 @@ class Sphinx:
|
||||
"""Load translated strings from the configured localedirs if enabled in
|
||||
the configuration.
|
||||
"""
|
||||
if self.config.language is not None:
|
||||
if self.config.language is None:
|
||||
self.translator, has_translation = locale.init([], None)
|
||||
else:
|
||||
logger.info(bold(__('loading translations [%s]... ') % self.config.language),
|
||||
nonl=True)
|
||||
user_locale_dirs = [
|
||||
path.join(self.srcdir, x) for x in self.config.locale_dirs]
|
||||
|
||||
# compile mo files if sphinx.po file in user locale directories are updated
|
||||
for catinfo in find_catalog_source_files(
|
||||
user_locale_dirs, self.config.language, domains=['sphinx'],
|
||||
charset=self.config.source_encoding):
|
||||
catinfo.write_mo(self.config.language)
|
||||
locale_dirs = [None, path.join(package_dir, 'locale')] + user_locale_dirs
|
||||
else:
|
||||
locale_dirs = []
|
||||
self.translator, has_translation = locale.init(locale_dirs, self.config.language)
|
||||
if self.config.language is not None:
|
||||
repo = CatalogRepository(self.srcdir, self.config.locale_dirs,
|
||||
self.config.language, self.config.source_encoding)
|
||||
for catalog in repo.catalogs:
|
||||
if catalog.domain == 'sphinx' and catalog.is_outdated():
|
||||
catalog.write_mo(self.config.language)
|
||||
|
||||
locale_dirs = [None, path.join(package_dir, 'locale')] + list(repo.locale_dirs)
|
||||
self.translator, has_translation = locale.init(locale_dirs, self.config.language)
|
||||
if has_translation or self.config.language == 'en':
|
||||
# "en" never needs to be translated
|
||||
logger.info(__('done'))
|
||||
|
||||
@@ -19,12 +19,11 @@ from sphinx.environment.adapters.asset import ImageAdapter
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.io import read_doc
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import i18n, import_object, logging, rst, status_iterator
|
||||
from sphinx.util import import_object, logging, rst, progress_message, status_iterator
|
||||
from sphinx.util.build_phase import BuildPhase
|
||||
from sphinx.util.console import bold # type: ignore
|
||||
from sphinx.util.docutils import sphinx_domains
|
||||
from sphinx.util.i18n import find_catalog
|
||||
from sphinx.util.matching import Matcher
|
||||
from sphinx.util.i18n import CatalogRepository, docname_to_domain
|
||||
from sphinx.util.osutil import SEP, ensuredir, relative_uri, relpath
|
||||
from sphinx.util.parallel import ParallelTasks, SerialTasks, make_chunks, \
|
||||
parallel_available
|
||||
@@ -236,14 +235,10 @@ class Builder:
|
||||
|
||||
def compile_all_catalogs(self):
|
||||
# type: () -> None
|
||||
catalogs = i18n.find_catalog_source_files(
|
||||
[path.join(self.srcdir, x) for x in self.config.locale_dirs],
|
||||
self.config.language,
|
||||
charset=self.config.source_encoding,
|
||||
force_all=True,
|
||||
excluded=Matcher(['**/.?**']))
|
||||
message = __('all of %d po files') % len(catalogs)
|
||||
self.compile_catalogs(catalogs, message)
|
||||
repo = CatalogRepository(self.srcdir, self.config.locale_dirs,
|
||||
self.config.language, self.config.source_encoding)
|
||||
message = __('all of %d po files') % len(list(repo.catalogs))
|
||||
self.compile_catalogs(set(repo.catalogs), message)
|
||||
|
||||
def compile_specific_catalogs(self, specified_files):
|
||||
# type: (List[str]) -> None
|
||||
@@ -251,28 +246,25 @@ class Builder:
|
||||
# type: (str) -> str
|
||||
docname = self.env.path2doc(path.abspath(fpath))
|
||||
if docname:
|
||||
return find_catalog(docname, self.config.gettext_compact)
|
||||
return docname_to_domain(docname, self.config.gettext_compact)
|
||||
else:
|
||||
return None
|
||||
|
||||
specified_domains = set(map(to_domain, specified_files))
|
||||
specified_domains.discard(None)
|
||||
catalogs = i18n.find_catalog_source_files(
|
||||
[path.join(self.srcdir, x) for x in self.config.locale_dirs],
|
||||
self.config.language,
|
||||
domains=list(specified_domains),
|
||||
charset=self.config.source_encoding,
|
||||
excluded=Matcher(['**/.?**']))
|
||||
catalogs = set()
|
||||
domains = set(map(to_domain, specified_files))
|
||||
repo = CatalogRepository(self.srcdir, self.config.locale_dirs,
|
||||
self.config.language, self.config.source_encoding)
|
||||
for catalog in repo.catalogs:
|
||||
if catalog.domain in domains and catalog.is_outdated():
|
||||
catalogs.add(catalog)
|
||||
message = __('targets for %d po files that are specified') % len(catalogs)
|
||||
self.compile_catalogs(catalogs, message)
|
||||
|
||||
def compile_update_catalogs(self):
|
||||
# type: () -> None
|
||||
catalogs = i18n.find_catalog_source_files(
|
||||
[path.join(self.srcdir, x) for x in self.config.locale_dirs],
|
||||
self.config.language,
|
||||
charset=self.config.source_encoding,
|
||||
excluded=Matcher(['**/.?**']))
|
||||
repo = CatalogRepository(self.srcdir, self.config.locale_dirs,
|
||||
self.config.language, self.config.source_encoding)
|
||||
catalogs = {c for c in repo.catalogs if c.is_outdated()}
|
||||
message = __('targets for %d po files that are out of date') % len(catalogs)
|
||||
self.compile_catalogs(catalogs, message)
|
||||
|
||||
@@ -298,8 +290,7 @@ class Builder:
|
||||
logger.warning(__('file %r given on command line is not under the '
|
||||
'source directory, ignoring'), filename)
|
||||
continue
|
||||
if not (path.isfile(filename) or
|
||||
any(path.isfile(filename + suffix) for suffix in suffixes)):
|
||||
if not path.isfile(filename):
|
||||
logger.warning(__('file %r given on command line does not exist, '
|
||||
'ignoring'), filename)
|
||||
continue
|
||||
@@ -351,16 +342,14 @@ class Builder:
|
||||
if updated_docnames:
|
||||
# save the environment
|
||||
from sphinx.application import ENV_PICKLE_FILENAME
|
||||
logger.info(bold(__('pickling environment... ')), nonl=True)
|
||||
with open(path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f:
|
||||
pickle.dump(self.env, f, pickle.HIGHEST_PROTOCOL)
|
||||
logger.info(__('done'))
|
||||
with progress_message(__('pickling environment')):
|
||||
with open(path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f:
|
||||
pickle.dump(self.env, f, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
# global actions
|
||||
self.app.phase = BuildPhase.CONSISTENCY_CHECK
|
||||
logger.info(bold(__('checking consistency... ')), nonl=True)
|
||||
self.env.check_consistency()
|
||||
logger.info(__('done'))
|
||||
with progress_message(__('checking consistency')):
|
||||
self.env.check_consistency()
|
||||
else:
|
||||
if method == 'update' and not docnames:
|
||||
logger.info(bold(__('no targets are out of date.')))
|
||||
@@ -559,9 +548,8 @@ class Builder:
|
||||
docnames.add(tocdocname)
|
||||
docnames.add(self.config.master_doc)
|
||||
|
||||
logger.info(bold(__('preparing documents... ')), nonl=True)
|
||||
self.prepare_writing(docnames)
|
||||
logger.info(__('done'))
|
||||
with progress_message(__('preparing documents')):
|
||||
self.prepare_writing(docnames)
|
||||
|
||||
if self.parallel_ok:
|
||||
# number of subprocesses is parallel-1 because the main process
|
||||
|
||||
@@ -35,7 +35,7 @@ except ImportError:
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, List, Tuple # NOQA
|
||||
from typing import Any, Dict, List, Set, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
@@ -134,8 +134,6 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
||||
html_scaled_image_link = False
|
||||
# don't generate search index or include search page
|
||||
search = False
|
||||
# use html5 translator by default
|
||||
default_html5_translator = True
|
||||
|
||||
coverpage_name = COVERPAGE_NAME
|
||||
toctree_template = TOCTREE_TEMPLATE
|
||||
@@ -213,6 +211,15 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
||||
result = self.get_refnodes(elem, result)
|
||||
return result
|
||||
|
||||
def check_refnodes(self, nodes):
|
||||
# type: (List[Dict[str, Any]]) -> None
|
||||
appeared = set() # type: Set[str]
|
||||
for node in nodes:
|
||||
if node['refuri'] in appeared:
|
||||
logger.warning(__('duplicated ToC entry found: %s'), node['refuri'])
|
||||
else:
|
||||
appeared.add(node['refuri'])
|
||||
|
||||
def get_toc(self):
|
||||
# type: () -> None
|
||||
"""Get the total table of contents, containing the master_doc
|
||||
@@ -646,7 +653,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
||||
if incr:
|
||||
self.playorder += 1
|
||||
self.tocid += 1
|
||||
return NavPoint(self.esc('navPoint%d' % self.tocid), self.playorder,
|
||||
return NavPoint('navPoint%d' % self.tocid, self.playorder,
|
||||
node['text'], node['refuri'], [])
|
||||
|
||||
def build_navpoints(self, nodes):
|
||||
@@ -726,6 +733,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
|
||||
else:
|
||||
# 'includehidden'
|
||||
refnodes = self.refnodes
|
||||
self.check_refnodes(refnodes)
|
||||
navpoints = self.build_navpoints(refnodes)
|
||||
level = max(item['level'] for item in self.refnodes)
|
||||
level = min(level, self.config.epub_tocdepth)
|
||||
|
||||
@@ -8,21 +8,15 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import plistlib
|
||||
import shlex
|
||||
import subprocess
|
||||
from os import path, environ
|
||||
from subprocess import CalledProcessError, PIPE, STDOUT
|
||||
import warnings
|
||||
|
||||
from sphinx import package_dir
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util import SkipProgressMessage, progress_message
|
||||
from sphinx.util.fileutil import copy_asset, copy_asset_file
|
||||
from sphinx.util.matching import Matcher
|
||||
from sphinx.util.osutil import ensuredir, make_filename
|
||||
from sphinxcontrib.applehelp import (
|
||||
AppleHelpCodeSigningFailed,
|
||||
AppleHelpIndexerFailed,
|
||||
AppleHelpBuilder,
|
||||
)
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -30,240 +24,20 @@ if False:
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
template_dir = path.join(package_dir, 'templates', 'applehelp')
|
||||
|
||||
|
||||
class AppleHelpIndexerFailed(SphinxError):
|
||||
category = __('Help indexer failed')
|
||||
|
||||
|
||||
class AppleHelpCodeSigningFailed(SphinxError):
|
||||
category = __('Code signing failed')
|
||||
|
||||
|
||||
class AppleHelpBuilder(StandaloneHTMLBuilder):
|
||||
"""
|
||||
Builder that outputs an Apple help book. Requires Mac OS X as it relies
|
||||
on the ``hiutil`` command line tool.
|
||||
"""
|
||||
name = 'applehelp'
|
||||
epilog = __('The help book is in %(outdir)s.\n'
|
||||
'Note that won\'t be able to view it unless you put it in '
|
||||
'~/Library/Documentation/Help or install it in your application '
|
||||
'bundle.')
|
||||
|
||||
# don't copy the reST source
|
||||
copysource = False
|
||||
supported_image_types = ['image/png', 'image/gif', 'image/jpeg',
|
||||
'image/tiff', 'image/jp2', 'image/svg+xml']
|
||||
|
||||
# don't add links
|
||||
add_permalinks = False
|
||||
|
||||
# this is an embedded HTML format
|
||||
embedded = True
|
||||
|
||||
# don't generate the search index or include the search page
|
||||
search = False
|
||||
|
||||
def init(self):
|
||||
# type: () -> None
|
||||
super().init()
|
||||
# the output files for HTML help must be .html only
|
||||
self.out_suffix = '.html'
|
||||
self.link_suffix = '.html'
|
||||
|
||||
if self.config.applehelp_bundle_id is None:
|
||||
raise SphinxError(__('You must set applehelp_bundle_id before '
|
||||
'building Apple Help output'))
|
||||
|
||||
self.bundle_path = path.join(self.outdir,
|
||||
self.config.applehelp_bundle_name + '.help')
|
||||
self.outdir = path.join(self.bundle_path,
|
||||
'Contents',
|
||||
'Resources',
|
||||
self.config.applehelp_locale + '.lproj')
|
||||
|
||||
def handle_finish(self):
|
||||
# type: () -> None
|
||||
super().handle_finish()
|
||||
|
||||
self.finish_tasks.add_task(self.copy_localized_files)
|
||||
self.finish_tasks.add_task(self.build_helpbook)
|
||||
|
||||
@progress_message(__('copying localized files'))
|
||||
def copy_localized_files(self):
|
||||
# type: () -> None
|
||||
source_dir = path.join(self.confdir, self.config.applehelp_locale + '.lproj')
|
||||
target_dir = self.outdir
|
||||
|
||||
if path.isdir(source_dir):
|
||||
excluded = Matcher(self.config.exclude_patterns + ['**/.*'])
|
||||
copy_asset(source_dir, target_dir, excluded,
|
||||
context=self.globalcontext, renderer=self.templates)
|
||||
|
||||
def build_helpbook(self):
|
||||
# type: () -> None
|
||||
contents_dir = path.join(self.bundle_path, 'Contents')
|
||||
resources_dir = path.join(contents_dir, 'Resources')
|
||||
language_dir = path.join(resources_dir,
|
||||
self.config.applehelp_locale + '.lproj')
|
||||
ensuredir(language_dir)
|
||||
|
||||
self.build_info_plist(contents_dir)
|
||||
self.copy_applehelp_icon(resources_dir)
|
||||
self.build_access_page(language_dir)
|
||||
self.build_helpindex(language_dir)
|
||||
|
||||
if self.config.applehelp_codesign_identity:
|
||||
self.do_codesign()
|
||||
|
||||
@progress_message(__('writing Info.plist'))
|
||||
def build_info_plist(self, contents_dir):
|
||||
# type: (str) -> None
|
||||
"""Construct the Info.plist file."""
|
||||
info_plist = {
|
||||
'CFBundleDevelopmentRegion': self.config.applehelp_dev_region,
|
||||
'CFBundleIdentifier': self.config.applehelp_bundle_id,
|
||||
'CFBundleInfoDictionaryVersion': '6.0',
|
||||
'CFBundlePackageType': 'BNDL',
|
||||
'CFBundleShortVersionString': self.config.release,
|
||||
'CFBundleSignature': 'hbwr',
|
||||
'CFBundleVersion': self.config.applehelp_bundle_version,
|
||||
'HPDBookAccessPath': '_access.html',
|
||||
'HPDBookIndexPath': 'search.helpindex',
|
||||
'HPDBookTitle': self.config.applehelp_title,
|
||||
'HPDBookType': '3',
|
||||
'HPDBookUsesExternalViewer': False,
|
||||
}
|
||||
|
||||
if self.config.applehelp_icon is not None:
|
||||
info_plist['HPDBookIconPath'] = path.basename(self.config.applehelp_icon)
|
||||
|
||||
if self.config.applehelp_kb_url is not None:
|
||||
info_plist['HPDBookKBProduct'] = self.config.applehelp_kb_product
|
||||
info_plist['HPDBookKBURL'] = self.config.applehelp_kb_url
|
||||
|
||||
if self.config.applehelp_remote_url is not None:
|
||||
info_plist['HPDBookRemoteURL'] = self.config.applehelp_remote_url
|
||||
|
||||
with open(path.join(contents_dir, 'Info.plist'), 'wb') as f:
|
||||
plistlib.dump(info_plist, f)
|
||||
|
||||
def copy_applehelp_icon(self, resources_dir):
|
||||
# type: (str) -> None
|
||||
"""Copy the icon, if one is supplied."""
|
||||
if self.config.applehelp_icon:
|
||||
|
||||
try:
|
||||
with progress_message(__('copying icon... ')):
|
||||
applehelp_icon = path.join(self.srcdir, self.config.applehelp_icon)
|
||||
copy_asset_file(applehelp_icon, resources_dir)
|
||||
except Exception as err:
|
||||
logger.warning(__('cannot copy icon file %r: %s'), applehelp_icon, err)
|
||||
|
||||
@progress_message(__('building access page'))
|
||||
def build_access_page(self, language_dir):
|
||||
# type: (str) -> None
|
||||
"""Build the access page."""
|
||||
context = {
|
||||
'toc': self.config.master_doc + self.out_suffix,
|
||||
'title': self.config.applehelp_title,
|
||||
}
|
||||
copy_asset_file(path.join(template_dir, '_access.html_t'), language_dir, context)
|
||||
|
||||
@progress_message(__('generating help index'))
|
||||
def build_helpindex(self, language_dir):
|
||||
# type: (str) -> None
|
||||
"""Generate the help index."""
|
||||
args = [
|
||||
self.config.applehelp_indexer_path,
|
||||
'-Cf',
|
||||
path.join(language_dir, 'search.helpindex'),
|
||||
language_dir
|
||||
]
|
||||
|
||||
if self.config.applehelp_index_anchors is not None:
|
||||
args.append('-a')
|
||||
|
||||
if self.config.applehelp_min_term_length is not None:
|
||||
args += ['-m', '%s' % self.config.applehelp_min_term_length]
|
||||
|
||||
if self.config.applehelp_stopwords is not None:
|
||||
args += ['-s', self.config.applehelp_stopwords]
|
||||
|
||||
if self.config.applehelp_locale is not None:
|
||||
args += ['-l', self.config.applehelp_locale]
|
||||
|
||||
if self.config.applehelp_disable_external_tools:
|
||||
raise SkipProgressMessage(__('you will need to index this help book with:\n %s'),
|
||||
' '.join([shlex.quote(arg) for arg in args]))
|
||||
else:
|
||||
try:
|
||||
subprocess.run(args, stdout=PIPE, stderr=STDOUT, check=True)
|
||||
except OSError:
|
||||
raise AppleHelpIndexerFailed(__('Command not found: %s') % args[0])
|
||||
except CalledProcessError as exc:
|
||||
raise AppleHelpIndexerFailed(exc.stdout)
|
||||
|
||||
@progress_message(__('signing help book'))
|
||||
def do_codesign(self):
|
||||
# type: () -> None
|
||||
"""If we've been asked to, sign the bundle."""
|
||||
args = [
|
||||
self.config.applehelp_codesign_path,
|
||||
'-s', self.config.applehelp_codesign_identity,
|
||||
'-f'
|
||||
]
|
||||
|
||||
args += self.config.applehelp_codesign_flags
|
||||
|
||||
args.append(self.bundle_path)
|
||||
|
||||
if self.config.applehelp_disable_external_tools:
|
||||
raise SkipProgressMessage(__('you will need to sign this help book with:\n %s'),
|
||||
' '.join([shlex.quote(arg) for arg in args]))
|
||||
else:
|
||||
try:
|
||||
subprocess.run(args, stdout=PIPE, stderr=STDOUT, check=True)
|
||||
except OSError:
|
||||
raise AppleHelpCodeSigningFailed(__('Command not found: %s') % args[0])
|
||||
except CalledProcessError as exc:
|
||||
raise AppleHelpCodeSigningFailed(exc.stdout)
|
||||
deprecated_alias('sphinx.builders.applehelp',
|
||||
{
|
||||
'AppleHelpCodeSigningFailed': AppleHelpCodeSigningFailed,
|
||||
'AppleHelpIndexerFailed': AppleHelpIndexerFailed,
|
||||
'AppleHelpBuilder': AppleHelpBuilder,
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.setup_extension('sphinx.builders.html')
|
||||
app.add_builder(AppleHelpBuilder)
|
||||
|
||||
app.add_config_value('applehelp_bundle_name',
|
||||
lambda self: make_filename(self.project), 'applehelp')
|
||||
app.add_config_value('applehelp_bundle_id', None, 'applehelp', [str])
|
||||
app.add_config_value('applehelp_dev_region', 'en-us', 'applehelp')
|
||||
app.add_config_value('applehelp_bundle_version', '1', 'applehelp')
|
||||
app.add_config_value('applehelp_icon', None, 'applehelp', [str])
|
||||
app.add_config_value('applehelp_kb_product',
|
||||
lambda self: '%s-%s' % (make_filename(self.project), self.release),
|
||||
'applehelp')
|
||||
app.add_config_value('applehelp_kb_url', None, 'applehelp', [str])
|
||||
app.add_config_value('applehelp_remote_url', None, 'applehelp', [str])
|
||||
app.add_config_value('applehelp_index_anchors', False, 'applehelp', [str])
|
||||
app.add_config_value('applehelp_min_term_length', None, 'applehelp', [str])
|
||||
app.add_config_value('applehelp_stopwords',
|
||||
lambda self: self.language or 'en', 'applehelp')
|
||||
app.add_config_value('applehelp_locale', lambda self: self.language or 'en', 'applehelp')
|
||||
app.add_config_value('applehelp_title', lambda self: self.project + ' Help', 'applehelp')
|
||||
app.add_config_value('applehelp_codesign_identity',
|
||||
lambda self: environ.get('CODE_SIGN_IDENTITY', None),
|
||||
'applehelp')
|
||||
app.add_config_value('applehelp_codesign_flags',
|
||||
lambda self: shlex.split(environ.get('OTHER_CODE_SIGN_FLAGS', '')),
|
||||
'applehelp')
|
||||
app.add_config_value('applehelp_indexer_path', '/usr/bin/hiutil', 'applehelp')
|
||||
app.add_config_value('applehelp_codesign_path', '/usr/bin/codesign', 'applehelp')
|
||||
app.add_config_value('applehelp_disable_external_tools', False, None)
|
||||
warnings.warn('sphinx.builders.applehelp has been moved to sphinxcontrib-applehelp.',
|
||||
RemovedInSphinx40Warning)
|
||||
app.setup_extension('sphinxcontrib.applehelp')
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
|
||||
68
sphinx/builders/dirhtml.py
Normal file
68
sphinx/builders/dirhtml.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
sphinx.builders.dirhtml
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Directory HTML builders.
|
||||
|
||||
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from os import path
|
||||
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.osutil import SEP, os_path
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, Set # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
|
||||
"""
|
||||
A StandaloneHTMLBuilder that creates all HTML pages as "index.html" in
|
||||
a directory given by their pagename, so that generated URLs don't have
|
||||
``.html`` in them.
|
||||
"""
|
||||
name = 'dirhtml'
|
||||
|
||||
def get_target_uri(self, docname, typ=None):
|
||||
# type: (str, str) -> str
|
||||
if docname == 'index':
|
||||
return ''
|
||||
if docname.endswith(SEP + 'index'):
|
||||
return docname[:-5] # up to sep
|
||||
return docname + SEP
|
||||
|
||||
def get_outfilename(self, pagename):
|
||||
# type: (str) -> str
|
||||
if pagename == 'index' or pagename.endswith(SEP + 'index'):
|
||||
outfilename = path.join(self.outdir, os_path(pagename) +
|
||||
self.out_suffix)
|
||||
else:
|
||||
outfilename = path.join(self.outdir, os_path(pagename),
|
||||
'index' + self.out_suffix)
|
||||
|
||||
return outfilename
|
||||
|
||||
def prepare_writing(self, docnames):
|
||||
# type: (Set[str]) -> None
|
||||
super().prepare_writing(docnames)
|
||||
self.globalcontext['no_search_suffix'] = True
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.setup_extension('sphinx.builders.html')
|
||||
|
||||
app.add_builder(DirectoryHTMLBuilder)
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
@@ -269,7 +269,7 @@ def setup(app):
|
||||
app.add_config_value('epub_version', 3.0, 'epub') # experimental
|
||||
app.add_config_value('epub_theme', 'epub', 'epub')
|
||||
app.add_config_value('epub_theme_options', {}, 'epub')
|
||||
app.add_config_value('epub_title', lambda self: self.html_title, 'epub')
|
||||
app.add_config_value('epub_title', lambda self: self.project, 'epub')
|
||||
app.add_config_value('epub_author', lambda self: self.author, 'epub')
|
||||
app.add_config_value('epub_language', lambda self: self.language or 'en', 'epub')
|
||||
app.add_config_value('epub_publisher', lambda self: self.author, 'epub')
|
||||
|
||||
@@ -22,7 +22,7 @@ from sphinx.errors import ThemeError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import split_index_msg, logging, status_iterator
|
||||
from sphinx.util.console import bold # type: ignore
|
||||
from sphinx.util.i18n import find_catalog
|
||||
from sphinx.util.i18n import docname_to_domain
|
||||
from sphinx.util.nodes import extract_messages, traverse_translatable_index
|
||||
from sphinx.util.osutil import relpath, ensuredir, canon_path
|
||||
from sphinx.util.tags import Tags
|
||||
@@ -140,7 +140,7 @@ class I18nBuilder(Builder):
|
||||
|
||||
def write_doc(self, docname, doctree):
|
||||
# type: (str, nodes.document) -> None
|
||||
catalog = self.catalogs[find_catalog(docname, self.config.gettext_compact)]
|
||||
catalog = self.catalogs[docname_to_domain(docname, self.config.gettext_compact)]
|
||||
|
||||
for node, msg in extract_messages(doctree):
|
||||
catalog.add(msg, node)
|
||||
|
||||
@@ -9,16 +9,13 @@
|
||||
"""
|
||||
|
||||
import html
|
||||
import pickle
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
from hashlib import md5
|
||||
from os import path
|
||||
|
||||
import docutils
|
||||
from docutils import nodes
|
||||
from docutils.core import publish_parts
|
||||
from docutils.frontend import OptionParser
|
||||
@@ -26,7 +23,6 @@ from docutils.io import DocTreeInput, StringOutput
|
||||
from docutils.utils import relative_path
|
||||
|
||||
from sphinx import package_dir, __display_version__
|
||||
from sphinx.application import ENV_PICKLE_FILENAME
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.deprecation import (
|
||||
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
|
||||
@@ -39,15 +35,14 @@ from sphinx.highlighting import PygmentsBridge
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.search import js_index
|
||||
from sphinx.theming import HTMLThemeFactory
|
||||
from sphinx.util import jsonimpl, logging, status_iterator
|
||||
from sphinx.util import logging, status_iterator
|
||||
from sphinx.util.console import bold # type: ignore
|
||||
from sphinx.util.docutils import is_html5_writer_available, new_document
|
||||
from sphinx.util.fileutil import copy_asset
|
||||
from sphinx.util.i18n import format_date
|
||||
from sphinx.util.inventory import InventoryFile
|
||||
from sphinx.util.matching import patmatch, Matcher, DOTFILES
|
||||
from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
|
||||
movefile, copyfile
|
||||
from sphinx.util.osutil import os_path, relative_uri, ensuredir, movefile, copyfile
|
||||
from sphinx.writers.html import HTMLWriter, HTMLTranslator
|
||||
|
||||
if False:
|
||||
@@ -58,7 +53,7 @@ if False:
|
||||
from sphinx.domains import Domain, Index, IndexEntry # NOQA
|
||||
from sphinx.util.tags import Tags # NOQA
|
||||
|
||||
# Experimental HTML5 Writer
|
||||
# HTML5 Writer is avialable or not
|
||||
if is_html5_writer_available():
|
||||
from sphinx.writers.html5 import HTML5Translator
|
||||
html5_ready = True
|
||||
@@ -67,8 +62,6 @@ else:
|
||||
|
||||
#: the filename for the inventory of objects
|
||||
INVENTORY_FILENAME = 'objects.inv'
|
||||
#: the filename for the "last build" file (for serializing builders)
|
||||
LAST_BUILD_FILENAME = 'last_build'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
return_codes_re = re.compile('[\r\n]+')
|
||||
@@ -116,24 +109,24 @@ class JSContainer(list):
|
||||
"""The container for JavaScript scripts."""
|
||||
def insert(self, index, obj):
|
||||
# type: (int, str) -> None
|
||||
warnings.warn('builder.script_files is deprecated. '
|
||||
'Please use app.add_js_file() instead.',
|
||||
RemovedInSphinx30Warning, stacklevel=2)
|
||||
warnings.warn('To modify script_files in the theme is deprecated. '
|
||||
'Please insert a <script> tag directly in your theme instead.',
|
||||
RemovedInSphinx30Warning, stacklevel=3)
|
||||
super().insert(index, obj)
|
||||
|
||||
def extend(self, other): # type: ignore
|
||||
# type: (List[str]) -> None
|
||||
warnings.warn('builder.script_files is deprecated. '
|
||||
'Please use app.add_js_file() instead.',
|
||||
RemovedInSphinx30Warning, stacklevel=2)
|
||||
warnings.warn('To modify script_files in the theme is deprecated. '
|
||||
'Please insert a <script> tag directly in your theme instead.',
|
||||
RemovedInSphinx30Warning, stacklevel=3)
|
||||
for item in other:
|
||||
self.append(item)
|
||||
|
||||
def __iadd__(self, other): # type: ignore
|
||||
# type: (List[str]) -> JSContainer
|
||||
warnings.warn('builder.script_files is deprecated. '
|
||||
'Please use app.add_js_file() instead.',
|
||||
RemovedInSphinx30Warning, stacklevel=2)
|
||||
warnings.warn('To modify script_files in the theme is deprecated. '
|
||||
'Please insert a <script> tag directly in your theme instead.',
|
||||
RemovedInSphinx30Warning, stacklevel=3)
|
||||
for item in other:
|
||||
self.append(item)
|
||||
return self
|
||||
@@ -242,8 +235,6 @@ class StandaloneHTMLBuilder(Builder):
|
||||
search = True # for things like HTML help and Apple help: suppress search
|
||||
use_index = False
|
||||
download_support = True # enable download role
|
||||
# use html5 translator by default
|
||||
default_html5_translator = False
|
||||
|
||||
imgpath = None # type: str
|
||||
domain_indices = [] # type: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] # NOQA
|
||||
@@ -285,11 +276,6 @@ class StandaloneHTMLBuilder(Builder):
|
||||
|
||||
self.use_index = self.get_builder_config('use_index', 'html')
|
||||
|
||||
if self.config.html_experimental_html5_writer and not html5_ready:
|
||||
logger.warning(__('html_experimental_html5_writer is set, but current version '
|
||||
'is old. Docutils\' version should be 0.13 or newer, but %s.'),
|
||||
docutils.__version__)
|
||||
|
||||
def create_build_info(self):
|
||||
# type: () -> BuildInfo
|
||||
return BuildInfo(self.config, self.tags, ['html'])
|
||||
@@ -374,14 +360,10 @@ class StandaloneHTMLBuilder(Builder):
|
||||
@property
|
||||
def default_translator_class(self): # type: ignore
|
||||
# type: () -> Type[nodes.NodeVisitor]
|
||||
use_html5_writer = self.config.html_experimental_html5_writer
|
||||
if use_html5_writer is None:
|
||||
use_html5_writer = self.default_html5_translator
|
||||
|
||||
if use_html5_writer and html5_ready:
|
||||
return HTML5Translator
|
||||
else:
|
||||
if not html5_ready or self.config.html4_writer:
|
||||
return HTMLTranslator
|
||||
else:
|
||||
return HTML5Translator
|
||||
|
||||
@property
|
||||
def math_renderer_name(self):
|
||||
@@ -562,7 +544,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
'parents': [],
|
||||
'logo': logo,
|
||||
'favicon': favicon,
|
||||
'html5_doctype': self.config.html_experimental_html5_writer and html5_ready,
|
||||
'html5_doctype': html5_ready and not self.config.html4_writer
|
||||
}
|
||||
if self.theme:
|
||||
self.globalcontext.update(
|
||||
@@ -1169,173 +1151,6 @@ class StandaloneHTMLBuilder(Builder):
|
||||
logger.info(__('done'))
|
||||
|
||||
|
||||
class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
|
||||
"""
|
||||
A StandaloneHTMLBuilder that creates all HTML pages as "index.html" in
|
||||
a directory given by their pagename, so that generated URLs don't have
|
||||
``.html`` in them.
|
||||
"""
|
||||
name = 'dirhtml'
|
||||
|
||||
def get_target_uri(self, docname, typ=None):
|
||||
# type: (str, str) -> str
|
||||
if docname == 'index':
|
||||
return ''
|
||||
if docname.endswith(SEP + 'index'):
|
||||
return docname[:-5] # up to sep
|
||||
return docname + SEP
|
||||
|
||||
def get_outfilename(self, pagename):
|
||||
# type: (str) -> str
|
||||
if pagename == 'index' or pagename.endswith(SEP + 'index'):
|
||||
outfilename = path.join(self.outdir, os_path(pagename) +
|
||||
self.out_suffix)
|
||||
else:
|
||||
outfilename = path.join(self.outdir, os_path(pagename),
|
||||
'index' + self.out_suffix)
|
||||
|
||||
return outfilename
|
||||
|
||||
def prepare_writing(self, docnames):
|
||||
# type: (Set[str]) -> None
|
||||
super().prepare_writing(docnames)
|
||||
self.globalcontext['no_search_suffix'] = True
|
||||
|
||||
|
||||
class SerializingHTMLBuilder(StandaloneHTMLBuilder):
|
||||
"""
|
||||
An abstract builder that serializes the generated HTML.
|
||||
"""
|
||||
#: the serializing implementation to use. Set this to a module that
|
||||
#: implements a `dump`, `load`, `dumps` and `loads` functions
|
||||
#: (pickle, simplejson etc.)
|
||||
implementation = None # type: Any
|
||||
implementation_dumps_unicode = False
|
||||
#: additional arguments for dump()
|
||||
additional_dump_args = () # type: Tuple
|
||||
|
||||
#: the filename for the global context file
|
||||
globalcontext_filename = None # type: str
|
||||
|
||||
supported_image_types = ['image/svg+xml', 'image/png',
|
||||
'image/gif', 'image/jpeg']
|
||||
|
||||
def init(self):
|
||||
# type: () -> None
|
||||
self.build_info = BuildInfo(self.config, self.tags)
|
||||
self.imagedir = '_images'
|
||||
self.current_docname = None
|
||||
self.theme = None # no theme necessary
|
||||
self.templates = None # no template bridge necessary
|
||||
self.init_templates()
|
||||
self.init_highlighter()
|
||||
self.init_css_files()
|
||||
self.init_js_files()
|
||||
self.use_index = self.get_builder_config('use_index', 'html')
|
||||
|
||||
def get_target_uri(self, docname, typ=None):
|
||||
# type: (str, str) -> str
|
||||
if docname == 'index':
|
||||
return ''
|
||||
if docname.endswith(SEP + 'index'):
|
||||
return docname[:-5] # up to sep
|
||||
return docname + SEP
|
||||
|
||||
def dump_context(self, context, filename):
|
||||
# type: (Dict, str) -> None
|
||||
if self.implementation_dumps_unicode:
|
||||
with open(filename, 'w', encoding='utf-8') as ft:
|
||||
self.implementation.dump(context, ft, *self.additional_dump_args)
|
||||
else:
|
||||
with open(filename, 'wb') as fb:
|
||||
self.implementation.dump(context, fb, *self.additional_dump_args)
|
||||
|
||||
def handle_page(self, pagename, ctx, templatename='page.html',
|
||||
outfilename=None, event_arg=None):
|
||||
# type: (str, Dict, str, str, Any) -> None
|
||||
ctx['current_page_name'] = pagename
|
||||
self.add_sidebars(pagename, ctx)
|
||||
|
||||
if not outfilename:
|
||||
outfilename = path.join(self.outdir,
|
||||
os_path(pagename) + self.out_suffix)
|
||||
|
||||
# we're not taking the return value here, since no template is
|
||||
# actually rendered
|
||||
self.app.emit('html-page-context', pagename, templatename, ctx, event_arg)
|
||||
|
||||
# make context object serializable
|
||||
for key in list(ctx):
|
||||
if isinstance(ctx[key], types.FunctionType):
|
||||
del ctx[key]
|
||||
|
||||
ensuredir(path.dirname(outfilename))
|
||||
self.dump_context(ctx, outfilename)
|
||||
|
||||
# if there is a source file, copy the source file for the
|
||||
# "show source" link
|
||||
if ctx.get('sourcename'):
|
||||
source_name = path.join(self.outdir, '_sources',
|
||||
os_path(ctx['sourcename']))
|
||||
ensuredir(path.dirname(source_name))
|
||||
copyfile(self.env.doc2path(pagename), source_name)
|
||||
|
||||
def handle_finish(self):
|
||||
# type: () -> None
|
||||
# dump the global context
|
||||
outfilename = path.join(self.outdir, self.globalcontext_filename)
|
||||
self.dump_context(self.globalcontext, outfilename)
|
||||
|
||||
# super here to dump the search index
|
||||
super().handle_finish()
|
||||
|
||||
# copy the environment file from the doctree dir to the output dir
|
||||
# as needed by the web app
|
||||
copyfile(path.join(self.doctreedir, ENV_PICKLE_FILENAME),
|
||||
path.join(self.outdir, ENV_PICKLE_FILENAME))
|
||||
|
||||
# touch 'last build' file, used by the web application to determine
|
||||
# when to reload its environment and clear the cache
|
||||
open(path.join(self.outdir, LAST_BUILD_FILENAME), 'w').close()
|
||||
|
||||
|
||||
class PickleHTMLBuilder(SerializingHTMLBuilder):
|
||||
"""
|
||||
A Builder that dumps the generated HTML into pickle files.
|
||||
"""
|
||||
name = 'pickle'
|
||||
epilog = __('You can now process the pickle files in %(outdir)s.')
|
||||
|
||||
implementation = pickle
|
||||
implementation_dumps_unicode = False
|
||||
additional_dump_args = (pickle.HIGHEST_PROTOCOL,)
|
||||
indexer_format = pickle
|
||||
indexer_dumps_unicode = False
|
||||
out_suffix = '.fpickle'
|
||||
globalcontext_filename = 'globalcontext.pickle'
|
||||
searchindex_filename = 'searchindex.pickle'
|
||||
|
||||
|
||||
# compatibility alias
|
||||
WebHTMLBuilder = PickleHTMLBuilder
|
||||
|
||||
|
||||
class JSONHTMLBuilder(SerializingHTMLBuilder):
|
||||
"""
|
||||
A builder that dumps the generated HTML into JSON files.
|
||||
"""
|
||||
name = 'json'
|
||||
epilog = __('You can now process the JSON files in %(outdir)s.')
|
||||
|
||||
implementation = jsonimpl
|
||||
implementation_dumps_unicode = True
|
||||
indexer_format = jsonimpl
|
||||
indexer_dumps_unicode = True
|
||||
out_suffix = '.fjson'
|
||||
globalcontext_filename = 'globalcontext.json'
|
||||
searchindex_filename = 'searchindex.json'
|
||||
|
||||
|
||||
def convert_html_css_files(app, config):
|
||||
# type: (Sphinx, Config) -> None
|
||||
"""This converts string styled html_css_files to tuple styled one."""
|
||||
@@ -1417,11 +1232,21 @@ def validate_math_renderer(app):
|
||||
|
||||
|
||||
# for compatibility
|
||||
from sphinx.builders.dirhtml import DirectoryHTMLBuilder # NOQA
|
||||
from sphinx.builders.singlehtml import SingleFileHTMLBuilder # NOQA
|
||||
from sphinxcontrib.serializinghtml import ( # NOQA
|
||||
LAST_BUILD_FILENAME, JSONHTMLBuilder, PickleHTMLBuilder, SerializingHTMLBuilder
|
||||
)
|
||||
|
||||
deprecated_alias('sphinx.builders.html',
|
||||
{
|
||||
'LAST_BUILD_FILENAME': LAST_BUILD_FILENAME,
|
||||
'DirectoryHTMLBuilder': DirectoryHTMLBuilder,
|
||||
'JSONHTMLBuilder': JSONHTMLBuilder,
|
||||
'PickleHTMLBuilder': PickleHTMLBuilder,
|
||||
'SerializingHTMLBuilder': SerializingHTMLBuilder,
|
||||
'SingleFileHTMLBuilder': SingleFileHTMLBuilder,
|
||||
'WebHTMLBuilder': PickleHTMLBuilder,
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
@@ -1430,9 +1255,6 @@ def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
# builders
|
||||
app.add_builder(StandaloneHTMLBuilder)
|
||||
app.add_builder(DirectoryHTMLBuilder)
|
||||
app.add_builder(PickleHTMLBuilder)
|
||||
app.add_builder(JSONHTMLBuilder)
|
||||
|
||||
# config values
|
||||
app.add_config_value('html_theme', 'alabaster', 'html')
|
||||
@@ -1472,9 +1294,9 @@ def setup(app):
|
||||
app.add_config_value('html_search_options', {}, 'html')
|
||||
app.add_config_value('html_search_scorer', '', None)
|
||||
app.add_config_value('html_scaled_image_link', True, 'html')
|
||||
app.add_config_value('html_experimental_html5_writer', None, 'html')
|
||||
app.add_config_value('html_baseurl', '', 'html')
|
||||
app.add_config_value('html_math_renderer', None, 'env')
|
||||
app.add_config_value('html4_writer', False, 'html')
|
||||
|
||||
# event handlers
|
||||
app.connect('config-inited', convert_html_css_files)
|
||||
|
||||
@@ -9,364 +9,36 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import html
|
||||
import os
|
||||
import warnings
|
||||
from os import path
|
||||
|
||||
from docutils import nodes
|
||||
from sphinxcontrib.htmlhelp import (
|
||||
chm_locales, chm_htmlescape, HTMLHelpBuilder, default_htmlhelp_basename
|
||||
)
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.environment.adapters.indexentries import IndexEntries
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.nodes import NodeMatcher
|
||||
from sphinx.util.osutil import make_filename_from_project
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, IO, List, Match, Tuple # NOQA
|
||||
from typing import Any, Dict # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Project file (*.hhp) template. 'outname' is the file basename (like
|
||||
# the pythlp in pythlp.hhp); 'version' is the doc version number (like
|
||||
# the 2.2 in Python 2.2).
|
||||
# The magical numbers in the long line under [WINDOWS] set most of the
|
||||
# user-visible features (visible buttons, tabs, etc).
|
||||
# About 0x10384e: This defines the buttons in the help viewer. The
|
||||
# following defns are taken from htmlhelp.h. Not all possibilities
|
||||
# actually work, and not all those that work are available from the Help
|
||||
# Workshop GUI. In particular, the Zoom/Font button works and is not
|
||||
# available from the GUI. The ones we're using are marked with 'x':
|
||||
#
|
||||
# 0x000002 Hide/Show x
|
||||
# 0x000004 Back x
|
||||
# 0x000008 Forward x
|
||||
# 0x000010 Stop
|
||||
# 0x000020 Refresh
|
||||
# 0x000040 Home x
|
||||
# 0x000080 Forward
|
||||
# 0x000100 Back
|
||||
# 0x000200 Notes
|
||||
# 0x000400 Contents
|
||||
# 0x000800 Locate x
|
||||
# 0x001000 Options x
|
||||
# 0x002000 Print x
|
||||
# 0x004000 Index
|
||||
# 0x008000 Search
|
||||
# 0x010000 History
|
||||
# 0x020000 Favorites
|
||||
# 0x040000 Jump 1
|
||||
# 0x080000 Jump 2
|
||||
# 0x100000 Zoom/Font x
|
||||
# 0x200000 TOC Next
|
||||
# 0x400000 TOC Prev
|
||||
|
||||
project_template = '''\
|
||||
[OPTIONS]
|
||||
Binary TOC=No
|
||||
Binary Index=No
|
||||
Compiled file=%(outname)s.chm
|
||||
Contents file=%(outname)s.hhc
|
||||
Default Window=%(outname)s
|
||||
Default topic=%(master_doc)s
|
||||
Display compile progress=No
|
||||
Full text search stop list file=%(outname)s.stp
|
||||
Full-text search=Yes
|
||||
Index file=%(outname)s.hhk
|
||||
Language=%(lcid)#x
|
||||
Title=%(title)s
|
||||
|
||||
[WINDOWS]
|
||||
%(outname)s="%(title)s","%(outname)s.hhc","%(outname)s.hhk",\
|
||||
"%(master_doc)s","%(master_doc)s",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0
|
||||
|
||||
[FILES]
|
||||
'''
|
||||
|
||||
contents_header = '''\
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1">
|
||||
<!-- Sitemap 1.0 -->
|
||||
</HEAD><BODY>
|
||||
<OBJECT type="text/site properties">
|
||||
<param name="Window Styles" value="0x801227">
|
||||
<param name="ImageType" value="Folder">
|
||||
</OBJECT>
|
||||
<UL>
|
||||
'''
|
||||
|
||||
contents_footer = '''\
|
||||
</UL></BODY></HTML>
|
||||
'''
|
||||
|
||||
object_sitemap = '''\
|
||||
<OBJECT type="text/sitemap">
|
||||
<param name="Name" value="%s">
|
||||
<param name="Local" value="%s">
|
||||
</OBJECT>
|
||||
'''
|
||||
|
||||
# List of words the full text search facility shouldn't index. This
|
||||
# becomes file outname.stp. Note that this list must be pretty small!
|
||||
# Different versions of the MS docs claim the file has a maximum size of
|
||||
# 256 or 512 bytes (including \r\n at the end of each line).
|
||||
# Note that "and", "or", "not" and "near" are operators in the search
|
||||
# language, so no point indexing them even if we wanted to.
|
||||
stopwords = """
|
||||
a and are as at
|
||||
be but by
|
||||
for
|
||||
if in into is it
|
||||
near no not
|
||||
of on or
|
||||
such
|
||||
that the their then there these they this to
|
||||
was will with
|
||||
""".split()
|
||||
|
||||
# The following list includes only languages supported by Sphinx. See
|
||||
# https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms930130(v=msdn.10)
|
||||
# for more.
|
||||
chm_locales = {
|
||||
# lang: LCID, encoding
|
||||
'ca': (0x403, 'cp1252'),
|
||||
'cs': (0x405, 'cp1250'),
|
||||
'da': (0x406, 'cp1252'),
|
||||
'de': (0x407, 'cp1252'),
|
||||
'en': (0x409, 'cp1252'),
|
||||
'es': (0x40a, 'cp1252'),
|
||||
'et': (0x425, 'cp1257'),
|
||||
'fa': (0x429, 'cp1256'),
|
||||
'fi': (0x40b, 'cp1252'),
|
||||
'fr': (0x40c, 'cp1252'),
|
||||
'hr': (0x41a, 'cp1250'),
|
||||
'hu': (0x40e, 'cp1250'),
|
||||
'it': (0x410, 'cp1252'),
|
||||
'ja': (0x411, 'cp932'),
|
||||
'ko': (0x412, 'cp949'),
|
||||
'lt': (0x427, 'cp1257'),
|
||||
'lv': (0x426, 'cp1257'),
|
||||
'nl': (0x413, 'cp1252'),
|
||||
'no_NB': (0x414, 'cp1252'),
|
||||
'pl': (0x415, 'cp1250'),
|
||||
'pt_BR': (0x416, 'cp1252'),
|
||||
'ru': (0x419, 'cp1251'),
|
||||
'sk': (0x41b, 'cp1250'),
|
||||
'sl': (0x424, 'cp1250'),
|
||||
'sv': (0x41d, 'cp1252'),
|
||||
'tr': (0x41f, 'cp1254'),
|
||||
'uk_UA': (0x422, 'cp1251'),
|
||||
'zh_CN': (0x804, 'cp936'),
|
||||
'zh_TW': (0x404, 'cp950'),
|
||||
}
|
||||
|
||||
|
||||
def chm_htmlescape(s, quote=True):
|
||||
# type: (str, bool) -> str
|
||||
"""
|
||||
chm_htmlescape() is a wrapper of html.escape().
|
||||
.hhc/.hhk files don't recognize hex escaping, we need convert
|
||||
hex escaping to decimal escaping. for example: ``'`` -> ``'``
|
||||
html.escape() may generates a hex escaping ``'`` for single
|
||||
quote ``'``, this wrapper fixes this.
|
||||
"""
|
||||
s = html.escape(s, quote)
|
||||
s = s.replace(''', ''') # re-escape as decimal
|
||||
return s
|
||||
|
||||
|
||||
class HTMLHelpBuilder(StandaloneHTMLBuilder):
|
||||
"""
|
||||
Builder that also outputs Windows HTML help project, contents and
|
||||
index files. Adapted from the original Doc/tools/prechm.py.
|
||||
"""
|
||||
name = 'htmlhelp'
|
||||
epilog = __('You can now run HTML Help Workshop with the .htp file in '
|
||||
'%(outdir)s.')
|
||||
|
||||
# don't copy the reST source
|
||||
copysource = False
|
||||
supported_image_types = ['image/png', 'image/gif', 'image/jpeg']
|
||||
|
||||
# don't add links
|
||||
add_permalinks = False
|
||||
# don't add sidebar etc.
|
||||
embedded = True
|
||||
|
||||
# don't generate search index or include search page
|
||||
search = False
|
||||
|
||||
lcid = 0x409
|
||||
encoding = 'cp1252'
|
||||
|
||||
def init(self):
|
||||
# type: () -> None
|
||||
# the output files for HTML help is .html by default
|
||||
self.out_suffix = '.html'
|
||||
self.link_suffix = '.html'
|
||||
super().init()
|
||||
# determine the correct locale setting
|
||||
locale = chm_locales.get(self.config.language)
|
||||
if locale is not None:
|
||||
self.lcid, self.encoding = locale
|
||||
|
||||
def open_file(self, outdir, basename, mode='w'):
|
||||
# type: (str, str, str) -> IO
|
||||
# open a file with the correct encoding for the selected language
|
||||
warnings.warn('HTMLHelpBuilder.open_file() is deprecated.',
|
||||
RemovedInSphinx40Warning)
|
||||
return open(path.join(outdir, basename), mode, encoding=self.encoding,
|
||||
errors='xmlcharrefreplace')
|
||||
|
||||
def update_page_context(self, pagename, templatename, ctx, event_arg):
|
||||
# type: (str, str, Dict, str) -> None
|
||||
ctx['encoding'] = self.encoding
|
||||
|
||||
def handle_finish(self):
|
||||
# type: () -> None
|
||||
self.build_hhx(self.outdir, self.config.htmlhelp_basename)
|
||||
|
||||
def write_doc(self, docname, doctree):
|
||||
# type: (str, nodes.document) -> None
|
||||
for node in doctree.traverse(nodes.reference):
|
||||
# add ``target=_blank`` attributes to external links
|
||||
if node.get('internal') is None and 'refuri' in node:
|
||||
node['target'] = '_blank'
|
||||
|
||||
super().write_doc(docname, doctree)
|
||||
|
||||
def build_hhx(self, outdir, outname):
|
||||
# type: (str, str) -> None
|
||||
logger.info(__('dumping stopword list...'))
|
||||
filename = path.join(outdir, outname + '.stp')
|
||||
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
|
||||
for word in sorted(stopwords):
|
||||
print(word, file=f)
|
||||
|
||||
logger.info(__('writing project file...'))
|
||||
filename = path.join(outdir, outname + '.hhp')
|
||||
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
|
||||
f.write(project_template % {
|
||||
'outname': outname,
|
||||
'title': self.config.html_title,
|
||||
'version': self.config.version,
|
||||
'project': self.config.project,
|
||||
'lcid': self.lcid,
|
||||
'master_doc': self.config.master_doc + self.out_suffix
|
||||
})
|
||||
if not outdir.endswith(os.sep):
|
||||
outdir += os.sep
|
||||
olen = len(outdir)
|
||||
for root, dirs, files in os.walk(outdir):
|
||||
dirs.sort()
|
||||
files.sort()
|
||||
staticdir = root.startswith(path.join(outdir, '_static'))
|
||||
for fn in sorted(files):
|
||||
if (staticdir and not fn.endswith('.js')) or \
|
||||
fn.endswith('.html'):
|
||||
print(path.join(root, fn)[olen:].replace(os.sep, '\\'),
|
||||
file=f)
|
||||
|
||||
logger.info(__('writing TOC file...'))
|
||||
filename = path.join(outdir, outname + '.hhc')
|
||||
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
|
||||
f.write(contents_header)
|
||||
# special books
|
||||
f.write('<LI> ' + object_sitemap % (self.config.html_short_title,
|
||||
self.config.master_doc + self.out_suffix))
|
||||
for indexname, indexcls, content, collapse in self.domain_indices:
|
||||
f.write('<LI> ' + object_sitemap % (indexcls.localname,
|
||||
'%s.html' % indexname))
|
||||
# the TOC
|
||||
tocdoc = self.env.get_and_resolve_doctree(
|
||||
self.config.master_doc, self, prune_toctrees=False)
|
||||
|
||||
def write_toc(node, ullevel=0):
|
||||
# type: (nodes.Node, int) -> None
|
||||
if isinstance(node, nodes.list_item):
|
||||
f.write('<LI> ')
|
||||
for subnode in node:
|
||||
write_toc(subnode, ullevel)
|
||||
elif isinstance(node, nodes.reference):
|
||||
link = node['refuri']
|
||||
title = chm_htmlescape(node.astext(), True)
|
||||
f.write(object_sitemap % (title, link))
|
||||
elif isinstance(node, nodes.bullet_list):
|
||||
if ullevel != 0:
|
||||
f.write('<UL>\n')
|
||||
for subnode in node:
|
||||
write_toc(subnode, ullevel + 1)
|
||||
if ullevel != 0:
|
||||
f.write('</UL>\n')
|
||||
elif isinstance(node, addnodes.compact_paragraph):
|
||||
for subnode in node:
|
||||
write_toc(subnode, ullevel)
|
||||
|
||||
matcher = NodeMatcher(addnodes.compact_paragraph, toctree=True)
|
||||
for node in tocdoc.traverse(matcher): # type: addnodes.compact_paragraph
|
||||
write_toc(node)
|
||||
f.write(contents_footer)
|
||||
|
||||
logger.info(__('writing index file...'))
|
||||
index = IndexEntries(self.env).create_index(self)
|
||||
filename = path.join(outdir, outname + '.hhk')
|
||||
with open(filename, 'w', encoding=self.encoding, errors='xmlcharrefreplace') as f:
|
||||
f.write('<UL>\n')
|
||||
|
||||
def write_index(title, refs, subitems):
|
||||
# type: (str, List[Tuple[str, str]], List[Tuple[str, List[Tuple[str, str]]]]) -> None # NOQA
|
||||
def write_param(name, value):
|
||||
# type: (str, str) -> None
|
||||
item = ' <param name="%s" value="%s">\n' % (name, value)
|
||||
f.write(item)
|
||||
title = chm_htmlescape(title, True)
|
||||
f.write('<LI> <OBJECT type="text/sitemap">\n')
|
||||
write_param('Keyword', title)
|
||||
if len(refs) == 0:
|
||||
write_param('See Also', title)
|
||||
elif len(refs) == 1:
|
||||
write_param('Local', refs[0][1])
|
||||
else:
|
||||
for i, ref in enumerate(refs):
|
||||
# XXX: better title?
|
||||
write_param('Name', '[%d] %s' % (i, ref[1]))
|
||||
write_param('Local', ref[1])
|
||||
f.write('</OBJECT>\n')
|
||||
if subitems:
|
||||
f.write('<UL> ')
|
||||
for subitem in subitems:
|
||||
write_index(subitem[0], subitem[1], [])
|
||||
f.write('</UL>')
|
||||
for (key, group) in index:
|
||||
for title, (refs, subitems, key_) in group:
|
||||
write_index(title, refs, subitems)
|
||||
f.write('</UL>\n')
|
||||
|
||||
|
||||
def default_htmlhelp_basename(config):
|
||||
# type: (Config) -> str
|
||||
"""Better default htmlhelp_basename setting."""
|
||||
return make_filename_from_project(config.project) + 'doc'
|
||||
deprecated_alias('sphinx.builders.devhelp',
|
||||
{
|
||||
'chm_locales': chm_locales,
|
||||
'chm_htmlescape': chm_htmlescape,
|
||||
'HTMLHelpBuilder': HTMLHelpBuilder,
|
||||
'default_htmlhelp_basename': default_htmlhelp_basename,
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.setup_extension('sphinx.builders.html')
|
||||
app.add_builder(HTMLHelpBuilder)
|
||||
|
||||
app.add_config_value('htmlhelp_basename', default_htmlhelp_basename, None)
|
||||
app.add_config_value('htmlhelp_file_suffix', None, 'html', [str])
|
||||
app.add_config_value('htmlhelp_link_suffix', None, 'html', [str])
|
||||
warnings.warn('sphinx.builders.htmlhelp has been moved to sphinxcontrib-htmlhelp.',
|
||||
RemovedInSphinx40Warning)
|
||||
app.setup_extension('sphinxcontrib.htmlhelp')
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
|
||||
@@ -28,7 +28,7 @@ from sphinx.environment.adapters.asset import ImageAdapter
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.transforms import SphinxTransformer
|
||||
from sphinx.util import texescape, logging, status_iterator
|
||||
from sphinx.util import texescape, logging, progress_message, status_iterator
|
||||
from sphinx.util.console import bold, darkgreen # type: ignore
|
||||
from sphinx.util.docutils import SphinxFileOutput, new_document
|
||||
from sphinx.util.fileutil import copy_asset_file
|
||||
@@ -250,33 +250,32 @@ class LaTeXBuilder(Builder):
|
||||
toctree_only = entry[5]
|
||||
destination = SphinxFileOutput(destination_path=path.join(self.outdir, targetname),
|
||||
encoding='utf-8', overwrite_if_changed=True)
|
||||
logger.info(__("processing %s..."), targetname, nonl=True)
|
||||
toctrees = self.env.get_doctree(docname).traverse(addnodes.toctree)
|
||||
if toctrees:
|
||||
if toctrees[0].get('maxdepth') > 0:
|
||||
tocdepth = toctrees[0].get('maxdepth')
|
||||
with progress_message(__("processing %s") % targetname):
|
||||
toctrees = self.env.get_doctree(docname).traverse(addnodes.toctree)
|
||||
if toctrees:
|
||||
if toctrees[0].get('maxdepth') > 0:
|
||||
tocdepth = toctrees[0].get('maxdepth')
|
||||
else:
|
||||
tocdepth = None
|
||||
else:
|
||||
tocdepth = None
|
||||
else:
|
||||
tocdepth = None
|
||||
doctree = self.assemble_doctree(
|
||||
docname, toctree_only,
|
||||
appendices=((docclass != 'howto') and self.config.latex_appendices or []))
|
||||
doctree['tocdepth'] = tocdepth
|
||||
self.apply_transforms(doctree)
|
||||
self.post_process_images(doctree)
|
||||
self.update_doc_context(title, author)
|
||||
doctree = self.assemble_doctree(
|
||||
docname, toctree_only,
|
||||
appendices=((docclass != 'howto') and self.config.latex_appendices or []))
|
||||
doctree['tocdepth'] = tocdepth
|
||||
self.apply_transforms(doctree)
|
||||
self.post_process_images(doctree)
|
||||
self.update_doc_context(title, author)
|
||||
|
||||
logger.info(__("writing... "), nonl=True)
|
||||
docsettings.author = author
|
||||
docsettings.title = title
|
||||
docsettings.contentsname = self.get_contentsname(docname)
|
||||
docsettings.docname = docname
|
||||
docsettings.docclass = docclass
|
||||
with progress_message(__("writing")):
|
||||
docsettings.author = author
|
||||
docsettings.title = title
|
||||
docsettings.contentsname = self.get_contentsname(docname)
|
||||
docsettings.docname = docname
|
||||
docsettings.docclass = docclass
|
||||
|
||||
doctree.settings = docsettings
|
||||
docwriter.write(doctree, destination)
|
||||
logger.info(__("done"))
|
||||
doctree.settings = docsettings
|
||||
docwriter.write(doctree, destination)
|
||||
|
||||
def get_contentsname(self, indexfile):
|
||||
# type: (str) -> str
|
||||
@@ -354,8 +353,15 @@ class LaTeXBuilder(Builder):
|
||||
# type: () -> None
|
||||
self.copy_image_files()
|
||||
self.write_message_catalog()
|
||||
self.copy_support_files()
|
||||
|
||||
# copy TeX support files from texinputs
|
||||
if self.config.latex_additional_files:
|
||||
self.copy_latex_additional_files()
|
||||
|
||||
@progress_message(__('copying TeX support files'))
|
||||
def copy_support_files(self):
|
||||
# type: () -> None
|
||||
"""copy TeX support files from texinputs."""
|
||||
# configure usage of xindy (impacts Makefile and latexmkrc)
|
||||
# FIXME: convert this rather to a confval with suitable default
|
||||
# according to language ? but would require extra documentation
|
||||
@@ -386,21 +392,19 @@ class LaTeXBuilder(Builder):
|
||||
copy_asset_file(path.join(staticdirname, 'Makefile_t'),
|
||||
self.outdir, context=context)
|
||||
|
||||
# copy additional files
|
||||
if self.config.latex_additional_files:
|
||||
logger.info(bold(__('copying additional files...')), nonl=True)
|
||||
for filename in self.config.latex_additional_files:
|
||||
logger.info(' ' + filename, nonl=True)
|
||||
copy_asset_file(path.join(self.confdir, filename), self.outdir)
|
||||
logger.info('')
|
||||
|
||||
# the logo is handled differently
|
||||
if self.config.latex_logo:
|
||||
if not path.isfile(path.join(self.confdir, self.config.latex_logo)):
|
||||
raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo)
|
||||
else:
|
||||
copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir)
|
||||
logger.info(__('done'))
|
||||
|
||||
@progress_message(__('copying additional files'))
|
||||
def copy_latex_additional_files(self):
|
||||
# type: () -> None
|
||||
for filename in self.config.latex_additional_files:
|
||||
logger.info(' ' + filename, nonl=True)
|
||||
copy_asset_file(path.join(self.confdir, filename), self.outdir)
|
||||
|
||||
def copy_image_files(self):
|
||||
# type: () -> None
|
||||
@@ -439,7 +443,7 @@ def validate_config_values(app, config):
|
||||
for key in list(config.latex_elements):
|
||||
if key not in DEFAULT_SETTINGS:
|
||||
msg = __("Unknown configure key: latex_elements[%r]. ignored.")
|
||||
logger.warning(msg % key)
|
||||
logger.warning(msg % (key,))
|
||||
config.latex_elements.pop(key)
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from sphinx.util import encode_uri, requests, logging
|
||||
from sphinx.util.console import ( # type: ignore
|
||||
purple, red, darkgreen, darkgray, darkred, turquoise
|
||||
)
|
||||
from sphinx.util.nodes import traverse_parent
|
||||
from sphinx.util.nodes import get_node_line
|
||||
from sphinx.util.requests import is_ssl_error
|
||||
|
||||
if False:
|
||||
@@ -271,17 +271,24 @@ class CheckExternalLinksBuilder(Builder):
|
||||
# type: (str, nodes.Node) -> None
|
||||
logger.info('')
|
||||
n = 0
|
||||
for node in doctree.traverse(nodes.reference):
|
||||
if 'refuri' not in node:
|
||||
|
||||
# reference nodes
|
||||
for refnode in doctree.traverse(nodes.reference):
|
||||
if 'refuri' not in refnode:
|
||||
continue
|
||||
uri = node['refuri']
|
||||
lineno = None
|
||||
for parent in traverse_parent(node):
|
||||
if parent.line:
|
||||
lineno = parent.line
|
||||
break
|
||||
uri = refnode['refuri']
|
||||
lineno = get_node_line(refnode)
|
||||
self.wqueue.put((uri, docname, lineno), False)
|
||||
n += 1
|
||||
|
||||
# image nodes
|
||||
for imgnode in doctree.traverse(nodes.image):
|
||||
uri = imgnode['candidates'].get('?')
|
||||
if uri and '://' in uri:
|
||||
lineno = get_node_line(imgnode)
|
||||
self.wqueue.put((uri, docname, lineno), False)
|
||||
n += 1
|
||||
|
||||
done = 0
|
||||
while done < n:
|
||||
self.process_result(self.rqueue.get())
|
||||
|
||||
@@ -18,7 +18,8 @@ from sphinx.builders import Builder
|
||||
from sphinx.environment import NoUri
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.console import bold, darkgreen # type: ignore
|
||||
from sphinx.util import progress_message
|
||||
from sphinx.util.console import darkgreen # type: ignore
|
||||
from sphinx.util.nodes import inline_all_toctrees
|
||||
from sphinx.util.osutil import make_filename_from_project
|
||||
from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator
|
||||
@@ -60,6 +61,7 @@ class ManualPageBuilder(Builder):
|
||||
return ''
|
||||
raise NoUri
|
||||
|
||||
@progress_message(__('writing'))
|
||||
def write(self, *ignored):
|
||||
# type: (Any) -> None
|
||||
docwriter = ManualPageWriter(self)
|
||||
@@ -68,8 +70,6 @@ class ManualPageBuilder(Builder):
|
||||
components=(docwriter,),
|
||||
read_config_files=True).get_default_values() # type: Any
|
||||
|
||||
logger.info(bold(__('writing... ')), nonl=True)
|
||||
|
||||
for info in self.config.man_pages:
|
||||
docname, name, description, authors, section = info
|
||||
if docname not in self.env.all_docs:
|
||||
@@ -105,7 +105,6 @@ class ManualPageBuilder(Builder):
|
||||
pendingnode.replace_self(pendingnode.children)
|
||||
|
||||
docwriter.write(largetree, destination)
|
||||
logger.info('')
|
||||
|
||||
def finish(self):
|
||||
# type: () -> None
|
||||
|
||||
@@ -16,7 +16,8 @@ from sphinx.builders.html import StandaloneHTMLBuilder
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.console import bold, darkgreen # type: ignore
|
||||
from sphinx.util import progress_message
|
||||
from sphinx.util.console import darkgreen # type: ignore
|
||||
from sphinx.util.nodes import inline_all_toctrees
|
||||
|
||||
if False:
|
||||
@@ -162,24 +163,32 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
|
||||
# type: (Any) -> None
|
||||
docnames = self.env.all_docs
|
||||
|
||||
logger.info(bold(__('preparing documents... ')), nonl=True)
|
||||
self.prepare_writing(docnames) # type: ignore
|
||||
logger.info(__('done'))
|
||||
with progress_message(__('preparing documents')):
|
||||
self.prepare_writing(docnames) # type: ignore
|
||||
|
||||
logger.info(bold(__('assembling single document... ')), nonl=True)
|
||||
doctree = self.assemble_doctree()
|
||||
self.env.toc_secnumbers = self.assemble_toc_secnumbers()
|
||||
self.env.toc_fignumbers = self.assemble_toc_fignumbers()
|
||||
logger.info('')
|
||||
logger.info(bold(__('writing... ')), nonl=True)
|
||||
self.write_doc_serialized(self.config.master_doc, doctree)
|
||||
self.write_doc(self.config.master_doc, doctree)
|
||||
logger.info(__('done'))
|
||||
with progress_message(__('assembling single document')):
|
||||
doctree = self.assemble_doctree()
|
||||
self.env.toc_secnumbers = self.assemble_toc_secnumbers()
|
||||
self.env.toc_fignumbers = self.assemble_toc_fignumbers()
|
||||
|
||||
with progress_message(__('writing')):
|
||||
self.write_doc_serialized(self.config.master_doc, doctree)
|
||||
self.write_doc(self.config.master_doc, doctree)
|
||||
|
||||
def finish(self):
|
||||
# type: () -> None
|
||||
self.write_additional_files()
|
||||
self.copy_image_files()
|
||||
self.copy_download_files()
|
||||
self.copy_static_files()
|
||||
self.copy_extra_files()
|
||||
self.write_buildinfo()
|
||||
self.dump_inventory()
|
||||
|
||||
@progress_message(__('writing additional files'))
|
||||
def write_additional_files(self):
|
||||
# type: () -> None
|
||||
# no indices or search pages are supported
|
||||
logger.info(bold(__('writing additional files...')), nonl=True)
|
||||
|
||||
# additional pages from conf.py
|
||||
for pagename, template in self.config.html_additional_pages.items():
|
||||
@@ -191,15 +200,6 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
|
||||
fn = path.join(self.outdir, '_static', 'opensearch.xml')
|
||||
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
|
||||
|
||||
logger.info('')
|
||||
|
||||
self.copy_image_files()
|
||||
self.copy_download_files()
|
||||
self.copy_static_files()
|
||||
self.copy_extra_files()
|
||||
self.write_buildinfo()
|
||||
self.dump_inventory()
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
|
||||
@@ -22,8 +22,8 @@ from sphinx.environment import NoUri
|
||||
from sphinx.environment.adapters.asset import ImageAdapter
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util import status_iterator
|
||||
from sphinx.util.console import bold, darkgreen # type: ignore
|
||||
from sphinx.util import progress_message, status_iterator
|
||||
from sphinx.util.console import darkgreen # type: ignore
|
||||
from sphinx.util.docutils import new_document
|
||||
from sphinx.util.fileutil import copy_asset_file
|
||||
from sphinx.util.nodes import inline_all_toctrees
|
||||
@@ -113,28 +113,27 @@ class TexinfoBuilder(Builder):
|
||||
destination = FileOutput(
|
||||
destination_path=path.join(self.outdir, targetname),
|
||||
encoding='utf-8')
|
||||
logger.info(__("processing %s..."), targetname, nonl=True)
|
||||
doctree = self.assemble_doctree(
|
||||
docname, toctree_only,
|
||||
appendices=(self.config.texinfo_appendices or []))
|
||||
logger.info(__("writing... "), nonl=True)
|
||||
self.post_process_images(doctree)
|
||||
docwriter = TexinfoWriter(self)
|
||||
settings = OptionParser(
|
||||
defaults=self.env.settings,
|
||||
components=(docwriter,),
|
||||
read_config_files=True).get_default_values() # type: Any
|
||||
settings.author = author
|
||||
settings.title = title
|
||||
settings.texinfo_filename = targetname[:-5] + '.info'
|
||||
settings.texinfo_elements = self.config.texinfo_elements
|
||||
settings.texinfo_dir_entry = direntry or ''
|
||||
settings.texinfo_dir_category = category or ''
|
||||
settings.texinfo_dir_description = description or ''
|
||||
settings.docname = docname
|
||||
doctree.settings = settings
|
||||
docwriter.write(doctree, destination)
|
||||
logger.info(__("done"))
|
||||
with progress_message(__("processing %s") % targetname):
|
||||
appendices = self.config.texinfo_appendices or []
|
||||
doctree = self.assemble_doctree(docname, toctree_only, appendices=appendices)
|
||||
|
||||
with progress_message(__("writing")):
|
||||
self.post_process_images(doctree)
|
||||
docwriter = TexinfoWriter(self)
|
||||
settings = OptionParser(
|
||||
defaults=self.env.settings,
|
||||
components=(docwriter,),
|
||||
read_config_files=True).get_default_values() # type: Any
|
||||
settings.author = author
|
||||
settings.title = title
|
||||
settings.texinfo_filename = targetname[:-5] + '.info'
|
||||
settings.texinfo_elements = self.config.texinfo_elements
|
||||
settings.texinfo_dir_entry = direntry or ''
|
||||
settings.texinfo_dir_category = category or ''
|
||||
settings.texinfo_dir_description = description or ''
|
||||
settings.docname = docname
|
||||
doctree.settings = settings
|
||||
docwriter.write(doctree, destination)
|
||||
|
||||
def assemble_doctree(self, indexfile, toctree_only, appendices):
|
||||
# type: (str, bool, List[str]) -> nodes.document
|
||||
@@ -182,16 +181,7 @@ class TexinfoBuilder(Builder):
|
||||
def finish(self):
|
||||
# type: () -> None
|
||||
self.copy_image_files()
|
||||
|
||||
logger.info(bold(__('copying Texinfo support files... ')), nonl=True)
|
||||
# copy Makefile
|
||||
fn = path.join(self.outdir, 'Makefile')
|
||||
logger.info(fn, nonl=True)
|
||||
try:
|
||||
copy_asset_file(os.path.join(template_dir, 'Makefile'), fn)
|
||||
except OSError as err:
|
||||
logger.warning(__("error writing file %s: %s"), fn, err)
|
||||
logger.info(__(' done'))
|
||||
self.copy_support_files()
|
||||
|
||||
def copy_image_files(self):
|
||||
# type: () -> None
|
||||
@@ -208,6 +198,15 @@ class TexinfoBuilder(Builder):
|
||||
logger.warning(__('cannot copy image file %r: %s'),
|
||||
path.join(self.srcdir, src), err)
|
||||
|
||||
def copy_support_files(self):
|
||||
# type: () -> None
|
||||
try:
|
||||
with progress_message(__('copying Texinfo support files')):
|
||||
logger.info('Makefile ', nonl=True)
|
||||
copy_asset_file(os.path.join(template_dir, 'Makefile'), self.outdir)
|
||||
except OSError as err:
|
||||
logger.warning(__("error writing file Makefile: %s"), err)
|
||||
|
||||
|
||||
def default_texinfo_documents(config):
|
||||
# type: (Config) -> List[Tuple[str, str, str, str, str, str, str]]
|
||||
|
||||
@@ -23,9 +23,8 @@ from sphinx.application import Sphinx
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import Tee, format_exception_cut_frames, save_traceback
|
||||
from sphinx.util.console import red, nocolor, color_terminal # type: ignore
|
||||
from sphinx.util.console import red, nocolor, color_terminal, terminal_safe # type: ignore
|
||||
from sphinx.util.docutils import docutils_namespace, patch_docutils
|
||||
from sphinx.util.pycompat import terminal_safe
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
|
||||
@@ -17,7 +17,6 @@ import time
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
from os import path
|
||||
from urllib.parse import quote
|
||||
|
||||
# try to import readline, unix specific enhancement
|
||||
try:
|
||||
@@ -37,11 +36,10 @@ import sphinx.locale
|
||||
from sphinx import __display_version__, package_dir
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import texescape
|
||||
from sphinx.util.console import ( # type: ignore
|
||||
colorize, bold, red, turquoise, nocolor, color_terminal
|
||||
)
|
||||
from sphinx.util.osutil import ensuredir, make_filename
|
||||
from sphinx.util.osutil import ensuredir
|
||||
from sphinx.util.template import SphinxRenderer
|
||||
|
||||
if False:
|
||||
@@ -375,25 +373,15 @@ def generate(d, overwrite=True, silent=False, templatedir=None):
|
||||
"""Generate project based on values in *d*."""
|
||||
template = QuickstartRenderer(templatedir=templatedir)
|
||||
|
||||
texescape.init()
|
||||
|
||||
if 'mastertoctree' not in d:
|
||||
d['mastertoctree'] = ''
|
||||
if 'mastertocmaxdepth' not in d:
|
||||
d['mastertocmaxdepth'] = 2
|
||||
|
||||
d['PY3'] = True
|
||||
d['project_fn'] = make_filename(d['project'])
|
||||
d['project_url'] = quote(d['project'].encode('idna'))
|
||||
d['project_manpage'] = d['project_fn'].lower()
|
||||
d['now'] = time.asctime()
|
||||
d['project_underline'] = column_width(d['project']) * '='
|
||||
d.setdefault('extensions', [])
|
||||
d['copyright'] = time.strftime('%Y') + ', ' + d['author']
|
||||
d['author_texescaped'] = d['author'].translate(texescape.tex_escape_map)
|
||||
d['project_doc'] = d['project'] + ' Documentation'
|
||||
d['project_doc_texescaped'] = (d['project'] + ' Documentation').\
|
||||
translate(texescape.tex_escape_map)
|
||||
|
||||
ensuredir(d['path'])
|
||||
|
||||
@@ -528,7 +516,7 @@ Makefile to be used with sphinx-build.
|
||||
group = parser.add_argument_group(__('Structure options'))
|
||||
group.add_argument('--sep', action='store_true', default=None,
|
||||
help=__('if specified, separate source and build dirs'))
|
||||
group.add_argument('--dot', metavar='DOT',
|
||||
group.add_argument('--dot', metavar='DOT', default='_',
|
||||
help=__('replacement for dot in _templates etc.'))
|
||||
|
||||
group = parser.add_argument_group(__('Project basic options'))
|
||||
@@ -542,9 +530,9 @@ Makefile to be used with sphinx-build.
|
||||
help=__('release of project'))
|
||||
group.add_argument('-l', '--language', metavar='LANGUAGE', dest='language',
|
||||
help=__('document language'))
|
||||
group.add_argument('--suffix', metavar='SUFFIX',
|
||||
group.add_argument('--suffix', metavar='SUFFIX', default='.rst',
|
||||
help=__('source file suffix'))
|
||||
group.add_argument('--master', metavar='MASTER',
|
||||
group.add_argument('--master', metavar='MASTER', default='index',
|
||||
help=__('master document name'))
|
||||
group.add_argument('--epub', action='store_true', default=False,
|
||||
help=__('use epub'))
|
||||
@@ -558,11 +546,11 @@ Makefile to be used with sphinx-build.
|
||||
action='append', help=__('enable arbitrary extensions'))
|
||||
|
||||
group = parser.add_argument_group(__('Makefile and Batchfile creation'))
|
||||
group.add_argument('--makefile', action='store_true', dest='makefile', default=None,
|
||||
group.add_argument('--makefile', action='store_true', dest='makefile', default=True,
|
||||
help=__('create makefile'))
|
||||
group.add_argument('--no-makefile', action='store_false', dest='makefile',
|
||||
help=__('do not create makefile'))
|
||||
group.add_argument('--batchfile', action='store_true', dest='batchfile', default=None,
|
||||
group.add_argument('--batchfile', action='store_true', dest='batchfile', default=True,
|
||||
help=__('create batchfile'))
|
||||
group.add_argument('--no-batchfile', action='store_false',
|
||||
dest='batchfile',
|
||||
@@ -604,6 +592,13 @@ def main(argv=sys.argv[1:]):
|
||||
# delete None or False value
|
||||
d = dict((k, v) for k, v in d.items() if v is not None)
|
||||
|
||||
# handle use of CSV-style extension values
|
||||
d.setdefault('extensions', [])
|
||||
for ext in d['extensions'][:]:
|
||||
if ',' in ext:
|
||||
d['extensions'].remove(ext)
|
||||
d['extensions'].extend(ext.split(','))
|
||||
|
||||
try:
|
||||
if 'quiet' in d:
|
||||
if not set(['project', 'author']).issubset(d):
|
||||
@@ -633,13 +628,6 @@ def main(argv=sys.argv[1:]):
|
||||
print('[Interrupted.]')
|
||||
return 130 # 128 + SIGINT
|
||||
|
||||
# handle use of CSV-style extension values
|
||||
d.setdefault('extensions', [])
|
||||
for ext in d['extensions'][:]:
|
||||
if ',' in ext:
|
||||
d['extensions'].remove(ext)
|
||||
d['extensions'].extend(ext.split(','))
|
||||
|
||||
for variable in d.get('variables', []):
|
||||
try:
|
||||
name, value = variable.split('=')
|
||||
|
||||
@@ -22,7 +22,8 @@ from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.i18n import format_date
|
||||
from sphinx.util.osutil import cd
|
||||
from sphinx.util.pycompat import execfile_, NoneType
|
||||
from sphinx.util.pycompat import execfile_
|
||||
from sphinx.util.typing import NoneType
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
|
||||
@@ -48,7 +48,7 @@ class _ModuleWrapper(object):
|
||||
def __getattr__(self, name):
|
||||
# type: (str) -> Any
|
||||
if name in self._objects:
|
||||
warnings.warn("%s.%s is now deprecated. Please refer CHANGES to grasp"
|
||||
warnings.warn("%s.%s is now deprecated. Please refer CHANGES to grasp "
|
||||
"the changes of Sphinx API." % (self._modname, name),
|
||||
self._warning, stacklevel=3)
|
||||
return self._objects[name]
|
||||
|
||||
@@ -47,9 +47,11 @@ class Highlight(SphinxDirective):
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
language = self.arguments[0].strip()
|
||||
linenothreshold = self.options.get('linenothreshold', sys.maxsize)
|
||||
return [addnodes.highlightlang(lang=self.arguments[0].strip(),
|
||||
linenothreshold=linenothreshold)]
|
||||
|
||||
self.env.temp_data['highlight_language'] = language
|
||||
return [addnodes.highlightlang(lang=language, linenothreshold=linenothreshold)]
|
||||
|
||||
|
||||
class HighlightLang(Highlight):
|
||||
@@ -110,8 +112,8 @@ class CodeBlock(SphinxDirective):
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
required_arguments = 0
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'linenos': directives.flag,
|
||||
@@ -152,10 +154,20 @@ class CodeBlock(SphinxDirective):
|
||||
code = '\n'.join(lines)
|
||||
|
||||
literal = nodes.literal_block(code, code) # type: nodes.Element
|
||||
literal['language'] = self.arguments[0]
|
||||
literal['linenos'] = 'linenos' in self.options or \
|
||||
'lineno-start' in self.options
|
||||
if 'linenos' in self.options or 'lineno-start' in self.options:
|
||||
literal['linenos'] = True
|
||||
literal['classes'] += self.options.get('class', [])
|
||||
if self.arguments:
|
||||
# highlight language specified
|
||||
literal['language'] = self.arguments[0]
|
||||
literal['force_highlighting'] = True
|
||||
else:
|
||||
# no highlight language specified. Then this directive refers the current
|
||||
# highlight setting via ``highlight`` directive or ``highlight_language``
|
||||
# configuration.
|
||||
literal['language'] = self.env.temp_data.get('highlight_language',
|
||||
self.config.highlight_language)
|
||||
literal['force_highlighting'] = False
|
||||
extra_args = literal['highlight_args'] = {}
|
||||
if hl_lines is not None:
|
||||
extra_args['hl_lines'] = hl_lines
|
||||
@@ -439,9 +451,9 @@ class LiteralInclude(SphinxDirective):
|
||||
retnode['language'] = 'udiff'
|
||||
elif 'language' in self.options:
|
||||
retnode['language'] = self.options['language']
|
||||
retnode['linenos'] = ('linenos' in self.options or
|
||||
'lineno-start' in self.options or
|
||||
'lineno-match' in self.options)
|
||||
if ('linenos' in self.options or 'lineno-start' in self.options or
|
||||
'lineno-match' in self.options):
|
||||
retnode['linenos'] = True
|
||||
retnode['classes'] += self.options.get('class', [])
|
||||
extra_args = retnode['highlight_args'] = {}
|
||||
if 'emphasize-lines' in self.options:
|
||||
|
||||
@@ -91,32 +91,54 @@ class Index:
|
||||
|
||||
def generate(self, docnames=None):
|
||||
# type: (Iterable[str]) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]
|
||||
"""Return entries for the index given by *name*. If *docnames* is
|
||||
given, restrict to entries referring to these docnames.
|
||||
"""Get entries for the index.
|
||||
|
||||
The return value is a tuple of ``(content, collapse)``, where *collapse*
|
||||
is a boolean that determines if sub-entries should start collapsed (for
|
||||
output formats that support collapsing sub-entries).
|
||||
If ``docnames`` is given, restrict to entries referring to these
|
||||
docnames.
|
||||
|
||||
*content* is a sequence of ``(letter, entries)`` tuples, where *letter*
|
||||
is the "heading" for the given *entries*, usually the starting letter.
|
||||
The return value is a tuple of ``(content, collapse)``:
|
||||
|
||||
*entries* is a sequence of single entries, where a single entry is a
|
||||
sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``.
|
||||
The items in this sequence have the following meaning:
|
||||
``collapse``
|
||||
A boolean that determines if sub-entries should start collapsed (for
|
||||
output formats that support collapsing sub-entries).
|
||||
|
||||
- `name` -- the name of the index entry to be displayed
|
||||
- `subtype` -- sub-entry related type:
|
||||
0 -- normal entry
|
||||
1 -- entry with sub-entries
|
||||
2 -- sub-entry
|
||||
- `docname` -- docname where the entry is located
|
||||
- `anchor` -- anchor for the entry within `docname`
|
||||
- `extra` -- extra info for the entry
|
||||
- `qualifier` -- qualifier for the description
|
||||
- `descr` -- description for the entry
|
||||
``content``:
|
||||
A sequence of ``(letter, entries)`` tuples, where ``letter`` is the
|
||||
"heading" for the given ``entries``, usually the starting letter, and
|
||||
``entries`` is a sequence of single entries. Each entry is a sequence
|
||||
``[name, subtype, docname, anchor, extra, qualifier, descr]``. The
|
||||
items in this sequence have the following meaning:
|
||||
|
||||
Qualifier and description are not rendered e.g. in LaTeX output.
|
||||
``name``
|
||||
The name of the index entry to be displayed.
|
||||
|
||||
``subtype``
|
||||
The sub-entry related type. One of:
|
||||
|
||||
``0``
|
||||
A normal entry.
|
||||
``1``
|
||||
An entry with sub-entries.
|
||||
``2``
|
||||
A sub-entry.
|
||||
|
||||
``docname``
|
||||
*docname* where the entry is located.
|
||||
|
||||
``anchor``
|
||||
Anchor for the entry within ``docname``
|
||||
|
||||
``extra``
|
||||
Extra info for the entry.
|
||||
|
||||
``qualifier``
|
||||
Qualifier for the description.
|
||||
|
||||
``descr``
|
||||
Description for the entry.
|
||||
|
||||
Qualifier and description are not rendered for some output formats such
|
||||
as LaTeX.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -318,21 +340,37 @@ class Domain:
|
||||
|
||||
def get_objects(self):
|
||||
# type: () -> Iterable[Tuple[str, str, str, str, str, int]]
|
||||
"""Return an iterable of "object descriptions", which are tuples with
|
||||
five items:
|
||||
"""Return an iterable of "object descriptions".
|
||||
|
||||
* `name` -- fully qualified name
|
||||
* `dispname` -- name to display when searching/linking
|
||||
* `type` -- object type, a key in ``self.object_types``
|
||||
* `docname` -- the document where it is to be found
|
||||
* `anchor` -- the anchor name for the object
|
||||
* `priority` -- how "important" the object is (determines placement
|
||||
in search results)
|
||||
Object descriptions are tuples with six items:
|
||||
|
||||
- 1: default priority (placed before full-text matches)
|
||||
- 0: object is important (placed before default-priority objects)
|
||||
- 2: object is unimportant (placed after full-text matches)
|
||||
- -1: object should not show up in search at all
|
||||
``name``
|
||||
Fully qualified name.
|
||||
|
||||
``dispname``
|
||||
Name to display when searching/linking.
|
||||
|
||||
``type``
|
||||
Object type, a key in ``self.object_types``.
|
||||
|
||||
``docname``
|
||||
The document where it is to be found.
|
||||
|
||||
``anchor``
|
||||
The anchor name for the object.
|
||||
|
||||
``priority``
|
||||
How "important" the object is (determines placement in search
|
||||
results). One of:
|
||||
|
||||
``1``
|
||||
Default priority (placed before full-text matches).
|
||||
``0``
|
||||
Object is important (placed before default-priority objects).
|
||||
``2``
|
||||
Object is unimportant (placed after full-text matches).
|
||||
``-1``
|
||||
Object should not show up in search at all.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
@@ -7158,7 +7158,7 @@ class CPPDomain(Domain):
|
||||
# the non-identifier refs are cross-references, which should be processed:
|
||||
# - fix parenthesis due to operator() and add_function_parentheses
|
||||
if typ != "identifier":
|
||||
title = contnode.pop(0).astext() # type: ignore
|
||||
title = contnode.pop(0).astext()
|
||||
# If it's operator(), we need to add '()' if explicit function parens
|
||||
# are requested. Then the Sphinx machinery will add another pair.
|
||||
# Also, if it's an 'any' ref that resolves to a function, we need to add
|
||||
|
||||
@@ -151,7 +151,7 @@ class PyXrefMixin:
|
||||
delims_re = re.compile(delims)
|
||||
sub_targets = re.split(delims, target)
|
||||
|
||||
split_contnode = bool(contnode and contnode.astext() == target) # type: ignore
|
||||
split_contnode = bool(contnode and contnode.astext() == target)
|
||||
|
||||
results = []
|
||||
for sub_target in filter(None, sub_targets):
|
||||
|
||||
@@ -25,7 +25,7 @@ from sphinx.transforms import SphinxTransformer
|
||||
from sphinx.util import DownloadFiles, FilenameUniqDict
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docutils import LoggingReporter
|
||||
from sphinx.util.i18n import find_catalog_files
|
||||
from sphinx.util.i18n import CatalogRepository, docname_to_domain
|
||||
from sphinx.util.nodes import is_translatable
|
||||
|
||||
if False:
|
||||
@@ -395,15 +395,13 @@ class BuildEnvironment:
|
||||
# move i18n process into the writing phase, and remove these lines.
|
||||
if builder.use_message_catalog:
|
||||
# add catalog mo file dependency
|
||||
repo = CatalogRepository(self.srcdir, self.config.locale_dirs,
|
||||
self.config.language, self.config.source_encoding)
|
||||
for docname in self.found_docs:
|
||||
catalog_files = find_catalog_files(
|
||||
docname,
|
||||
self.srcdir,
|
||||
self.config.locale_dirs,
|
||||
self.config.language,
|
||||
self.config.gettext_compact)
|
||||
for filename in catalog_files:
|
||||
self.dependencies[docname].add(filename)
|
||||
domain = docname_to_domain(docname, self.config.gettext_compact)
|
||||
for catalog in repo.catalogs:
|
||||
if catalog.domain == domain:
|
||||
self.dependencies[docname].add(catalog.mo_path)
|
||||
except OSError as exc:
|
||||
raise DocumentError(__('Failed to scan documents in %s: %r') % (self.srcdir, exc))
|
||||
|
||||
|
||||
@@ -18,9 +18,11 @@ from typing import Any
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
import sphinx
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
|
||||
from sphinx.ext.autodoc.importer import mock, import_object, get_object_members
|
||||
from sphinx.ext.autodoc.importer import _MockImporter # to keep compatibility # NOQA
|
||||
from sphinx.deprecation import (
|
||||
RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
|
||||
)
|
||||
from sphinx.ext.autodoc.importer import import_object, get_object_members
|
||||
from sphinx.ext.autodoc.mock import mock
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.util import logging
|
||||
@@ -72,7 +74,7 @@ INSTANCEATTR = object()
|
||||
def members_option(arg):
|
||||
# type: (Any) -> Union[object, List[str]]
|
||||
"""Used to convert the :members: option to auto directives."""
|
||||
if arg is None:
|
||||
if arg is None or arg is True:
|
||||
return ALL
|
||||
return [x.strip() for x in arg.split(',')]
|
||||
|
||||
@@ -1002,6 +1004,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
return None
|
||||
try:
|
||||
if (not isfunction(self.object) and
|
||||
not inspect.ismethod(self.object) and
|
||||
not isbuiltin(self.object) and
|
||||
not inspect.isclass(self.object) and
|
||||
hasattr(self.object, '__call__')):
|
||||
@@ -1032,6 +1035,23 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
pass
|
||||
|
||||
|
||||
class DecoratorDocumenter(FunctionDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for decorator functions.
|
||||
"""
|
||||
objtype = 'decorator'
|
||||
|
||||
# must be lower than FunctionDocumenter
|
||||
priority = -1
|
||||
|
||||
def format_args(self):
|
||||
args = super().format_args()
|
||||
if ',' in args:
|
||||
return args
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for classes.
|
||||
@@ -1454,6 +1474,15 @@ def merge_autodoc_default_flags(app, config):
|
||||
)
|
||||
|
||||
|
||||
from sphinx.ext.autodoc.mock import _MockImporter # NOQA
|
||||
|
||||
deprecated_alias('sphinx.ext.autodoc',
|
||||
{
|
||||
'_MockImporter': _MockImporter,
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.add_autodocumenter(ModuleDocumenter)
|
||||
@@ -1461,6 +1490,7 @@ def setup(app):
|
||||
app.add_autodocumenter(ExceptionDocumenter)
|
||||
app.add_autodocumenter(DataDocumenter)
|
||||
app.add_autodocumenter(FunctionDocumenter)
|
||||
app.add_autodocumenter(DecoratorDocumenter)
|
||||
app.add_autodocumenter(MethodDocumenter)
|
||||
app.add_autodocumenter(AttributeDocumenter)
|
||||
app.add_autodocumenter(InstanceAttributeDocumenter)
|
||||
|
||||
@@ -8,199 +8,22 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from importlib.abc import Loader, MetaPathFinder
|
||||
from importlib.machinery import ModuleSpec
|
||||
from types import FunctionType, MethodType, ModuleType
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.inspect import isenumclass, safe_getattr
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Callable, Dict, Generator, Iterator, List, Optional, Sequence, Tuple, Union # NOQA
|
||||
from typing import Any, Callable, Dict, List # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _MockObject:
|
||||
"""Used by autodoc_mock_imports."""
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
# type: (Any, Any) -> Any
|
||||
if len(args) == 3 and isinstance(args[1], tuple) and args[1][-1].__class__ is cls:
|
||||
# subclassing MockObject
|
||||
return type(args[0], (_MockObject,), args[2], **kwargs) # type: ignore
|
||||
else:
|
||||
return super(_MockObject, cls).__new__(cls)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
self.__qualname__ = ''
|
||||
|
||||
def __len__(self):
|
||||
# type: () -> int
|
||||
return 0
|
||||
|
||||
def __contains__(self, key):
|
||||
# type: (str) -> bool
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> Iterator
|
||||
return iter([])
|
||||
|
||||
def __mro_entries__(self, bases):
|
||||
# type: (Tuple) -> Tuple
|
||||
return bases
|
||||
|
||||
def __getitem__(self, key):
|
||||
# type: (str) -> _MockObject
|
||||
return self
|
||||
|
||||
def __getattr__(self, key):
|
||||
# type: (str) -> _MockObject
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
# type: (Any, Any) -> Any
|
||||
if args and type(args[0]) in [FunctionType, MethodType]:
|
||||
# Appears to be a decorator, pass through unchanged
|
||||
return args[0]
|
||||
return self
|
||||
|
||||
|
||||
class _MockModule(ModuleType):
|
||||
"""Used by autodoc_mock_imports."""
|
||||
__file__ = os.devnull
|
||||
|
||||
def __init__(self, name, loader=None):
|
||||
# type: (str, _MockImporter) -> None
|
||||
super().__init__(name)
|
||||
self.__all__ = [] # type: List[str]
|
||||
self.__path__ = [] # type: List[str]
|
||||
|
||||
if loader is not None:
|
||||
warnings.warn('The loader argument for _MockModule is deprecated.',
|
||||
RemovedInSphinx30Warning)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# type: (str) -> _MockObject
|
||||
o = _MockObject()
|
||||
o.__module__ = self.__name__
|
||||
return o
|
||||
|
||||
|
||||
class _MockImporter(MetaPathFinder):
|
||||
def __init__(self, names):
|
||||
# type: (List[str]) -> None
|
||||
self.names = names
|
||||
self.mocked_modules = [] # type: List[str]
|
||||
# enable hook by adding itself to meta_path
|
||||
sys.meta_path.insert(0, self)
|
||||
|
||||
warnings.warn('_MockImporter is now deprecated.',
|
||||
RemovedInSphinx30Warning)
|
||||
|
||||
def disable(self):
|
||||
# type: () -> None
|
||||
# remove `self` from `sys.meta_path` to disable import hook
|
||||
sys.meta_path = [i for i in sys.meta_path if i is not self]
|
||||
# remove mocked modules from sys.modules to avoid side effects after
|
||||
# running auto-documenter
|
||||
for m in self.mocked_modules:
|
||||
if m in sys.modules:
|
||||
del sys.modules[m]
|
||||
|
||||
def find_module(self, name, path=None):
|
||||
# type: (str, Sequence[Union[bytes, str]]) -> Any
|
||||
# check if name is (or is a descendant of) one of our base_packages
|
||||
for n in self.names:
|
||||
if n == name or name.startswith(n + '.'):
|
||||
return self
|
||||
return None
|
||||
|
||||
def load_module(self, name):
|
||||
# type: (str) -> ModuleType
|
||||
if name in sys.modules:
|
||||
# module has already been imported, return it
|
||||
return sys.modules[name]
|
||||
else:
|
||||
logger.debug('[autodoc] adding a mock module %s!', name)
|
||||
module = _MockModule(name, self)
|
||||
sys.modules[name] = module
|
||||
self.mocked_modules.append(name)
|
||||
return module
|
||||
|
||||
|
||||
class MockLoader(Loader):
|
||||
"""A loader for mocking."""
|
||||
def __init__(self, finder):
|
||||
# type: (MockFinder) -> None
|
||||
super().__init__()
|
||||
self.finder = finder
|
||||
|
||||
def create_module(self, spec):
|
||||
# type: (ModuleSpec) -> ModuleType
|
||||
logger.debug('[autodoc] adding a mock module as %s!', spec.name)
|
||||
self.finder.mocked_modules.append(spec.name)
|
||||
return _MockModule(spec.name)
|
||||
|
||||
def exec_module(self, module):
|
||||
# type: (ModuleType) -> None
|
||||
pass # nothing to do
|
||||
|
||||
|
||||
class MockFinder(MetaPathFinder):
|
||||
"""A finder for mocking."""
|
||||
|
||||
def __init__(self, modnames):
|
||||
# type: (List[str]) -> None
|
||||
super().__init__()
|
||||
self.modnames = modnames
|
||||
self.loader = MockLoader(self)
|
||||
self.mocked_modules = [] # type: List[str]
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
# type: (str, Sequence[Union[bytes, str]], ModuleType) -> ModuleSpec
|
||||
for modname in self.modnames:
|
||||
# check if fullname is (or is a descendant of) one of our targets
|
||||
if modname == fullname or fullname.startswith(modname + '.'):
|
||||
return ModuleSpec(fullname, self.loader)
|
||||
|
||||
return None
|
||||
|
||||
def invalidate_caches(self):
|
||||
# type: () -> None
|
||||
"""Invalidate mocked modules on sys.modules."""
|
||||
for modname in self.mocked_modules:
|
||||
sys.modules.pop(modname, None)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mock(modnames):
|
||||
# type: (List[str]) -> Generator[None, None, None]
|
||||
"""Insert mock modules during context::
|
||||
|
||||
with mock(['target.module.name']):
|
||||
# mock modules are enabled here
|
||||
...
|
||||
"""
|
||||
try:
|
||||
finder = MockFinder(modnames)
|
||||
sys.meta_path.insert(0, finder)
|
||||
yield
|
||||
finally:
|
||||
sys.meta_path.remove(finder)
|
||||
finder.invalidate_caches()
|
||||
|
||||
|
||||
def import_module(modname, warningiserror=False):
|
||||
# type: (str, bool) -> Any
|
||||
"""
|
||||
@@ -324,3 +147,19 @@ def get_object_members(subject, objpath, attrgetter, analyzer=None):
|
||||
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||
|
||||
return members
|
||||
|
||||
|
||||
from sphinx.ext.autodoc.mock import ( # NOQA
|
||||
_MockImporter, _MockModule, _MockObject, MockFinder, MockLoader, mock
|
||||
)
|
||||
|
||||
deprecated_alias('sphinx.ext.autodoc.importer',
|
||||
{
|
||||
'_MockImporter': _MockImporter,
|
||||
'_MockModule': _MockModule,
|
||||
'_MockObject': _MockObject,
|
||||
'MockFinder': MockFinder,
|
||||
'MockLoader': MockLoader,
|
||||
'mock': mock,
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
217
sphinx/ext/autodoc/mock.py
Normal file
217
sphinx/ext/autodoc/mock.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
sphinx.ext.autodoc.mock
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
mock for autodoc
|
||||
|
||||
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from importlib.abc import Loader, MetaPathFinder
|
||||
from importlib.machinery import ModuleSpec
|
||||
from types import FunctionType, MethodType, ModuleType
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.util import logging
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _MockObject:
|
||||
"""Used by autodoc_mock_imports."""
|
||||
|
||||
__display_name__ = '_MockObject'
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
# type: (Any, Any) -> Any
|
||||
if len(args) == 3 and isinstance(args[1], tuple):
|
||||
superclass = args[1][-1].__class__
|
||||
if superclass is cls:
|
||||
# subclassing MockObject
|
||||
return _make_subclass(args[0], superclass.__display_name__,
|
||||
superclass=superclass, attributes=args[2])
|
||||
|
||||
return super(_MockObject, cls).__new__(cls)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
self.__qualname__ = ''
|
||||
|
||||
def __len__(self):
|
||||
# type: () -> int
|
||||
return 0
|
||||
|
||||
def __contains__(self, key):
|
||||
# type: (str) -> bool
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> Iterator
|
||||
return iter([])
|
||||
|
||||
def __mro_entries__(self, bases):
|
||||
# type: (Tuple) -> Tuple
|
||||
return (self.__class__,)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# type: (str) -> _MockObject
|
||||
return _make_subclass(key, self.__display_name__, self.__class__)()
|
||||
|
||||
def __getattr__(self, key):
|
||||
# type: (str) -> _MockObject
|
||||
return _make_subclass(key, self.__display_name__, self.__class__)()
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
# type: (Any, Any) -> Any
|
||||
if args and type(args[0]) in [FunctionType, MethodType]:
|
||||
# Appears to be a decorator, pass through unchanged
|
||||
return args[0]
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return self.__display_name__
|
||||
|
||||
|
||||
def _make_subclass(name, module, superclass=_MockObject, attributes=None):
|
||||
# type: (str, str, Any, dict) -> Any
|
||||
attrs = {'__module__': module, '__display_name__': module + '.' + name}
|
||||
attrs.update(attributes or {})
|
||||
|
||||
return type(name, (superclass,), attrs)
|
||||
|
||||
|
||||
class _MockModule(ModuleType):
|
||||
"""Used by autodoc_mock_imports."""
|
||||
__file__ = os.devnull
|
||||
|
||||
def __init__(self, name, loader=None):
|
||||
# type: (str, _MockImporter) -> None
|
||||
super().__init__(name)
|
||||
self.__all__ = [] # type: List[str]
|
||||
self.__path__ = [] # type: List[str]
|
||||
|
||||
if loader is not None:
|
||||
warnings.warn('The loader argument for _MockModule is deprecated.',
|
||||
RemovedInSphinx30Warning)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# type: (str) -> _MockObject
|
||||
return _make_subclass(name, self.__name__)()
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return self.__name__
|
||||
|
||||
|
||||
class _MockImporter(MetaPathFinder):
|
||||
def __init__(self, names):
|
||||
# type: (List[str]) -> None
|
||||
self.names = names
|
||||
self.mocked_modules = [] # type: List[str]
|
||||
# enable hook by adding itself to meta_path
|
||||
sys.meta_path.insert(0, self)
|
||||
|
||||
warnings.warn('_MockImporter is now deprecated.',
|
||||
RemovedInSphinx30Warning)
|
||||
|
||||
def disable(self):
|
||||
# type: () -> None
|
||||
# remove `self` from `sys.meta_path` to disable import hook
|
||||
sys.meta_path = [i for i in sys.meta_path if i is not self]
|
||||
# remove mocked modules from sys.modules to avoid side effects after
|
||||
# running auto-documenter
|
||||
for m in self.mocked_modules:
|
||||
if m in sys.modules:
|
||||
del sys.modules[m]
|
||||
|
||||
def find_module(self, name, path=None):
|
||||
# type: (str, Sequence[Union[bytes, str]]) -> Any
|
||||
# check if name is (or is a descendant of) one of our base_packages
|
||||
for n in self.names:
|
||||
if n == name or name.startswith(n + '.'):
|
||||
return self
|
||||
return None
|
||||
|
||||
def load_module(self, name):
|
||||
# type: (str) -> ModuleType
|
||||
if name in sys.modules:
|
||||
# module has already been imported, return it
|
||||
return sys.modules[name]
|
||||
else:
|
||||
logger.debug('[autodoc] adding a mock module %s!', name)
|
||||
module = _MockModule(name, self)
|
||||
sys.modules[name] = module
|
||||
self.mocked_modules.append(name)
|
||||
return module
|
||||
|
||||
|
||||
class MockLoader(Loader):
|
||||
"""A loader for mocking."""
|
||||
def __init__(self, finder):
|
||||
# type: (MockFinder) -> None
|
||||
super().__init__()
|
||||
self.finder = finder
|
||||
|
||||
def create_module(self, spec):
|
||||
# type: (ModuleSpec) -> ModuleType
|
||||
logger.debug('[autodoc] adding a mock module as %s!', spec.name)
|
||||
self.finder.mocked_modules.append(spec.name)
|
||||
return _MockModule(spec.name)
|
||||
|
||||
def exec_module(self, module):
|
||||
# type: (ModuleType) -> None
|
||||
pass # nothing to do
|
||||
|
||||
|
||||
class MockFinder(MetaPathFinder):
|
||||
"""A finder for mocking."""
|
||||
|
||||
def __init__(self, modnames):
|
||||
# type: (List[str]) -> None
|
||||
super().__init__()
|
||||
self.modnames = modnames
|
||||
self.loader = MockLoader(self)
|
||||
self.mocked_modules = [] # type: List[str]
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
# type: (str, Sequence[Union[bytes, str]], ModuleType) -> ModuleSpec
|
||||
for modname in self.modnames:
|
||||
# check if fullname is (or is a descendant of) one of our targets
|
||||
if modname == fullname or fullname.startswith(modname + '.'):
|
||||
return ModuleSpec(fullname, self.loader)
|
||||
|
||||
return None
|
||||
|
||||
def invalidate_caches(self):
|
||||
# type: () -> None
|
||||
"""Invalidate mocked modules on sys.modules."""
|
||||
for modname in self.mocked_modules:
|
||||
sys.modules.pop(modname, None)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mock(modnames):
|
||||
# type: (List[str]) -> Generator[None, None, None]
|
||||
"""Insert mock modules during context::
|
||||
|
||||
with mock(['target.module.name']):
|
||||
# mock modules are enabled here
|
||||
...
|
||||
"""
|
||||
try:
|
||||
finder = MockFinder(modnames)
|
||||
sys.meta_path.insert(0, finder)
|
||||
yield
|
||||
finally:
|
||||
sys.meta_path.remove(finder)
|
||||
finder.invalidate_caches()
|
||||
@@ -30,11 +30,23 @@ if False:
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
|
||||
def get_node_depth(node):
|
||||
i = 0
|
||||
cur_node = node
|
||||
while cur_node.parent != node.document:
|
||||
cur_node = cur_node.parent
|
||||
i += 1
|
||||
return i
|
||||
|
||||
|
||||
def register_sections_as_label(app, document):
|
||||
# type: (Sphinx, nodes.Node) -> None
|
||||
labels = app.env.domaindata['std']['labels']
|
||||
anonlabels = app.env.domaindata['std']['anonlabels']
|
||||
for node in document.traverse(nodes.section):
|
||||
if (app.config.autosectionlabel_maxdepth and
|
||||
get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
|
||||
continue
|
||||
labelid = node['ids'][0]
|
||||
docname = app.env.docname
|
||||
title = cast(nodes.title, node[0])
|
||||
@@ -57,6 +69,7 @@ def register_sections_as_label(app, document):
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.add_config_value('autosectionlabel_prefix_document', False, 'env')
|
||||
app.add_config_value('autosectionlabel_maxdepth', None, 'env')
|
||||
app.connect('doctree-read', register_sections_as_label)
|
||||
|
||||
return {
|
||||
|
||||
@@ -73,11 +73,12 @@ from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.ext.autodoc import get_documenters
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge, Options
|
||||
from sphinx.ext.autodoc.importer import import_module
|
||||
from sphinx.ext.autodoc.mock import mock
|
||||
from sphinx.locale import __
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.util import import_object, rst, logging
|
||||
from sphinx.util.docutils import (
|
||||
NullReporter, SphinxDirective, new_document, switch_source_input
|
||||
NullReporter, SphinxDirective, SphinxRole, new_document, switch_source_input
|
||||
)
|
||||
from sphinx.util.matching import Matcher
|
||||
|
||||
@@ -286,7 +287,8 @@ class Autosummary(SphinxDirective):
|
||||
display_name = name.split('.')[-1]
|
||||
|
||||
try:
|
||||
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
|
||||
with mock(self.config.autosummary_mock_imports):
|
||||
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
|
||||
except ImportError:
|
||||
logger.warning(__('failed to import %s'), name)
|
||||
items.append((name, '', '', name))
|
||||
@@ -641,6 +643,7 @@ def autolink_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]):
|
||||
Expands to ':obj:`text`' if `text` is an object that can be imported;
|
||||
otherwise expands to '*text*'.
|
||||
"""
|
||||
warnings.warn('autolink_role() is deprecated.', RemovedInSphinx40Warning)
|
||||
env = inliner.document.settings.env
|
||||
pyobj_role = env.get_domain('py').role('obj')
|
||||
objects, msg = pyobj_role('obj', rawtext, etext, lineno, inliner, options, content)
|
||||
@@ -659,6 +662,34 @@ def autolink_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]):
|
||||
return objects, msg
|
||||
|
||||
|
||||
class AutoLink(SphinxRole):
|
||||
"""Smart linking role.
|
||||
|
||||
Expands to ':obj:`text`' if `text` is an object that can be imported;
|
||||
otherwise expands to '*text*'.
|
||||
"""
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
pyobj_role = self.env.get_domain('py').role('obj')
|
||||
objects, errors = pyobj_role('obj', self.rawtext, self.text, self.lineno,
|
||||
self.inliner, self.options, self.content)
|
||||
if errors:
|
||||
return objects, errors
|
||||
|
||||
assert len(objects) == 1
|
||||
pending_xref = cast(addnodes.pending_xref, objects[0])
|
||||
try:
|
||||
# try to import object by name
|
||||
prefixes = get_import_prefixes_from_env(self.env)
|
||||
import_by_name(pending_xref['reftarget'], prefixes)
|
||||
except ImportError:
|
||||
literal = cast(nodes.literal, pending_xref[0])
|
||||
objects[0] = nodes.emphasis(self.rawtext, literal.astext(),
|
||||
classes=literal['classes'])
|
||||
|
||||
return objects, errors
|
||||
|
||||
|
||||
def get_rst_suffix(app):
|
||||
# type: (Sphinx) -> str
|
||||
def get_supported_format(suffix):
|
||||
@@ -702,10 +733,11 @@ def process_generate_options(app):
|
||||
'But your source_suffix does not contain .rst. Skipped.'))
|
||||
return
|
||||
|
||||
generate_autosummary_docs(genfiles, builder=app.builder,
|
||||
warn=logger.warning, info=logger.info,
|
||||
suffix=suffix, base_path=app.srcdir,
|
||||
app=app)
|
||||
with mock(app.config.autosummary_mock_imports):
|
||||
generate_autosummary_docs(genfiles, builder=app.builder,
|
||||
warn=logger.warning, info=logger.info,
|
||||
suffix=suffix, base_path=app.srcdir,
|
||||
app=app)
|
||||
|
||||
|
||||
def setup(app):
|
||||
@@ -725,8 +757,11 @@ def setup(app):
|
||||
man=(autosummary_noop, autosummary_noop),
|
||||
texinfo=(autosummary_noop, autosummary_noop))
|
||||
app.add_directive('autosummary', Autosummary)
|
||||
app.add_role('autolink', autolink_role)
|
||||
app.add_role('autolink', AutoLink())
|
||||
app.connect('doctree-read', process_autosummary_toc)
|
||||
app.connect('builder-inited', process_generate_options)
|
||||
app.add_config_value('autosummary_generate', [], True, [bool])
|
||||
app.add_config_value('autosummary_mock_imports',
|
||||
lambda config: config.autodoc_mock_imports, 'env')
|
||||
|
||||
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
||||
|
||||
@@ -83,16 +83,6 @@ class TestDirective(SphinxDirective):
|
||||
|
||||
def run(self):
|
||||
# type: () -> List[nodes.Node]
|
||||
if 'skipif' in self.options:
|
||||
condition = self.options['skipif']
|
||||
context = {} # type: Dict[str, Any]
|
||||
if self.config.doctest_global_setup:
|
||||
exec(self.config.doctest_global_setup, context)
|
||||
should_skip = eval(condition, context)
|
||||
if self.config.doctest_global_cleanup:
|
||||
exec(self.config.doctest_global_cleanup, context)
|
||||
if should_skip:
|
||||
return []
|
||||
# use ordinary docutils nodes for test code: they get special attributes
|
||||
# so that our builder recognizes them, and the other builders are happy.
|
||||
code = '\n'.join(self.content)
|
||||
@@ -160,6 +150,8 @@ class TestDirective(SphinxDirective):
|
||||
self.state.document.reporter.warning(
|
||||
__("'%s' is not a valid pyversion option") % spec,
|
||||
line=self.lineno)
|
||||
if 'skipif' in self.options:
|
||||
node['skipif'] = self.options['skipif']
|
||||
return [node]
|
||||
|
||||
|
||||
@@ -404,6 +396,20 @@ Doctest summary
|
||||
return node.line - 1
|
||||
return None
|
||||
|
||||
def skipped(self, node):
|
||||
# type: (nodes.Element) -> bool
|
||||
if 'skipif' not in node:
|
||||
return False
|
||||
else:
|
||||
condition = node['skipif']
|
||||
context = {} # type: Dict[str, Any]
|
||||
if self.config.doctest_global_setup:
|
||||
exec(self.config.doctest_global_setup, context)
|
||||
should_skip = eval(condition, context)
|
||||
if self.config.doctest_global_cleanup:
|
||||
exec(self.config.doctest_global_cleanup, context)
|
||||
return should_skip
|
||||
|
||||
def test_doc(self, docname, doctree):
|
||||
# type: (str, nodes.Node) -> None
|
||||
groups = {} # type: Dict[str, TestGroup]
|
||||
@@ -429,8 +435,10 @@ Doctest summary
|
||||
# type: (nodes.Node) -> bool
|
||||
return isinstance(node, (nodes.literal_block, nodes.comment)) \
|
||||
and 'testnodetype' in node
|
||||
|
||||
for node in doctree.traverse(condition): # type: nodes.Element
|
||||
if self.skipped(node):
|
||||
continue
|
||||
|
||||
source = node['test'] if 'test' in node else node.astext()
|
||||
filename = self.get_filename_for_node(node, docname)
|
||||
line_number = self.get_line_number(node)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import urllib
|
||||
|
||||
import sphinx
|
||||
|
||||
@@ -19,14 +20,22 @@ if False:
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
|
||||
def create_nojekyll(app, env):
|
||||
def create_nojekyll_and_cname(app, env):
|
||||
# type: (Sphinx, BuildEnvironment) -> None
|
||||
if app.builder.format == 'html':
|
||||
path = os.path.join(app.builder.outdir, '.nojekyll')
|
||||
open(path, 'wt').close()
|
||||
open(os.path.join(app.builder.outdir, '.nojekyll'), 'wt').close()
|
||||
|
||||
html_baseurl = app.config.html_baseurl
|
||||
if html_baseurl:
|
||||
domain = urllib.parse.urlparse(html_baseurl).hostname
|
||||
if domain and not domain.endswith(".github.io"):
|
||||
with open(os.path.join(app.builder.outdir, 'CNAME'), 'wt') as f:
|
||||
# NOTE: don't write a trailing newline. The `CNAME` file that's
|
||||
# auto-generated by the Github UI doesn't have one.
|
||||
f.write(domain)
|
||||
|
||||
|
||||
def setup(app):
|
||||
# type: (Sphinx) -> Dict[str, Any]
|
||||
app.connect('env-updated', create_nojekyll)
|
||||
app.connect('env-updated', create_nojekyll_and_cname)
|
||||
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
||||
|
||||
@@ -196,9 +196,7 @@ class GraphvizSimple(SphinxDirective):
|
||||
node = graphviz()
|
||||
node['code'] = '%s %s {\n%s\n}\n' % \
|
||||
(self.name, self.arguments[0], '\n'.join(self.content))
|
||||
node['options'] = {
|
||||
'docname': path.splitext(self.state.document.current_source)[0],
|
||||
}
|
||||
node['options'] = {'docname': self.env.docname}
|
||||
if 'graphviz_dot' in self.options:
|
||||
node['options']['graphviz_dot'] = self.options['graphviz_dot']
|
||||
if 'alt' in self.options:
|
||||
|
||||
@@ -54,9 +54,9 @@ class ImagemagickConverter(ImageConverter):
|
||||
# type: (str, str) -> bool
|
||||
"""Converts the image to expected one."""
|
||||
try:
|
||||
if _from.lower().endswith('.gif'):
|
||||
# when target is GIF format, pick the first frame
|
||||
_from += '[0]'
|
||||
# append an index 0 to source filename to pick up the first frame
|
||||
# (or first page) of image (ex. Animation GIF, PDF)
|
||||
_from += '[0]'
|
||||
|
||||
args = ([self.config.image_converter] +
|
||||
self.config.image_converter_args +
|
||||
|
||||
@@ -12,6 +12,7 @@ import posixpath
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from hashlib import sha1
|
||||
from os import path
|
||||
@@ -26,7 +27,6 @@ from sphinx.util import logging
|
||||
from sphinx.util.math import get_node_equation_number, wrap_displaymath
|
||||
from sphinx.util.osutil import ensuredir
|
||||
from sphinx.util.png import read_png_depth, write_png_depth
|
||||
from sphinx.util.pycompat import sys_encoding
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -46,9 +46,9 @@ class MathExtError(SphinxError):
|
||||
def __init__(self, msg, stderr=None, stdout=None):
|
||||
# type: (str, bytes, bytes) -> None
|
||||
if stderr:
|
||||
msg += '\n[stderr]\n' + stderr.decode(sys_encoding, 'replace')
|
||||
msg += '\n[stderr]\n' + stderr.decode(sys.getdefaultencoding(), 'replace')
|
||||
if stdout:
|
||||
msg += '\n[stdout]\n' + stdout.decode(sys_encoding, 'replace')
|
||||
msg += '\n[stdout]\n' + stdout.decode(sys.getdefaultencoding(), 'replace')
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from typing import Any, Union # NOQA
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.transforms import (
|
||||
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
|
||||
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
|
||||
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds, FigureAligner,
|
||||
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
|
||||
UnreferencedFootnotesDetector, SphinxSmartQuotes, DoctreeReadEvent, ManpageLink
|
||||
)
|
||||
@@ -96,7 +96,7 @@ class SphinxStandaloneReader(SphinxBaseReader):
|
||||
"""
|
||||
transforms = [ApplySourceWorkaround, ExtraTranslatableNodes, PreserveTranslatableMessages,
|
||||
Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets,
|
||||
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds,
|
||||
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, FigureAligner,
|
||||
RemoveTranslatableInline, FilterSystemMessages, RefOnlyBulletListTransform,
|
||||
UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink,
|
||||
SphinxDomains, SubstitutionDefinitionsRemover, DoctreeReadEvent,
|
||||
|
||||
@@ -267,7 +267,9 @@ def get_translation(catalog, namespace='general'):
|
||||
|
||||
With this code, sphinx searches a message catalog from
|
||||
``${package_dir}/locales/${language}/LC_MESSAGES/myextension.mo``
|
||||
The :confval:`language` is used for the searching.
|
||||
(ex. ``sphinxcontrib.applehelp.mo``). Of course, you can use
|
||||
arbitrary catalog name instead of ``__name__``. The
|
||||
:confval:`language` is used for the searching.
|
||||
|
||||
.. versionadded:: 1.8
|
||||
"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
327
sphinx/roles.py
327
sphinx/roles.py
@@ -9,13 +9,15 @@
|
||||
"""
|
||||
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from docutils import nodes, utils
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.locale import _
|
||||
from sphinx.util import ws_re
|
||||
from sphinx.util.docutils import ReferenceRole, SphinxRole
|
||||
from sphinx.util.nodes import split_explicit_title, process_index_entry, \
|
||||
set_role_source_info
|
||||
|
||||
@@ -44,7 +46,7 @@ generic_docroles = {
|
||||
|
||||
# -- generic cross-reference role ----------------------------------------------
|
||||
|
||||
class XRefRole:
|
||||
class XRefRole(ReferenceRole):
|
||||
"""
|
||||
A generic cross-referencing role. To create a callable that can be used as
|
||||
a role function, create an instance of this class.
|
||||
@@ -82,8 +84,12 @@ class XRefRole:
|
||||
if innernodeclass is not None:
|
||||
self.innernodeclass = innernodeclass
|
||||
|
||||
super().__init__()
|
||||
|
||||
def _fix_parens(self, env, has_explicit_title, title, target):
|
||||
# type: (BuildEnvironment, bool, str, str) -> Tuple[str, str]
|
||||
warnings.warn('XRefRole._fix_parens() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
if not has_explicit_title:
|
||||
if title.endswith('()'):
|
||||
# remove parentheses
|
||||
@@ -96,55 +102,70 @@ class XRefRole:
|
||||
target = target[:-2]
|
||||
return title, target
|
||||
|
||||
def __call__(self, typ, rawtext, text, lineno, inliner,
|
||||
options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
env = inliner.document.settings.env
|
||||
if not typ:
|
||||
typ = env.temp_data.get('default_role')
|
||||
if not typ:
|
||||
typ = env.config.default_role
|
||||
if not typ:
|
||||
raise SphinxError('cannot determine default role!')
|
||||
def update_title_and_target(self, title, target):
|
||||
# type: (str, str) -> Tuple[str, str]
|
||||
if not self.has_explicit_title:
|
||||
if title.endswith('()'):
|
||||
# remove parentheses
|
||||
title = title[:-2]
|
||||
if self.config.add_function_parentheses:
|
||||
# add them back to all occurrences if configured
|
||||
title += '()'
|
||||
# remove parentheses from the target too
|
||||
if target.endswith('()'):
|
||||
target = target[:-2]
|
||||
return title, target
|
||||
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
if ':' not in self.name:
|
||||
self.refdomain, self.reftype = '', self.name
|
||||
self.classes = ['xref', self.reftype]
|
||||
else:
|
||||
typ = typ.lower()
|
||||
if ':' not in typ:
|
||||
domain, role = '', typ
|
||||
classes = ['xref', role]
|
||||
self.refdomain, self.reftype = self.name.split(':', 1)
|
||||
self.classes = ['xref', self.refdomain, '%s-%s' % (self.refdomain, self.reftype)]
|
||||
|
||||
if self.text.startswith('!'):
|
||||
# if the first character is a bang, don't cross-reference at all
|
||||
return self.create_non_xref_node()
|
||||
else:
|
||||
domain, role = typ.split(':', 1)
|
||||
classes = ['xref', domain, '%s-%s' % (domain, role)]
|
||||
# if the first character is a bang, don't cross-reference at all
|
||||
if text[0:1] == '!':
|
||||
text = utils.unescape(text)[1:]
|
||||
if self.fix_parens:
|
||||
text, tgt = self._fix_parens(env, False, text, "")
|
||||
innernode = self.innernodeclass(rawtext, text, classes=classes)
|
||||
return self.result_nodes(inliner.document, env, innernode, is_ref=False)
|
||||
# split title and target in role content
|
||||
has_explicit_title, title, target = split_explicit_title(text)
|
||||
title = utils.unescape(title)
|
||||
target = utils.unescape(target)
|
||||
# fix-up title and target
|
||||
return self.create_xref_node()
|
||||
|
||||
def create_non_xref_node(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
text = utils.unescape(self.text[1:])
|
||||
if self.fix_parens:
|
||||
self.has_explicit_title = False # treat as implicit
|
||||
text, target = self.update_title_and_target(text, "")
|
||||
|
||||
node = self.innernodeclass(self.rawtext, text, classes=self.classes)
|
||||
return self.result_nodes(self.inliner.document, self.env, node, is_ref=False)
|
||||
|
||||
def create_xref_node(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
target = self.target
|
||||
title = self.title
|
||||
if self.lowercase:
|
||||
target = target.lower()
|
||||
if self.fix_parens:
|
||||
title, target = self._fix_parens(
|
||||
env, has_explicit_title, title, target)
|
||||
title, target = self.update_title_and_target(title, target)
|
||||
|
||||
# create the reference node
|
||||
refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain,
|
||||
refexplicit=has_explicit_title)
|
||||
# we may need the line number for warnings
|
||||
set_role_source_info(inliner, lineno, refnode)
|
||||
title, target = self.process_link(env, refnode, has_explicit_title, title, target)
|
||||
# now that the target and title are finally determined, set them
|
||||
options = {'refdoc': self.env.docname,
|
||||
'refdomain': self.refdomain,
|
||||
'reftype': self.reftype,
|
||||
'refexplicit': self.has_explicit_title,
|
||||
'refwarn': self.warn_dangling}
|
||||
refnode = self.nodeclass(self.rawtext, **options)
|
||||
self.set_source_info(refnode)
|
||||
|
||||
# determine the target and title for the class
|
||||
title, target = self.process_link(self.env, refnode, self.has_explicit_title,
|
||||
title, target)
|
||||
refnode['reftarget'] = target
|
||||
refnode += self.innernodeclass(rawtext, title, classes=classes)
|
||||
# we also need the source document
|
||||
refnode['refdoc'] = env.docname
|
||||
refnode['refwarn'] = self.warn_dangling
|
||||
# result_nodes allow further modification of return values
|
||||
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
|
||||
refnode += self.innernodeclass(self.rawtext, title, classes=self.classes)
|
||||
|
||||
return self.result_nodes(self.inliner.document, self.env, refnode, is_ref=True)
|
||||
|
||||
# methods that can be overwritten
|
||||
|
||||
@@ -179,6 +200,8 @@ class AnyXRefRole(XRefRole):
|
||||
def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
"""Role for PEP/RFC references that generate an index entry."""
|
||||
warnings.warn('indexmarkup_role() is deprecated. Please use PEP or RFC class instead.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
env = inliner.document.settings.env
|
||||
if not typ:
|
||||
assert env.temp_data['default_role']
|
||||
@@ -242,11 +265,87 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]
|
||||
raise ValueError('unknown role type: %s' % typ)
|
||||
|
||||
|
||||
class PEP(ReferenceRole):
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
target_id = 'index-%s' % self.env.new_serialno('index')
|
||||
entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
|
||||
target_id, '', None)]
|
||||
|
||||
index = addnodes.index(entries=entries)
|
||||
target = nodes.target('', '', ids=[target_id])
|
||||
self.inliner.document.note_explicit_target(target)
|
||||
|
||||
try:
|
||||
refuri = self.build_uri()
|
||||
reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['pep'])
|
||||
if self.has_explicit_title:
|
||||
reference += nodes.strong(self.title, self.title)
|
||||
else:
|
||||
title = "PEP " + self.title
|
||||
reference += nodes.strong(title, title)
|
||||
except ValueError:
|
||||
msg = self.inliner.reporter.error('invalid PEP number %s' % self.target,
|
||||
line=self.lineno)
|
||||
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
|
||||
return [prb], [msg]
|
||||
|
||||
return [index, target, reference], []
|
||||
|
||||
def build_uri(self):
|
||||
# type: () -> str
|
||||
base_url = self.inliner.document.settings.pep_base_url
|
||||
ret = self.target.split('#', 1)
|
||||
if len(ret) == 2:
|
||||
return base_url + 'pep-%04d#%s' % (int(ret[0]), ret[1])
|
||||
else:
|
||||
return base_url + 'pep-%04d' % int(ret[0])
|
||||
|
||||
|
||||
class RFC(ReferenceRole):
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
target_id = 'index-%s' % self.env.new_serialno('index')
|
||||
entries = [('single', 'RFC; RFC %s' % self.target, target_id, '', None)]
|
||||
|
||||
index = addnodes.index(entries=entries)
|
||||
target = nodes.target('', '', ids=[target_id])
|
||||
self.inliner.document.note_explicit_target(target)
|
||||
|
||||
try:
|
||||
refuri = self.build_uri()
|
||||
reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['rfc'])
|
||||
if self.has_explicit_title:
|
||||
reference += nodes.strong(self.title, self.title)
|
||||
else:
|
||||
title = "RFC " + self.title
|
||||
reference += nodes.strong(title, title)
|
||||
except ValueError:
|
||||
msg = self.inliner.reporter.error('invalid RFC number %s' % self.target,
|
||||
line=self.lineno)
|
||||
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
|
||||
return [prb], [msg]
|
||||
|
||||
return [index, target, reference], []
|
||||
|
||||
def build_uri(self):
|
||||
# type: () -> str
|
||||
base_url = self.inliner.document.settings.rfc_base_url
|
||||
ret = self.target.split('#', 1)
|
||||
if len(ret) == 2:
|
||||
return base_url + self.inliner.rfc_url % int(ret[0]) + '#' + ret[1]
|
||||
else:
|
||||
return base_url + self.inliner.rfc_url % int(ret[0])
|
||||
|
||||
|
||||
_amp_re = re.compile(r'(?<!&)&(?![&\s])')
|
||||
|
||||
|
||||
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
warnings.warn('menusel_role() is deprecated. '
|
||||
'Please use MenuSelection or GUILabel class instead.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
env = inliner.document.settings.env
|
||||
if not typ:
|
||||
assert env.temp_data['default_role']
|
||||
@@ -279,6 +378,32 @@ def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
return [node], []
|
||||
|
||||
|
||||
class GUILabel(SphinxRole):
|
||||
amp_re = re.compile(r'(?<!&)&(?![&\s])')
|
||||
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
node = nodes.inline(rawtext=self.rawtext, classes=[self.name])
|
||||
spans = self.amp_re.split(self.text)
|
||||
node += nodes.Text(spans.pop(0))
|
||||
for span in spans:
|
||||
span = span.replace('&&', '&')
|
||||
|
||||
letter = nodes.Text(span[0])
|
||||
accelerator = nodes.inline('', '', letter, classes=['accelerator'])
|
||||
node += accelerator
|
||||
node += nodes.Text(span[1:])
|
||||
|
||||
return [node], []
|
||||
|
||||
|
||||
class MenuSelection(GUILabel):
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
self.text = self.text.replace('-->', '\N{TRIANGULAR BULLET}') # type: ignore
|
||||
return super().run()
|
||||
|
||||
|
||||
_litvar_re = re.compile('{([^}]+)}')
|
||||
parens_re = re.compile(r'(\\*{|\\*})')
|
||||
|
||||
@@ -286,6 +411,9 @@ parens_re = re.compile(r'(\\*{|\\*})')
|
||||
def emph_literal_role(typ, rawtext, text, lineno, inliner,
|
||||
options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
warnings.warn('emph_literal_role() is deprecated. '
|
||||
'Please use EmphasizedLiteral class instead.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
env = inliner.document.settings.env
|
||||
if not typ:
|
||||
assert env.temp_data['default_role']
|
||||
@@ -333,11 +461,65 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner,
|
||||
return [retnode], []
|
||||
|
||||
|
||||
class EmphasizedLiteral(SphinxRole):
|
||||
parens_re = re.compile(r'(\\\\|\\{|\\}|{|})')
|
||||
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
children = self.parse(self.text)
|
||||
node = nodes.literal(self.rawtext, '', *children,
|
||||
role=self.name.lower(), classes=[self.name])
|
||||
|
||||
return [node], []
|
||||
|
||||
def parse(self, text):
|
||||
# type: (str) -> List[nodes.Node]
|
||||
result = [] # type: List[nodes.Node]
|
||||
|
||||
stack = ['']
|
||||
for part in self.parens_re.split(text):
|
||||
if part == '\\\\': # escaped backslash
|
||||
stack[-1] += '\\'
|
||||
elif part == '{':
|
||||
if len(stack) >= 2 and stack[-2] == "{": # nested
|
||||
stack[-1] += "{"
|
||||
else:
|
||||
# start emphasis
|
||||
stack.append('{')
|
||||
stack.append('')
|
||||
elif part == '}':
|
||||
if len(stack) == 3 and stack[1] == "{" and len(stack[2]) > 0:
|
||||
# emphasized word found
|
||||
if stack[0]:
|
||||
result.append(nodes.Text(stack[0], stack[0]))
|
||||
result.append(nodes.emphasis(stack[2], stack[2]))
|
||||
stack = ['']
|
||||
else:
|
||||
# emphasized word not found; the rparen is not a special symbol
|
||||
stack.append('}')
|
||||
stack = [''.join(stack)]
|
||||
elif part == '\\{': # escaped left-brace
|
||||
stack[-1] += '{'
|
||||
elif part == '\\}': # escaped right-brace
|
||||
stack[-1] += '}'
|
||||
else: # others (containing escaped braces)
|
||||
stack[-1] += part
|
||||
|
||||
if ''.join(stack):
|
||||
# remaining is treated as Text
|
||||
text = ''.join(stack)
|
||||
result.append(nodes.Text(text, text))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
_abbr_re = re.compile(r'\((.*)\)$', re.S)
|
||||
|
||||
|
||||
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
warnings.warn('abbr_role() is deprecated. Please use Abbrevation class instead.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
text = utils.unescape(text)
|
||||
m = _abbr_re.search(text)
|
||||
if m is None:
|
||||
@@ -349,8 +531,25 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
return [nodes.abbreviation(abbr, abbr, **options)], []
|
||||
|
||||
|
||||
class Abbreviation(SphinxRole):
|
||||
abbr_re = re.compile(r'\((.*)\)$', re.S)
|
||||
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
matched = self.abbr_re.search(self.text)
|
||||
if matched:
|
||||
text = self.text[:matched.start()].strip()
|
||||
self.options['explanation'] = matched.group(1)
|
||||
else:
|
||||
text = self.text
|
||||
|
||||
return [nodes.abbreviation(self.rawtext, text, **self.options)], []
|
||||
|
||||
|
||||
def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
warnings.warn('index_role() is deprecated. Please use Index class instead.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
# create new reference target
|
||||
env = inliner.document.settings.env
|
||||
targetid = 'index-%s' % env.new_serialno('index')
|
||||
@@ -378,20 +577,44 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
return [indexnode, targetnode, textnode], []
|
||||
|
||||
|
||||
class Index(ReferenceRole):
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
target_id = 'index-%s' % self.env.new_serialno('index')
|
||||
if self.has_explicit_title:
|
||||
# if an explicit target is given, process it as a full entry
|
||||
title = self.title
|
||||
entries = process_index_entry(self.target, target_id)
|
||||
else:
|
||||
# otherwise we just create a single entry
|
||||
if self.target.startswith('!'):
|
||||
title = self.title[1:]
|
||||
entries = [('single', self.target[1:], target_id, 'main', None)]
|
||||
else:
|
||||
title = self.title
|
||||
entries = [('single', self.target, target_id, '', None)]
|
||||
|
||||
index = addnodes.index(entries=entries)
|
||||
target = nodes.target('', '', ids=[target_id])
|
||||
text = nodes.Text(title, title)
|
||||
self.set_source_info(index)
|
||||
return [index, target, text], []
|
||||
|
||||
|
||||
specific_docroles = {
|
||||
# links to download references
|
||||
'download': XRefRole(nodeclass=addnodes.download_reference),
|
||||
# links to anything
|
||||
'any': AnyXRefRole(warn_dangling=True),
|
||||
|
||||
'pep': indexmarkup_role,
|
||||
'rfc': indexmarkup_role,
|
||||
'guilabel': menusel_role,
|
||||
'menuselection': menusel_role,
|
||||
'file': emph_literal_role,
|
||||
'samp': emph_literal_role,
|
||||
'abbr': abbr_role,
|
||||
'index': index_role,
|
||||
'pep': PEP(),
|
||||
'rfc': RFC(),
|
||||
'guilabel': GUILabel(),
|
||||
'menuselection': MenuSelection(),
|
||||
'file': EmphasizedLiteral(),
|
||||
'samp': EmphasizedLiteral(),
|
||||
'abbr': Abbreviation(),
|
||||
'index': Index(),
|
||||
} # type: Dict[str, RoleFunction]
|
||||
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ from docutils import nodes
|
||||
from sphinx import addnodes
|
||||
from sphinx import package_dir
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.util import jsdump, rpartition
|
||||
from sphinx.search.jssplitter import splitter_code
|
||||
from sphinx.util import jsdump, rpartition
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>{{ title|e }}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta http-equiv="refresh" content="0;url={{ toc|e }}" />
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
31
sphinx/templates/htmlhelp/project.hhc
Normal file
31
sphinx/templates/htmlhelp/project.hhc
Normal file
@@ -0,0 +1,31 @@
|
||||
{%- macro sitemap(name, docname) -%}
|
||||
<OBJECT type="text/sitemap">
|
||||
<PARAM name="Name" value="{{ name|e }}" />
|
||||
<PARAM name="Local" value="{{ docname|e }}{{ suffix }}" />
|
||||
</OBJECT>
|
||||
{%- endmacro -%}
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1" />
|
||||
<!-- Sitemap 1.0 -->
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<OBJECT type="text/site properties">
|
||||
<PARAM name="Window Styles" value="0x801227" />
|
||||
<PARAM name="ImageType" value="Folder" />
|
||||
</OBJECT>
|
||||
<UL>
|
||||
<LI>
|
||||
{{ sitemap(short_title, master_doc)|indent(8) }}
|
||||
</LI>
|
||||
{%- for indexname, indexcls, content, collapse in domain_indices %}
|
||||
<LI>
|
||||
{{ sitemap(indexcls.localname, indexname)|indent(8) }}
|
||||
</LI>
|
||||
{%- endfor %}
|
||||
{{ body|indent(6) }}
|
||||
</UL>
|
||||
</BODY>
|
||||
</HTML>
|
||||
21
sphinx/templates/htmlhelp/project.hhp
Normal file
21
sphinx/templates/htmlhelp/project.hhp
Normal file
@@ -0,0 +1,21 @@
|
||||
[OPTIONS]
|
||||
Binary TOC=No
|
||||
Binary Index=No
|
||||
Compiled file={{ outname }}.chm
|
||||
Contents file={{ outname }}.hhc
|
||||
Default Window={{ outname }}
|
||||
Default topic={{ master_doc }}
|
||||
Display compile progress=No
|
||||
Full text search stop list file={{ outname }}.stp
|
||||
Full-text search=Yes
|
||||
Index file={{ outname }}.hhk
|
||||
Language={{ "%#x"|format(lcid) }}
|
||||
Title={{ title }}
|
||||
|
||||
[WINDOWS]
|
||||
{{ outname }}="{{ title }}","{{ outname }}.hhc","{{ outname }}.hhk","{{ master_doc }}","{{ master_doc }}",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0
|
||||
|
||||
[FILES]
|
||||
{%- for filename in files %}
|
||||
{{ filename }}
|
||||
{%- endfor %}
|
||||
33
sphinx/templates/htmlhelp/project.stp
Normal file
33
sphinx/templates/htmlhelp/project.stp
Normal file
@@ -0,0 +1,33 @@
|
||||
a
|
||||
and
|
||||
are
|
||||
as
|
||||
at
|
||||
be
|
||||
but
|
||||
by
|
||||
for
|
||||
if
|
||||
in
|
||||
into
|
||||
is
|
||||
it
|
||||
near
|
||||
no
|
||||
not
|
||||
of
|
||||
on
|
||||
or
|
||||
such
|
||||
that
|
||||
the
|
||||
their
|
||||
then
|
||||
there
|
||||
these
|
||||
they
|
||||
this
|
||||
to
|
||||
was
|
||||
will
|
||||
with
|
||||
@@ -30,10 +30,16 @@ project = {{ project | repr }}
|
||||
copyright = {{ copyright | repr }}
|
||||
author = {{ author | repr }}
|
||||
|
||||
{%- if version %}
|
||||
|
||||
# The short X.Y version
|
||||
version = {{ version | repr }}
|
||||
{%- endif %}
|
||||
{%- if release %}
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = {{ release | repr }}
|
||||
{%- endif %}
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
@@ -50,17 +56,20 @@ extensions = [
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['{{ dot }}templates']
|
||||
|
||||
{% if suffix != '.rst' -%}
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = {{ suffix | repr }}
|
||||
|
||||
{% if master_doc != 'index' -%}
|
||||
{% endif -%}
|
||||
{% if master != 'index' -%}
|
||||
# The master toctree document.
|
||||
master_doc = {{ master | repr }}
|
||||
|
||||
{% endif -%}
|
||||
{% if language -%}
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
@@ -68,6 +77,7 @@ master_doc = {{ master | repr }}
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = {{ language | repr }}
|
||||
|
||||
{% endif -%}
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
@@ -81,44 +91,10 @@ exclude_patterns = [{{ exclude_patterns }}]
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['{{ dot }}static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
|
||||
# -- Options for Epub output -------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = project
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#
|
||||
# epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#
|
||||
# epub_uid = ''
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
{%- if extensions %}
|
||||
|
||||
|
||||
|
||||
@@ -17,15 +17,16 @@ html: $(addsuffix .html,$(ALLDOCS))
|
||||
pdf: $(addsuffix .pdf,$(ALLDOCS))
|
||||
|
||||
install-info: info
|
||||
for f in *.info; do \\
|
||||
cp -t $(infodir) "$$f" && \\
|
||||
$(INSTALL_INFO) --info-dir=$(infodir) "$$f" ; \\
|
||||
for f in *.info; do \
|
||||
mkdir -p $(infodir) && \
|
||||
cp "$$f" $(infodir) && \
|
||||
$(INSTALL_INFO) --info-dir=$(infodir) "$$f" ; \
|
||||
done
|
||||
|
||||
uninstall-info: info
|
||||
for f in *.info; do \\
|
||||
rm -f "$(infodir)/$$f" ; \\
|
||||
$(INSTALL_INFO) --delete --info-dir=$(infodir) "$$f" ; \\
|
||||
for f in *.info; do \
|
||||
rm -f "$(infodir)/$$f" ; \
|
||||
$(INSTALL_INFO) --delete --info-dir=$(infodir) "$$f" ; \
|
||||
done
|
||||
|
||||
%.info: %.texi
|
||||
|
||||
@@ -707,53 +707,79 @@
|
||||
|
||||
%% GRAPHICS
|
||||
%
|
||||
% \sphinxincludegraphics defined to resize images larger than the line width,
|
||||
% except if height or width option present.
|
||||
% \sphinxincludegraphics resizes images larger than the TeX \linewidth (which
|
||||
% is adjusted in indented environments), or taller than a certain maximal
|
||||
% height (usually \textheight and this is reduced in the environments which use
|
||||
% framed.sty to avoid infinite loop if image too tall).
|
||||
%
|
||||
% If scale is present, rescale before fitting to line width. (since 1.5)
|
||||
\newbox\spx@image@box
|
||||
\newcommand*{\sphinxincludegraphics}[2][]{%
|
||||
\in@{height}{#1}\ifin@\else\in@{width}{#1}\fi
|
||||
\ifin@ % height or width present
|
||||
\includegraphics[#1]{#2}%
|
||||
\else % no height nor width (but #1 may be "scale=...")
|
||||
% In case height or width options are present the rescaling is done
|
||||
% (since 2.0), in a way keeping the width:height ratio either native from
|
||||
% image or from the width and height options if both were present.
|
||||
%
|
||||
\newdimen\spx@image@maxheight
|
||||
\AtBeginDocument{\spx@image@maxheight\textheight}
|
||||
|
||||
% box scratch register
|
||||
\newdimen\spx@image@box
|
||||
\newcommand*{\sphinxsafeincludegraphics}[2][]{%
|
||||
% #1 contains possibly width=, height=, but no scale= since 1.8.4
|
||||
\setbox\spx@image@box\hbox{\includegraphics[#1,draft]{#2}}%
|
||||
\in@false % use some handy boolean flag
|
||||
\ifdim \wd\spx@image@box>\linewidth
|
||||
\setbox\spx@image@box\box\voidb@x % clear memory
|
||||
\includegraphics[#1,width=\linewidth]{#2}%
|
||||
\in@true % flag to remember to adjust options and set box dimensions
|
||||
% compute height which results from rescaling width to \linewidth
|
||||
% and keep current aspect ratio. multiply-divide in \numexpr uses
|
||||
% temporarily doubled precision, hence no overflow. (of course we
|
||||
% assume \ht is not a few sp's below \maxdimen...(about 16384pt).
|
||||
\edef\spx@image@rescaledheight % with sp units
|
||||
{\the\numexpr\ht\spx@image@box
|
||||
*\linewidth/\wd\spx@image@box sp}%
|
||||
\ifdim\spx@image@rescaledheight>\spx@image@maxheight
|
||||
% the rescaled height will be too big, so it is height which decides
|
||||
% the rescaling factor
|
||||
\def\spx@image@requiredheight{\spx@image@maxheight}% dimen register
|
||||
\edef\spx@image@requiredwidth % with sp units
|
||||
{\the\numexpr\wd\spx@image@box
|
||||
*\spx@image@maxheight/\ht\spx@image@box sp}%
|
||||
% TODO: decide if this commented-out block could be needed due to
|
||||
% rounding in numexpr operations going up
|
||||
% \ifdim\spx@image@requiredwidth>\linewidth
|
||||
% \def\spx@image@requiredwidth{\linewidth}% dimen register
|
||||
% \fi
|
||||
\else
|
||||
\def\spx@image@requiredwidth{\linewidth}% dimen register
|
||||
\let\spx@image@requiredheight\spx@image@rescaledheight% sp units
|
||||
\fi
|
||||
\else
|
||||
% width is ok, let's check height
|
||||
\ifdim\ht\spx@image@box>\spx@image@maxheight
|
||||
\in@true
|
||||
\edef\spx@image@requiredwidth % with sp units
|
||||
{\the\numexpr\wd\spx@image@box
|
||||
*\spx@image@maxheight/\ht\spx@image@box sp}%
|
||||
\def\spx@image@requiredheight{\spx@image@maxheight}% dimen register
|
||||
\fi
|
||||
\fi % end of check of width and height
|
||||
\ifin@
|
||||
\setbox\spx@image@box
|
||||
\hbox{\includegraphics
|
||||
[%#1,% contained only width and/or height and overruled anyhow
|
||||
width=\spx@image@requiredwidth,height=\spx@image@requiredheight]%
|
||||
{#2}}%
|
||||
% \includegraphics does not set box dimensions to the exactly
|
||||
% requested ones, see https://github.com/latex3/latex2e/issues/112
|
||||
\wd\spx@image@box\spx@image@requiredwidth
|
||||
\ht\spx@image@box\spx@image@requiredheight
|
||||
\leavevmode\box\spx@image@box
|
||||
\else
|
||||
% here we do not modify the options, no need to adjust width and height
|
||||
% on output, they will be computed exactly as with "draft" option
|
||||
\setbox\spx@image@box\box\voidb@x % clear memory
|
||||
\includegraphics[#1]{#2}%
|
||||
\fi
|
||||
\fi
|
||||
}
|
||||
% \sphinxsafeincludegraphics resizes images larger than the line width,
|
||||
% or taller than about the text height (whether or not height/width options
|
||||
% were used). This is requested to avoid a crash with \MakeFramed as used by
|
||||
% sphinxShadowBox (topic/contents) and sphinxheavybox (admonitions), and also
|
||||
% by sphinxVerbatim (but a priori no image inclusion there).
|
||||
\newdimen\spx@image@maxheight
|
||||
% default maximal setting will get reduced by sphinxShadowBox/sphinxheavybox
|
||||
\AtBeginDocument{\spx@image@maxheight\textheight}
|
||||
\newcommand*{\sphinxsafeincludegraphics}[2][]{%
|
||||
\gdef\spx@includegraphics@options{#1}%
|
||||
\setbox\spx@image@box\hbox{\includegraphics[#1,draft]{#2}}%
|
||||
\in@false
|
||||
\ifdim \wd\spx@image@box>\linewidth
|
||||
\g@addto@macro\spx@includegraphics@options{,width=\linewidth}%
|
||||
\in@true
|
||||
\fi
|
||||
% no rotation, no need to worry about depth
|
||||
\ifdim \ht\spx@image@box>\spx@image@maxheight
|
||||
\g@addto@macro\spx@includegraphics@options{,height=\spx@image@maxheight}%
|
||||
\in@true
|
||||
\fi
|
||||
\ifin@
|
||||
\g@addto@macro\spx@includegraphics@options{,keepaspectratio}%
|
||||
\fi
|
||||
\setbox\spx@image@box\box\voidb@x % clear memory
|
||||
\expandafter\includegraphics\expandafter[\spx@includegraphics@options]{#2}%
|
||||
}%
|
||||
% Use the "safe" one by default (2.0)
|
||||
\def\sphinxincludegraphics{\sphinxsafeincludegraphics}
|
||||
|
||||
|
||||
%% FIGURE IN TABLE
|
||||
@@ -1374,7 +1400,6 @@
|
||||
+2\sphinxshadowsep
|
||||
+\sphinxshadowsize
|
||||
+\baselineskip\relax
|
||||
\let\sphinxincludegraphics\sphinxsafeincludegraphics
|
||||
% configure framed.sty not to add extra vertical spacing
|
||||
\ltx@ifundefined{OuterFrameSep}{}{\OuterFrameSep\z@skip}%
|
||||
% the \trivlist will add the vertical spacing on top and bottom which is
|
||||
@@ -1461,7 +1486,6 @@
|
||||
-\dimexpr2\FrameRule
|
||||
+2\FrameSep
|
||||
+\baselineskip\relax % will happen again if nested, needed indeed!
|
||||
\let\sphinxincludegraphics\sphinxsafeincludegraphics
|
||||
% configure framed.sty's parameters to obtain same vertical spacing
|
||||
% as for "light" boxes. We need for this to manually insert parskip glue and
|
||||
% revert a skip done by framed before the frame.
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
{\Large
|
||||
\begin{tabular}[t]{c}
|
||||
\@author
|
||||
\end{tabular}}\par
|
||||
\end{tabular}\kern-\tabcolsep}\par
|
||||
\vspace{25pt}
|
||||
\@date \par
|
||||
\py@authoraddress \par
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
{\LARGE
|
||||
\begin{tabular}[t]{c}
|
||||
\@author
|
||||
\end{tabular}
|
||||
\end{tabular}\kern-\tabcolsep
|
||||
\par}
|
||||
\vfill\vfill
|
||||
{\large
|
||||
|
||||
@@ -14,12 +14,7 @@
|
||||
<script type="text/javascript" src="{{ pathto('_static/searchtools.js', 1) }}"></script>
|
||||
{%- endblock %}
|
||||
{% block extrahead %}
|
||||
<script type="text/javascript">
|
||||
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
|
||||
</script>
|
||||
{# this is used when loading the search index using $.ajax fails,
|
||||
such as on Chrome for documents on localhost #}
|
||||
<script type="text/javascript" id="searchindexloader"></script>
|
||||
<script type="text/javascript" src="{{ pathto('searchindex.js', 1) }}" defer></script>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
@@ -231,6 +231,16 @@ a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
a.brackets:before,
|
||||
span.brackets > a:before{
|
||||
content: "[";
|
||||
}
|
||||
|
||||
a.brackets:after,
|
||||
span.brackets > a:after {
|
||||
content: "]";
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
@@ -391,6 +401,16 @@ table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
th > p:first-child,
|
||||
td > p:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
th > p:last-child,
|
||||
td > p:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/* -- figures --------------------------------------------------------------- */
|
||||
|
||||
div.figure {
|
||||
@@ -460,11 +480,57 @@ ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
li > p:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
li > p:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
dl.footnote > dt,
|
||||
dl.citation > dt {
|
||||
float: left;
|
||||
}
|
||||
|
||||
dl.footnote > dd,
|
||||
dl.citation > dd {
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
dl.footnote > dd:after,
|
||||
dl.citation > dd:after {
|
||||
content: "";
|
||||
clear: both;
|
||||
}
|
||||
|
||||
dl.field-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
dl.field-list > dt {
|
||||
flex-basis: 20%;
|
||||
font-weight: bold;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
dl.field-list > dt:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
dl.field-list > dd {
|
||||
flex-basis: 70%;
|
||||
padding-left: 1em;
|
||||
margin-left: 0em;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
dd > p:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,16 +75,6 @@ var Search = {
|
||||
}
|
||||
},
|
||||
|
||||
loadIndex : function(url) {
|
||||
$.ajax({type: "GET", url: url, data: null,
|
||||
dataType: "script", cache: true,
|
||||
complete: function(jqxhr, textstatus) {
|
||||
if (textstatus != "success") {
|
||||
document.getElementById("searchindexloader").src = url;
|
||||
}
|
||||
}});
|
||||
},
|
||||
|
||||
setIndex : function(index) {
|
||||
var q;
|
||||
this._index = index;
|
||||
@@ -130,7 +120,7 @@ var Search = {
|
||||
this.out = $('#search-results');
|
||||
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
|
||||
this.dots = $('<span></span>').appendTo(this.title);
|
||||
this.status = $('<p style="display: none"></p>').appendTo(this.out);
|
||||
this.status = $('<p class="search-summary"> </p>').appendTo(this.out);
|
||||
this.output = $('<ul class="search"/>').appendTo(this.out);
|
||||
|
||||
$('#search-progress').text(_('Preparing search...'));
|
||||
|
||||
@@ -23,7 +23,7 @@ from sphinx.locale import _, __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docutils import new_document
|
||||
from sphinx.util.i18n import format_date
|
||||
from sphinx.util.nodes import apply_source_workaround, is_smartquotable
|
||||
from sphinx.util.nodes import NodeMatcher, apply_source_workaround, is_smartquotable
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -309,6 +309,19 @@ class UnreferencedFootnotesDetector(SphinxTransform):
|
||||
location=node)
|
||||
|
||||
|
||||
class FigureAligner(SphinxTransform):
|
||||
"""
|
||||
Align figures to center by default.
|
||||
"""
|
||||
default_priority = 700
|
||||
|
||||
def apply(self, **kwargs):
|
||||
# type: (Any) -> None
|
||||
matcher = NodeMatcher(nodes.table, nodes.figure)
|
||||
for node in self.document.traverse(matcher): # type: nodes.Element
|
||||
node.setdefault('align', 'center')
|
||||
|
||||
|
||||
class FilterSystemMessages(SphinxTransform):
|
||||
"""Filter system messages from a doctree."""
|
||||
default_priority = 999
|
||||
|
||||
@@ -21,7 +21,7 @@ from sphinx.domains.std import make_glossary_term, split_term_classifiers
|
||||
from sphinx.locale import __, init as init_locale
|
||||
from sphinx.transforms import SphinxTransform
|
||||
from sphinx.util import split_index_msg, logging
|
||||
from sphinx.util.i18n import find_catalog
|
||||
from sphinx.util.i18n import docname_to_domain
|
||||
from sphinx.util.nodes import (
|
||||
LITERAL_TYPE_NODES, IMAGE_TYPE_NODES, NodeMatcher,
|
||||
extract_messages, is_pending_meta, traverse_translatable_index,
|
||||
@@ -94,7 +94,7 @@ class Locale(SphinxTransform):
|
||||
assert source.startswith(self.env.srcdir)
|
||||
docname = path.splitext(relative_path(path.join(self.env.srcdir, 'dummy'),
|
||||
source))[0]
|
||||
textdomain = find_catalog(docname, self.config.gettext_compact)
|
||||
textdomain = docname_to_domain(docname, self.config.gettext_compact)
|
||||
|
||||
# fetch translations
|
||||
dirs = [path.join(self.env.srcdir, directory)
|
||||
|
||||
@@ -89,7 +89,7 @@ class HighlightLanguageVisitor(nodes.NodeVisitor):
|
||||
if 'language' not in node:
|
||||
node['language'] = setting.language
|
||||
node['force_highlighting'] = False
|
||||
else:
|
||||
elif 'force_highlighting' not in node:
|
||||
node['force_highlighting'] = True
|
||||
if 'linenos' not in node:
|
||||
lines = node.astext().count('\n')
|
||||
|
||||
@@ -603,22 +603,26 @@ class PeekableIterator:
|
||||
|
||||
def import_object(objname, source=None):
|
||||
# type: (str, str) -> Any
|
||||
"""Import python object by qualname."""
|
||||
try:
|
||||
module, name = objname.rsplit('.', 1)
|
||||
except ValueError as err:
|
||||
raise ExtensionError('Invalid full object name %s' % objname +
|
||||
(source and ' (needed for %s)' % source or ''),
|
||||
err)
|
||||
try:
|
||||
return getattr(__import__(module, None, None, [name]), name)
|
||||
except ImportError as err:
|
||||
raise ExtensionError('Could not import %s' % module +
|
||||
(source and ' (needed for %s)' % source or ''),
|
||||
err)
|
||||
except AttributeError as err:
|
||||
raise ExtensionError('Could not find %s' % objname +
|
||||
(source and ' (needed for %s)' % source or ''),
|
||||
err)
|
||||
objpath = objname.split('.')
|
||||
modname = objpath.pop(0)
|
||||
obj = __import__(modname)
|
||||
for name in objpath:
|
||||
modname += '.' + name
|
||||
try:
|
||||
obj = getattr(obj, name)
|
||||
except AttributeError:
|
||||
__import__(modname)
|
||||
obj = getattr(obj, name)
|
||||
|
||||
return obj
|
||||
except (AttributeError, ImportError) as exc:
|
||||
if source:
|
||||
raise ExtensionError('Could not import %s (needed for %s)' %
|
||||
(objname, source), exc)
|
||||
else:
|
||||
raise ExtensionError('Could not import %s' % objname, exc)
|
||||
|
||||
|
||||
def encode_uri(uri):
|
||||
|
||||
@@ -27,6 +27,12 @@ _ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm')
|
||||
codes = {} # type: Dict[str, str]
|
||||
|
||||
|
||||
def terminal_safe(s):
|
||||
# type: (str) -> str
|
||||
"""safely encode a string for printing to the terminal."""
|
||||
return s.encode('ascii', 'backslashreplace').decode('ascii')
|
||||
|
||||
|
||||
def get_terminal_width():
|
||||
# type: () -> int
|
||||
"""Borrowed from the py lib."""
|
||||
|
||||
@@ -23,10 +23,10 @@ from docutils import nodes
|
||||
from docutils.io import FileOutput
|
||||
from docutils.parsers.rst import Directive, directives, roles, convert_directive_function
|
||||
from docutils.statemachine import StateMachine
|
||||
from docutils.utils import Reporter
|
||||
from docutils.utils import Reporter, unescape
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.errors import ExtensionError
|
||||
from sphinx.errors import ExtensionError, SphinxError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
|
||||
@@ -36,7 +36,8 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
|
||||
if False:
|
||||
# For type annotation
|
||||
from types import ModuleType # NOQA
|
||||
from typing import Any, Callable, Generator, List, Set, Tuple, Type # NOQA
|
||||
from typing import Any, Callable, Dict, Generator, List, Set, Tuple, Type # NOQA
|
||||
from docutils.parsers.rst.states import Inliner # NOQA
|
||||
from docutils.statemachine import State, StringList # NOQA
|
||||
from sphinx.builders import Builder # NOQA
|
||||
from sphinx.config import Config # NOQA
|
||||
@@ -383,6 +384,99 @@ class SphinxDirective(Directive):
|
||||
return self.env.config
|
||||
|
||||
|
||||
class SphinxRole:
|
||||
"""A base class for Sphinx roles.
|
||||
|
||||
This class provides helper methods for Sphinx roles.
|
||||
|
||||
.. note:: The subclasses of this class might not work with docutils.
|
||||
This class is strongly coupled with Sphinx.
|
||||
"""
|
||||
name = None #: The role name actually used in the document.
|
||||
rawtext = None #: A string containing the entire interpreted text input.
|
||||
text = None #: The interpreted text content.
|
||||
lineno = None #: The line number where the interpreted text begins.
|
||||
inliner = None #: The ``docutils.parsers.rst.states.Inliner`` object.
|
||||
options = None #: A dictionary of directive options for customization
|
||||
#: (from the "role" directive).
|
||||
content = None #: A list of strings, the directive content for customization
|
||||
#: (from the "role" directive).
|
||||
|
||||
def __call__(self, name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
self.rawtext = rawtext
|
||||
self.text = unescape(text)
|
||||
self.lineno = lineno
|
||||
self.inliner = inliner
|
||||
self.options = options
|
||||
self.content = content
|
||||
|
||||
# guess role type
|
||||
if name:
|
||||
self.name = name.lower()
|
||||
else:
|
||||
self.name = self.env.temp_data.get('default_role')
|
||||
if not self.name:
|
||||
self.name = self.env.config.default_role
|
||||
if not self.name:
|
||||
raise SphinxError('cannot determine default role!')
|
||||
|
||||
return self.run()
|
||||
|
||||
def run(self):
|
||||
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
# type: () -> BuildEnvironment
|
||||
"""Reference to the :class:`.BuildEnvironment` object."""
|
||||
return self.inliner.document.settings.env
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
# type: () -> Config
|
||||
"""Reference to the :class:`.Config` object."""
|
||||
return self.env.config
|
||||
|
||||
def set_source_info(self, node, lineno=None):
|
||||
# type: (nodes.Node, int) -> None
|
||||
if lineno is None:
|
||||
lineno = self.lineno
|
||||
|
||||
source_info = self.inliner.reporter.get_source_and_line(lineno) # type: ignore
|
||||
node.source, node.line = source_info
|
||||
|
||||
|
||||
class ReferenceRole(SphinxRole):
|
||||
"""A base class for reference roles.
|
||||
|
||||
The reference roles can accpet ``link title <target>`` style as a text for
|
||||
the role. The parsed result; link title and target will be stored to
|
||||
``self.title`` and ``self.target``.
|
||||
"""
|
||||
has_explicit_title = None #: A boolean indicates the role has explicit title or not.
|
||||
title = None #: The link title for the interpreted text.
|
||||
target = None #: The link target for the interpreted text.
|
||||
|
||||
# \x00 means the "<" was backslash-escaped
|
||||
explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)
|
||||
|
||||
def __call__(self, name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
|
||||
matched = self.explicit_title_re.match(text)
|
||||
if matched:
|
||||
self.has_explicit_title = True
|
||||
self.title = unescape(matched.group(1))
|
||||
self.target = unescape(matched.group(2))
|
||||
else:
|
||||
self.has_explicit_title = False
|
||||
self.title = unescape(text)
|
||||
self.target = unescape(text)
|
||||
|
||||
return super().__call__(name, rawtext, text, lineno, inliner, options, content)
|
||||
|
||||
|
||||
class SphinxTranslator(nodes.NodeVisitor):
|
||||
"""A base class for Sphinx translators.
|
||||
|
||||
|
||||
@@ -19,19 +19,19 @@ import babel.dates
|
||||
from babel.messages.mofile import write_mo
|
||||
from babel.messages.pofile import read_po
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.matching import Matcher
|
||||
from sphinx.util.osutil import SEP, relpath
|
||||
from sphinx.util.osutil import SEP, canon_path, relpath
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Callable, List, Set # NOQA
|
||||
from typing import Callable, Generator, List, Set, Tuple # NOQA
|
||||
from sphinx.environment import BuildEnvironment # NOQA
|
||||
|
||||
LocaleFileInfoBase = namedtuple('CatalogInfo', 'base_dir,domain,charset')
|
||||
@@ -81,8 +81,55 @@ class CatalogInfo(LocaleFileInfoBase):
|
||||
logger.warning(__('writing error: %s, %s'), self.mo_path, exc)
|
||||
|
||||
|
||||
class CatalogRepository:
|
||||
"""A repository for message catalogs."""
|
||||
|
||||
def __init__(self, basedir, locale_dirs, language, encoding):
|
||||
# type: (str, List[str], str, str) -> None
|
||||
self.basedir = basedir
|
||||
self._locale_dirs = locale_dirs
|
||||
self.language = language
|
||||
self.encoding = encoding
|
||||
|
||||
@property
|
||||
def locale_dirs(self):
|
||||
# type: () -> Generator[str, None, None]
|
||||
if not self.language:
|
||||
return
|
||||
|
||||
for locale_dir in self._locale_dirs:
|
||||
locale_dir = path.join(self.basedir, locale_dir)
|
||||
if path.exists(path.join(locale_dir, self.language, 'LC_MESSAGES')):
|
||||
yield locale_dir
|
||||
|
||||
@property
|
||||
def pofiles(self):
|
||||
# type: () -> Generator[Tuple[str, str], None, None]
|
||||
for locale_dir in self.locale_dirs:
|
||||
basedir = path.join(locale_dir, self.language, 'LC_MESSAGES')
|
||||
for root, dirnames, filenames in os.walk(basedir):
|
||||
# skip dot-directories
|
||||
for dirname in dirnames:
|
||||
if dirname.startswith('.'):
|
||||
dirnames.remove(dirname)
|
||||
|
||||
for filename in filenames:
|
||||
if filename.endswith('.po'):
|
||||
fullpath = path.join(root, filename)
|
||||
yield basedir, relpath(fullpath, basedir)
|
||||
|
||||
@property
|
||||
def catalogs(self):
|
||||
# type: () -> Generator[CatalogInfo, None, None]
|
||||
for basedir, filename in self.pofiles:
|
||||
domain = canon_path(path.splitext(filename)[0])
|
||||
yield CatalogInfo(basedir, domain, self.encoding)
|
||||
|
||||
|
||||
def find_catalog(docname, compaction):
|
||||
# type: (str, bool) -> str
|
||||
warnings.warn('find_catalog() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
if compaction:
|
||||
ret = docname.split(SEP, 1)[0]
|
||||
else:
|
||||
@@ -91,8 +138,19 @@ def find_catalog(docname, compaction):
|
||||
return ret
|
||||
|
||||
|
||||
def docname_to_domain(docname, compation):
|
||||
# type: (str, bool) -> str
|
||||
"""Convert docname to domain for catalogs."""
|
||||
if compation:
|
||||
return docname.split(SEP, 1)[0]
|
||||
else:
|
||||
return docname
|
||||
|
||||
|
||||
def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction):
|
||||
# type: (str, str, List[str], str, bool) -> List[str]
|
||||
warnings.warn('find_catalog_files() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
if not(lang and locale_dirs):
|
||||
return []
|
||||
|
||||
@@ -120,6 +178,8 @@ def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact
|
||||
default is False.
|
||||
:return: [CatalogInfo(), ...]
|
||||
"""
|
||||
warnings.warn('find_catalog_source_files() is deprecated.',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
if gettext_compact is not None:
|
||||
warnings.warn('gettext_compact argument for find_catalog_source_files() '
|
||||
'is deprecated.', RemovedInSphinx30Warning, stacklevel=2)
|
||||
|
||||
@@ -20,7 +20,7 @@ from io import StringIO
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx30Warning
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.pycompat import NoneType
|
||||
from sphinx.util.typing import NoneType
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
|
||||
@@ -136,11 +136,11 @@ class InventoryFile:
|
||||
if not m:
|
||||
continue
|
||||
name, type, prio, location, dispname = m.groups()
|
||||
if type == 'py:module' and type in invdata and \
|
||||
name in invdata[type]: # due to a bug in 1.1 and below,
|
||||
# two inventory entries are created
|
||||
# for Python modules, and the first
|
||||
# one is correct
|
||||
if type == 'py:module' and type in invdata and name in invdata[type]:
|
||||
# due to a bug in 1.1 and below,
|
||||
# two inventory entries are created
|
||||
# for Python modules, and the first
|
||||
# one is correct
|
||||
continue
|
||||
if location.endswith('$'):
|
||||
location = location[:-1] + name
|
||||
|
||||
@@ -9,13 +9,20 @@
|
||||
"""
|
||||
|
||||
import json
|
||||
import warnings
|
||||
from collections import UserString
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, IO # NOQA
|
||||
|
||||
|
||||
warnings.warn('sphinx.util.jsonimpl is deprecated',
|
||||
RemovedInSphinx40Warning, stacklevel=2)
|
||||
|
||||
|
||||
class SphinxJSONEncoder(json.JSONEncoder):
|
||||
"""JSONEncoder subclass that forces translation proxies."""
|
||||
def default(self, obj):
|
||||
|
||||
@@ -285,6 +285,14 @@ def find_source_node(node):
|
||||
return None
|
||||
|
||||
|
||||
def get_node_line(node):
|
||||
# type: (nodes.Element) -> int
|
||||
for pnode in traverse_parent(node):
|
||||
if pnode.line:
|
||||
return pnode.line
|
||||
return None
|
||||
|
||||
|
||||
def traverse_parent(node, cls=None):
|
||||
# type: (nodes.Element, Any) -> Iterable[nodes.Element]
|
||||
while node:
|
||||
@@ -293,6 +301,15 @@ def traverse_parent(node, cls=None):
|
||||
node = node.parent
|
||||
|
||||
|
||||
def get_prev_node(node):
|
||||
# type: (nodes.Node) -> nodes.Node
|
||||
pos = node.parent.index(node)
|
||||
if pos > 0:
|
||||
return node.parent[pos - 1]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def traverse_translatable_index(doctree):
|
||||
# type: (nodes.Element) -> Iterable[Tuple[nodes.Element, List[str]]]
|
||||
"""Traverse translatable index node from a document tree."""
|
||||
|
||||
@@ -17,6 +17,8 @@ import warnings
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.console import terminal_safe
|
||||
from sphinx.util.typing import NoneType
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -26,22 +28,9 @@ if False:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NoneType = type(None)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Python 2/3 compatibility
|
||||
|
||||
# sys_encoding: some kind of default system encoding; should be used with
|
||||
# a lenient error handler
|
||||
sys_encoding = sys.getdefaultencoding()
|
||||
|
||||
|
||||
# terminal_safe(): safely encode a string for printing to the terminal
|
||||
def terminal_safe(s):
|
||||
# type: (str) -> str
|
||||
return s.encode('ascii', 'backslashreplace').decode('ascii')
|
||||
|
||||
|
||||
# convert_with_2to3():
|
||||
# support for running 2to3 over config files
|
||||
def convert_with_2to3(filepath):
|
||||
@@ -99,9 +88,12 @@ def execfile_(filepath, _globals, open=open):
|
||||
|
||||
deprecated_alias('sphinx.util.pycompat',
|
||||
{
|
||||
'NoneType': NoneType, # type: ignore
|
||||
'TextIOWrapper': io.TextIOWrapper,
|
||||
'htmlescape': html.escape,
|
||||
'indent': textwrap.indent,
|
||||
'terminal_safe': terminal_safe,
|
||||
'sys_encoding': sys.getdefaultencoding(),
|
||||
'u': '',
|
||||
},
|
||||
RemovedInSphinx40Warning)
|
||||
|
||||
@@ -20,6 +20,9 @@ DirectiveOption = Callable[[str], Any]
|
||||
# Text like nodes which are initialized with text and rawsource
|
||||
TextlikeNode = Union[nodes.Text, nodes.TextElement]
|
||||
|
||||
# type of None
|
||||
NoneType = type(None)
|
||||
|
||||
# common role functions
|
||||
RoleFunction = Callable[[str, str, str, int, Inliner, Dict, List[str]],
|
||||
Tuple[List[nodes.Node], List[nodes.system_message]]]
|
||||
|
||||
@@ -509,9 +509,7 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
|
||||
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
||||
self.add_permalink_ref(node.parent, _('Permalink to this code'))
|
||||
elif isinstance(node.parent, nodes.figure):
|
||||
image_nodes = node.parent.traverse(nodes.image)
|
||||
target_node = image_nodes and image_nodes[0] or node.parent
|
||||
self.add_permalink_ref(target_node, _('Permalink to this image'))
|
||||
self.add_permalink_ref(node.parent, _('Permalink to this image'))
|
||||
elif node.parent.get('toctree'):
|
||||
self.add_permalink_ref(node.parent.parent, _('Permalink to this toctree'))
|
||||
|
||||
|
||||
@@ -455,9 +455,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
||||
if isinstance(node.parent, nodes.container) and node.parent.get('literal_block'):
|
||||
self.add_permalink_ref(node.parent, _('Permalink to this code'))
|
||||
elif isinstance(node.parent, nodes.figure):
|
||||
image_nodes = node.parent.traverse(nodes.image)
|
||||
target_node = image_nodes and image_nodes[0] or node.parent
|
||||
self.add_permalink_ref(target_node, _('Permalink to this image'))
|
||||
self.add_permalink_ref(node.parent, _('Permalink to this image'))
|
||||
elif node.parent.get('toctree'):
|
||||
self.add_permalink_ref(node.parent.parent, _('Permalink to this toctree'))
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ from sphinx.errors import SphinxError
|
||||
from sphinx.locale import admonitionlabels, _, __
|
||||
from sphinx.util import split_into, logging
|
||||
from sphinx.util.docutils import SphinxTranslator
|
||||
from sphinx.util.nodes import clean_astext
|
||||
from sphinx.util.nodes import clean_astext, get_prev_node
|
||||
from sphinx.util.template import LaTeXRenderer
|
||||
from sphinx.util.texescape import tex_escape_map, tex_replace_map
|
||||
|
||||
@@ -1533,7 +1533,11 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
# in reverse order
|
||||
post = [] # type: List[str]
|
||||
include_graphics_options = []
|
||||
is_inline = self.is_inline(node)
|
||||
has_hyperlink = isinstance(node.parent, nodes.reference)
|
||||
if has_hyperlink:
|
||||
is_inline = self.is_inline(node.parent)
|
||||
else:
|
||||
is_inline = self.is_inline(node)
|
||||
if 'width' in attrs:
|
||||
if 'scale' in attrs:
|
||||
w = self.latex_image_length(attrs['width'], attrs['scale'])
|
||||
@@ -1575,7 +1579,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
if self.in_parsed_literal:
|
||||
pre.append('{\\sphinxunactivateextrasandspace ')
|
||||
post.append('}')
|
||||
if not is_inline:
|
||||
if not is_inline and not has_hyperlink:
|
||||
pre.append('\n\\noindent')
|
||||
post.append('\n')
|
||||
pre.reverse()
|
||||
@@ -1755,7 +1759,12 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
if 'refuri' in node:
|
||||
return
|
||||
if node.get('refid'):
|
||||
add_target(node['refid'])
|
||||
prev_node = get_prev_node(node)
|
||||
if isinstance(prev_node, nodes.reference) and node['refid'] == prev_node['refid']:
|
||||
# a target for a hyperlink reference having alias
|
||||
pass
|
||||
else:
|
||||
add_target(node['refid'])
|
||||
for id in node['ids']:
|
||||
add_target(id)
|
||||
|
||||
@@ -1858,6 +1867,8 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
for id in node.get('ids'):
|
||||
anchor = not self.in_caption
|
||||
self.body += self.hypertarget(id, anchor=anchor)
|
||||
if not self.is_inline(node):
|
||||
self.body.append('\n')
|
||||
uri = node.get('refuri', '')
|
||||
if not uri and node.get('refid'):
|
||||
uri = '%' + self.curfilestack[-1] + '#' + node['refid']
|
||||
@@ -1911,6 +1922,8 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
def depart_reference(self, node):
|
||||
# type: (nodes.Element) -> None
|
||||
self.body.append(self.context.pop())
|
||||
if not self.is_inline(node):
|
||||
self.body.append('\n')
|
||||
|
||||
def visit_number_reference(self, node):
|
||||
# type: (nodes.Element) -> None
|
||||
|
||||
@@ -247,7 +247,7 @@ class TexinfoTranslator(SphinxTranslator):
|
||||
title = self.settings.title # type: str
|
||||
if not title:
|
||||
title_node = self.document.next_node(nodes.title)
|
||||
title = (title and title_node.astext()) or '<untitled>'
|
||||
title = (title_node and title_node.astext()) or '<untitled>'
|
||||
elements['title'] = self.escape_id(title) or '<untitled>'
|
||||
# filename
|
||||
if not elements['filename']:
|
||||
@@ -387,9 +387,12 @@ class TexinfoTranslator(SphinxTranslator):
|
||||
def escape_id(self, s):
|
||||
# type: (str) -> str
|
||||
"""Return an escaped string suitable for node names and anchors."""
|
||||
bad_chars = ',:.()'
|
||||
bad_chars = ',:()'
|
||||
for bc in bad_chars:
|
||||
s = s.replace(bc, ' ')
|
||||
if re.search('[^ .]', s):
|
||||
# remove DOTs if name contains other characters
|
||||
s = s.replace('.', ' ')
|
||||
s = ' '.join(s.split()).strip()
|
||||
return self.escape(s)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import docutils
|
||||
import pytest
|
||||
@@ -23,13 +24,32 @@ collect_ignore = ['roots']
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def rootdir():
|
||||
return path(os.path.dirname(__file__)).abspath() / 'roots'
|
||||
return path(__file__).parent.abspath() / 'roots'
|
||||
|
||||
|
||||
def pytest_report_header(config):
|
||||
return ("libraries: Sphinx-%s, docutils-%s" %
|
||||
(sphinx.__display_version__, docutils.__version__))
|
||||
header = ("libraries: Sphinx-%s, docutils-%s" %
|
||||
(sphinx.__display_version__, docutils.__version__))
|
||||
if hasattr(config, '_tmp_path_factory'):
|
||||
header += "\nbase tempdir: %s" % config._tmp_path_factory.getbasetemp()
|
||||
|
||||
return header
|
||||
|
||||
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
comparer.pytest_assertrepr_compare(op, left, right)
|
||||
|
||||
|
||||
def _initialize_test_directory(session):
|
||||
if 'SPHINX_TEST_TEMPDIR' in os.environ:
|
||||
tempdir = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR'))
|
||||
print('Temporary files will be placed in %s.' % tempdir)
|
||||
|
||||
if os.path.exists(tempdir):
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
os.makedirs(tempdir)
|
||||
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
_initialize_test_directory(session)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
project = 'test'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user