Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA
2020-03-07 09:57:16 +09:00
25 changed files with 668 additions and 304 deletions

View File

@@ -52,6 +52,10 @@ Incompatible changes
node_id for cross reference
* #7229: rst domain: Non intended behavior is removed such as ``numref_`` links
to ``.. rst:role:: numref``
* #6903: py domain: Internal data structure has changed. Both objects and
modules have node_id for cross reference
* #6903: py domain: Non intended behavior is removed such as ``say_hello_``
links to ``.. py:function:: say_hello()``
Deprecated
----------
@@ -63,6 +67,7 @@ Deprecated
* ``sphinx.testing.path.Path.text()``
* ``sphinx.testing.path.Path.bytes()``
* ``sphinx.util.inspect.getargspec()``
* ``sphinx.writers.latex.LaTeXWriter.format_docclass()``
Features added
--------------
@@ -78,6 +83,7 @@ Features added
* #3106: domain: Register hyperlink target for index page automatically
* #6558: std domain: emit a warning for duplicated generic objects
* #6830: py domain: Add new event: :event:`object-description-transform`
* #6895: py domain: Do not emit nitpicky warnings for built-in types
* py domain: Support lambda functions in function signature
* Support priority of event handlers. For more detail, see
:py:meth:`.Sphinx.connect()`
@@ -89,6 +95,8 @@ Features added
``no-scaled-link`` class
* #7144: Add CSS class indicating its domain for each desc node
* #7211: latex: Use babel for Chinese document when using XeLaTeX
* #6672: LaTeX: Support LaTeX Theming (experimental)
* #7005: LaTeX: Add LaTeX styling macro for :rst:role:`kbd` role
* #7220: genindex: Show "main" index entries at first
* #7103: linkcheck: writes all links to ``output.json``
* #7025: html search: full text search can be disabled for individual document

View File

@@ -45,7 +45,6 @@ package.
.. automethod:: Sphinx.add_enumerable_node(node, figtype, title_getter=None, \*\*kwds)
.. method:: Sphinx.add_directive(name, func, content, arguments, \*\*options)
.. automethod:: Sphinx.add_directive(name, directiveclass)
.. automethod:: Sphinx.add_role(name, role)
@@ -54,7 +53,6 @@ package.
.. automethod:: Sphinx.add_domain(domain)
.. method:: Sphinx.add_directive_to_domain(domain, name, func, content, arguments, \*\*options)
.. automethod:: Sphinx.add_directive_to_domain(domain, name, directiveclass)
.. automethod:: Sphinx.add_role_to_domain(domain, name, role)
@@ -107,6 +105,7 @@ Emitting events
---------------
.. class:: Sphinx
:noindex:
.. automethod:: emit(event, \*arguments)
@@ -268,11 +267,6 @@ connect handlers to the events. Example:
environment from the main process. *docnames* is a set of document names
that have been read in the subprocess.
For a sample of how to deal with this event, look at the standard
``sphinx.ext.todo`` extension. The implementation is often similar to that
of :event:`env-purge-doc`, only that information is not removed, but added to
the main environment from the other environment.
.. versionadded:: 1.3
.. event:: env-updated (app, env)

View File

@@ -61,6 +61,11 @@ The following is a list of deprecated interfaces.
- 5.0
- ``inspect.getargspec()``
* - ``sphinx.writers.latex.LaTeXWriter.format_docclass()``
- 3.0
- 5.0
- LaTeX Themes
* - ``decode`` argument of ``sphinx.pycode.ModuleAnalyzer()``
- 2.4
- 4.0

View File

@@ -817,6 +817,8 @@ Macros
multiple paragraphs in header cells of tables.
.. versionadded:: 1.6.3
``\sphinxstylecodecontinued`` and ``\sphinxstylecodecontinues``.
.. versionadded:: 3.0
``\sphinxkeyboard``
- ``\sphinxtableofcontents``: it is a
wrapper (defined differently in :file:`sphinxhowto.cls` and in
:file:`sphinxmanual.cls`) of standard ``\tableofcontents``. The macro

View File

@@ -227,6 +227,7 @@ them to generate links or output multiply used elements.
documents.
.. function:: pathto(file, 1)
:noindex:
Return the path to a *file* which is a filename relative to the root of the
generated output. Use this to refer to static files.
@@ -413,10 +414,6 @@ are in HTML form), these variables are also available:
nonempty if the :confval:`html_copy_source` value is ``True``.
This has empty value on creating automatically-generated files.
.. data:: title
The page title.
.. data:: toc
The local table of contents for the current page, rendered as HTML bullet

View File

@@ -1904,7 +1904,7 @@ These options influence LaTeX output.
This value determines how to group the document tree into LaTeX source files.
It must be a list of tuples ``(startdocname, targetname, title, author,
documentclass, toctree_only)``, where the items are:
theme, toctree_only)``, where the items are:
*startdocname*
String that specifies the :term:`document name` of the LaTeX file's master
@@ -1926,13 +1926,8 @@ These options influence LaTeX output.
applies. Use ``\\and`` to separate multiple authors, as in:
``'John \\and Sarah'`` (backslashes must be Python-escaped to reach LaTeX).
*documentclass*
Normally, one of ``'manual'`` or ``'howto'`` (provided by Sphinx and based
on ``'report'``, resp. ``'article'``; Japanese documents use ``'jsbook'``,
resp. ``'jreport'``.) "howto" (non-Japanese) documents will not get
appendices. Also they have a simpler title page. Other document classes
can be given. Independently of the document class, the "sphinx" package is
always loaded in order to define Sphinx's custom LaTeX commands.
*theme*
LaTeX theme. See :confval:`latex_theme`.
*toctree_only*
Must be ``True`` or ``False``. If true, the *startdoc* document itself is
@@ -2087,6 +2082,33 @@ These options influence LaTeX output.
This overrides the files which is provided from Sphinx such as
``sphinx.sty``.
.. confval:: latex_theme
The "theme" that the LaTeX output should use. It is a collection of settings
for LaTeX output (ex. document class, top level sectioning unit and so on).
As a built-in LaTeX themes, ``manual`` and ``howto`` are bundled.
``manual``
A LaTeX theme for writing a manual. It imports the ``report`` document
class (Japanese documents use ``jsbook``).
``howto``
A LaTeX theme for writing an article. It imports the ``article`` document
class (Japanese documents use ``jreport`` rather). :confval:`latex_appendices`
is available only for this theme.
It defaults to ``'manual'``.
.. versionadded:: 3.0
.. confval:: latex_theme_path
A list of paths that contain custom LaTeX themes as subdirectories. Relative
paths are taken as relative to the configuration directory.
.. versionadded:: 3.0
.. _text-options:

View File

@@ -273,6 +273,7 @@ Additionally, the following filters are available
replaces the builtin Jinja `escape filter`_ that does html-escaping.
.. function:: underline(s, line='=')
:noindex:
Add a title underline to a piece of text.

View File

@@ -20,7 +20,8 @@ import sphinx.builders.latex.nodes # NOQA # Workaround: import this before wri
from sphinx import package_dir, addnodes, highlighting
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS
from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS, SHORTHANDOFF
from sphinx.builders.latex.theming import Theme, ThemeFactory
from sphinx.builders.latex.util import ExtBabel
from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx40Warning
@@ -126,11 +127,13 @@ class LaTeXBuilder(Builder):
self.context = {} # type: Dict[str, Any]
self.docnames = [] # type: Iterable[str]
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app)
self.usepackages = self.app.registry.latex_packages
texescape.init()
self.init_context()
self.init_babel()
self.init_multilingual()
def get_outdated_docs(self) -> Union[str, List[str]]:
return 'all documents' # for now
@@ -206,6 +209,41 @@ class LaTeXBuilder(Builder):
logger.warning(__('no Babel option known for language %r'),
self.config.language)
def init_multilingual(self) -> None:
if self.context['latex_engine'] == 'pdflatex':
if not self.babel.uses_cyrillic():
if 'X2' in self.context['fontenc']:
self.context['substitutefont'] = '\\usepackage{substitutefont}'
self.context['textcyrillic'] = '\\usepackage[Xtwo]{sphinxcyrillic}'
elif 'T2A' in self.context['fontenc']:
self.context['substitutefont'] = '\\usepackage{substitutefont}'
self.context['textcyrillic'] = '\\usepackage[TtwoA]{sphinxcyrillic}'
if 'LGR' in self.context['fontenc']:
self.context['substitutefont'] = '\\usepackage{substitutefont}'
else:
self.context['textgreek'] = ''
# 'babel' key is public and user setting must be obeyed
if self.context['babel']:
self.context['classoptions'] += ',' + self.babel.get_language()
# this branch is not taken for xelatex/lualatex if default settings
self.context['multilingual'] = self.context['babel']
if self.config.language:
self.context['shorthandoff'] = SHORTHANDOFF
# Times fonts don't work with Cyrillic languages
if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements:
self.context['fontpkg'] = ''
elif self.context['polyglossia']:
self.context['classoptions'] += ',' + self.babel.get_language()
options = self.babel.get_mainlanguage_options()
if options:
language = r'\setmainlanguage[%s]{%s}' % (options, self.babel.get_language())
else:
language = r'\setmainlanguage{%s}' % self.babel.get_language()
self.context['multilingual'] = '%s\n%s' % (self.context['polyglossia'], language)
def write_stylesheet(self) -> None:
highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style)
stylesheet = path.join(self.outdir, 'sphinxhighlight.sty')
@@ -227,7 +265,8 @@ class LaTeXBuilder(Builder):
self.write_stylesheet()
for entry in self.document_data:
docname, targetname, title, author, docclass = entry[:5]
docname, targetname, title, author, themename = entry[:5]
theme = self.themes.get(themename)
toctree_only = False
if len(entry) > 5:
toctree_only = entry[5]
@@ -243,21 +282,22 @@ class LaTeXBuilder(Builder):
doctree = self.assemble_doctree(
docname, toctree_only,
appendices=(self.config.latex_appendices if docclass != 'howto' else []))
doctree['docclass'] = docclass
appendices=(self.config.latex_appendices if theme.name != 'howto' else []))
doctree['docclass'] = theme.docclass
doctree['contentsname'] = self.get_contentsname(docname)
doctree['tocdepth'] = tocdepth
self.post_process_images(doctree)
self.update_doc_context(title, author)
self.update_doc_context(title, author, theme)
with progress_message(__("writing")):
docsettings._author = author
docsettings._title = title
docsettings._contentsname = doctree['contentsname']
docsettings._docname = docname
docsettings._docclass = docclass
docsettings._docclass = theme.name
doctree.settings = docsettings
docwriter.theme = theme
docwriter.write(doctree, destination)
def get_contentsname(self, indexfile: str) -> str:
@@ -270,9 +310,11 @@ class LaTeXBuilder(Builder):
return contentsname
def update_doc_context(self, title: str, author: str) -> None:
def update_doc_context(self, title: str, author: str, theme: Theme) -> None:
self.context['title'] = title
self.context['author'] = author
self.context['docclass'] = theme.docclass
self.context['wrapperclass'] = theme.wrapperclass
def assemble_doctree(self, indexfile: str, toctree_only: bool, appendices: List[str]) -> nodes.document: # NOQA
self.docnames = set([indexfile] + appendices)
@@ -487,7 +529,7 @@ def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, st
make_filename_from_project(config.project) + '.tex',
texescape.escape_abbr(project),
texescape.escape_abbr(author),
'manual')]
config.latex_theme)]
def setup(app: Sphinx) -> Dict[str, Any]:
@@ -510,6 +552,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('latex_show_pagerefs', False, None)
app.add_config_value('latex_elements', {}, None)
app.add_config_value('latex_additional_files', [], None)
app.add_config_value('latex_theme', 'manual', None, [str])
app.add_config_value('latex_theme_path', [], None, [list])
app.add_config_value('latex_docclass', default_latex_docclass, None)

View File

@@ -192,3 +192,11 @@ ADDITIONAL_SETTINGS = {
'fontpkg': XELATEX_GREEK_DEFAULT_FONTPKG,
},
} # type: Dict[Any, Dict[str, Any]]
SHORTHANDOFF = r'''
\ifdefined\shorthandoff
\ifnum\catcode`\=\string=\active\shorthandoff{=}\fi
\ifnum\catcode`\"=\active\shorthandoff{"}\fi
\fi
'''

View File

@@ -0,0 +1,118 @@
"""
sphinx.builders.latex.theming
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Theming support for LaTeX builder.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import configparser
from os import path
from typing import Dict
from sphinx.application import Sphinx
from sphinx.config import Config
from sphinx.errors import ThemeError
from sphinx.locale import __
from sphinx.util import logging
logger = logging.getLogger(__name__)
class Theme:
"""A set of LaTeX configurations."""
def __init__(self, name: str) -> None:
self.name = name
self.docclass = name
self.wrapperclass = name
self.toplevel_sectioning = 'chapter'
class BuiltInTheme(Theme):
"""A built-in LaTeX theme."""
def __init__(self, name: str, config: Config) -> None:
# Note: Don't call supermethod here.
self.name = name
self.latex_docclass = config.latex_docclass # type: Dict[str, str]
@property
def docclass(self) -> str: # type: ignore
if self.name == 'howto':
return self.latex_docclass.get('howto', 'article')
else:
return self.latex_docclass.get('manual', 'report')
@property
def wrapperclass(self) -> str: # type: ignore
if self.name in ('manual', 'howto'):
return 'sphinx' + self.name
else:
return self.name
@property
def toplevel_sectioning(self) -> str: # type: ignore
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
if self.name == 'howto' and not self.docclass.startswith('j'):
return 'section'
else:
return 'chapter'
class UserTheme(Theme):
"""A user defined LaTeX theme."""
def __init__(self, name: str, filename: str) -> None:
self.name = name
self.config = configparser.RawConfigParser()
self.config.read(path.join(filename))
try:
self.docclass = self.config.get('theme', 'docclass')
self.wrapperclass = self.config.get('theme', 'wrapperclass')
self.toplevel_sectioning = self.config.get('theme', 'toplevel_sectioning')
except configparser.NoSectionError:
raise ThemeError(__('%r doesn\'t have "theme" setting') % filename)
except configparser.NoOptionError as exc:
raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0]))
class ThemeFactory:
"""A factory class for LaTeX Themes."""
def __init__(self, app: Sphinx) -> None:
self.themes = {} # type: Dict[str, Theme]
self.theme_paths = [path.join(app.srcdir, p) for p in app.config.latex_theme_path]
self.load_builtin_themes(app.config)
def load_builtin_themes(self, config: Config) -> None:
"""Load built-in themes."""
self.themes['manual'] = BuiltInTheme('manual', config)
self.themes['howto'] = BuiltInTheme('howto', config)
def get(self, name: str) -> Theme:
"""Get a theme for given *name*."""
if name in self.themes:
return self.themes[name]
else:
theme = self.find_user_theme(name)
if theme:
return theme
else:
return Theme(name)
def find_user_theme(self, name: str) -> Theme:
"""Find a theme named as *name* from latex_theme_path."""
for theme_path in self.theme_paths:
config_path = path.join(theme_path, name, 'theme.conf')
if path.isfile(config_path):
try:
return UserTheme(name, config_path)
except ThemeError as exc:
logger.warning(exc)
return None

View File

@@ -11,11 +11,14 @@
import re
import warnings
from copy import deepcopy
from typing import Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Type, Union
from typing import (
Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Type, TypeVar, Union
)
from docutils import nodes, utils
from docutils.nodes import Element, Node, TextElement
from docutils.nodes import Element, Node, TextElement, system_message
from docutils.parsers.rst import directives
from docutils.parsers.rst.states import Inliner
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
@@ -39,6 +42,7 @@ from sphinx.util.nodes import make_refnode
logger = logging.getLogger(__name__)
StringifyTransform = Callable[[Any], str]
T = TypeVar('T')
"""
Important note on ids
@@ -652,7 +656,7 @@ class ASTCPPAttribute(ASTBase):
def __init__(self, arg: str) -> None:
self.arg = arg
def _stringify(self, transform):
def _stringify(self, transform: StringifyTransform) -> str:
return "[[" + self.arg + "]]"
def describe_signature(self, signode: desc_signature) -> None:
@@ -733,12 +737,13 @@ class ASTPointerLiteral(ASTBase):
def get_id(self, version: int) -> str:
return 'LDnE'
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('nullptr'))
class ASTBooleanLiteral(ASTBase):
def __init__(self, value):
def __init__(self, value: bool) -> None:
self.value = value
def _stringify(self, transform: StringifyTransform) -> str:
@@ -753,7 +758,8 @@ class ASTBooleanLiteral(ASTBase):
else:
return 'L0E'
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(str(self)))
@@ -767,7 +773,8 @@ class ASTNumberLiteral(ASTBase):
def get_id(self, version: int) -> str:
return "L%sE" % self.data
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
@@ -802,7 +809,8 @@ class ASTCharLiteral(ASTBase):
def get_id(self, version: int) -> str:
return self.type + str(self.value)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
@@ -818,7 +826,8 @@ class ASTStringLiteral(ASTBase):
# note: the length is not really correct with escaping
return "LA%d_KcE" % (len(self.data) - 2)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
@@ -830,7 +839,8 @@ class ASTThisLiteral(ASTBase):
def get_id(self, version: int) -> str:
return "fpT"
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text("this"))
@@ -844,7 +854,8 @@ class ASTParenExpr(ASTBase):
def get_id(self, version: int) -> str:
return self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('(', '('))
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')', ')'))
@@ -894,7 +905,8 @@ class ASTFoldExpr(ASTBase):
res.append(self.rightExpr.get_id(version))
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('('))
if self.leftExpr:
self.leftExpr.describe_signature(signode, mode, env, symbol)
@@ -936,7 +948,8 @@ class ASTBinOpExpr(ASTBase):
res.append(self.exprs[-1].get_id(version))
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode.append(nodes.Text(' '))
@@ -970,7 +983,8 @@ class ASTAssignmentExpr(ASTBase):
res.append(self.exprs[-1].get_id(version))
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode.append(nodes.Text(' '))
@@ -994,7 +1008,8 @@ class ASTCastExpr(ASTBase):
def get_id(self, version: int) -> str:
return 'cv' + self.typ.get_id(version) + self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('('))
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
@@ -1012,7 +1027,8 @@ class ASTUnaryOpExpr(ASTBase):
def get_id(self, version: int) -> str:
return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(self.op))
self.expr.describe_signature(signode, mode, env, symbol)
@@ -1027,7 +1043,8 @@ class ASTSizeofParamPack(ASTBase):
def get_id(self, version: int) -> str:
return 'sZ' + self.identifier.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof...('))
self.identifier.describe_signature(signode, mode, env,
symbol=symbol, prefix="", templateArgs="")
@@ -1044,7 +1061,8 @@ class ASTSizeofType(ASTBase):
def get_id(self, version: int) -> str:
return 'st' + self.typ.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof('))
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
@@ -1060,7 +1078,8 @@ class ASTSizeofExpr(ASTBase):
def get_id(self, version: int) -> str:
return 'sz' + self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof '))
self.expr.describe_signature(signode, mode, env, symbol)
@@ -1075,7 +1094,8 @@ class ASTAlignofExpr(ASTBase):
def get_id(self, version: int) -> str:
return 'at' + self.typ.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('alignof('))
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
@@ -1091,7 +1111,8 @@ class ASTNoexceptExpr(ASTBase):
def get_id(self, version: int) -> str:
return 'nx' + self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('noexcept('))
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
@@ -1130,7 +1151,8 @@ class ASTNewExpr(ASTBase):
res.append('E')
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
if self.rooted:
signode.append(nodes.Text('::'))
signode.append(nodes.Text('new '))
@@ -1166,7 +1188,8 @@ class ASTDeleteExpr(ASTBase):
id = "dl"
return id + self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
if self.rooted:
signode.append(nodes.Text('::'))
signode.append(nodes.Text('delete '))
@@ -1196,7 +1219,8 @@ class ASTExplicitCast(ASTBase):
self.typ.get_id(version) +
self.expr.get_id(version))
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(self.cast))
signode.append(nodes.Text('<'))
self.typ.describe_signature(signode, mode, env, symbol)
@@ -1218,7 +1242,8 @@ class ASTTypeId(ASTBase):
prefix = 'ti' if self.isType else 'te'
return prefix + self.typeOrExpr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('typeid'))
signode.append(nodes.Text('('))
self.typeOrExpr.describe_signature(signode, mode, env, symbol)
@@ -1239,7 +1264,8 @@ class ASTPostfixCallExpr(ASTBase):
res.append('E')
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.lst.describe_signature(signode, mode, env, symbol)
@@ -1253,7 +1279,8 @@ class ASTPostfixArray(ASTBase):
def get_id(self, idPrefix: str, version: int) -> str:
return 'ix' + idPrefix + self.expr.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('['))
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(']'))
@@ -1266,7 +1293,8 @@ class ASTPostfixInc(ASTBase):
def get_id(self, idPrefix: str, version: int) -> str:
return 'pp' + idPrefix
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('++'))
@@ -1277,7 +1305,8 @@ class ASTPostfixDec(ASTBase):
def get_id(self, idPrefix: str, version: int) -> str:
return 'mm' + idPrefix
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('--'))
@@ -1291,7 +1320,8 @@ class ASTPostfixMember(ASTBase):
def get_id(self, idPrefix: str, version: int) -> str:
return 'dt' + idPrefix + self.name.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('.'))
self.name.describe_signature(signode, 'noneIsName', env, symbol)
@@ -1306,7 +1336,8 @@ class ASTPostfixMemberOfPointer(ASTBase):
def get_id(self, idPrefix: str, version: int) -> str:
return 'pt' + idPrefix + self.name.get_id(version)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('->'))
self.name.describe_signature(signode, 'noneIsName', env, symbol)
@@ -1329,7 +1360,8 @@ class ASTPostfixExpr(ASTBase):
id = p.get_id(id, version)
return id
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.prefix.describe_signature(signode, mode, env, symbol)
for p in self.postFixes:
p.describe_signature(signode, mode, env, symbol)
@@ -1346,7 +1378,8 @@ class ASTPackExpansionExpr(ASTBase):
id = self.expr.get_id(version)
return 'sp' + id
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.expr.describe_signature(signode, mode, env, symbol)
signode += nodes.Text('...')
@@ -1361,7 +1394,8 @@ class ASTFallbackExpr(ASTBase):
def get_id(self, version: int) -> str:
return str(self.expr)
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += nodes.Text(self.expr)
@@ -1667,13 +1701,13 @@ class ASTTemplateParams(ASTBase):
env: "BuildEnvironment", symbol: "Symbol", lineSpec: bool = None
) -> None:
# 'lineSpec' is defaulted becuase of template template parameters
def makeLine(parentNode=parentNode):
def makeLine(parentNode: desc_signature = parentNode) -> addnodes.desc_signature_line:
signode = addnodes.desc_signature_line()
parentNode += signode
signode.sphinx_cpp_tagname = 'templateParams'
return signode
if self.isNested:
lineNode = parentNode
lineNode = parentNode # type: Element
else:
lineNode = makeLine()
lineNode += nodes.Text("template<")
@@ -1822,7 +1856,7 @@ class ASTTemplateDeclarationPrefix(ASTBase):
class ASTOperator(ASTBase):
def is_anon(self):
def is_anon(self) -> bool:
return False
def is_operator(self) -> bool:
@@ -2300,11 +2334,11 @@ class ASTParametersQualifiers(ASTBase):
paramlist += param
signode += paramlist
def _add_anno(signode, text):
def _add_anno(signode: desc_signature, text: str) -> None:
signode += nodes.Text(' ')
signode += addnodes.desc_annotation(text, text)
def _add_text(signode, text):
def _add_text(signode: desc_signature, text: str) -> None:
signode += nodes.Text(' ' + text)
if self.volatile:
@@ -2376,7 +2410,7 @@ class ASTDeclSpecsSimple(ASTBase):
return ' '.join(res)
def describe_signature(self, modifiers: List[Node]) -> None:
def _add(modifiers, text):
def _add(modifiers: List[Node], text: str) -> None:
if len(modifiers) > 0:
modifiers.append(nodes.Text(' '))
modifiers.append(addnodes.desc_annotation(text, text))
@@ -2456,9 +2490,9 @@ class ASTDeclSpecs(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
modifiers = [] # type: List[nodes.Node]
modifiers = [] # type: List[Node]
def _add(modifiers, text):
def _add(modifiers: List[Node], text: str) -> None:
if len(modifiers) > 0:
modifiers.append(nodes.Text(' '))
modifiers.append(addnodes.desc_annotation(text, text))
@@ -2503,7 +2537,8 @@ class ASTArray(ASTBase):
else:
return 'A_'
def describe_signature(self, signode, mode, env, symbol):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
signode.append(nodes.Text("["))
if self.size:
@@ -2595,7 +2630,7 @@ class ASTDeclaratorPtr(ASTBase):
if len(self.attrs) > 0 and (self.volatile or self.const):
signode += nodes.Text(' ')
def _add_anno(signode, text):
def _add_anno(signode: desc_signature, text: str) -> None:
signode += addnodes.desc_annotation(text, text)
if self.volatile:
_add_anno(signode, 'volatile')
@@ -2795,7 +2830,7 @@ class ASTDeclaratorMemPtr(ASTBase):
self.className.describe_signature(signode, mode, env, symbol)
signode += nodes.Text('::*')
def _add_anno(signode, text):
def _add_anno(signode: desc_signature, text: str) -> None:
signode += addnodes.desc_annotation(text, text)
if self.volatile:
_add_anno(signode, 'volatile')
@@ -3565,7 +3600,7 @@ class Symbol:
debug_show_tree = False
@staticmethod
def debug_print(*args):
def debug_print(*args: Any) -> None:
print(Symbol.debug_indent_string * Symbol.debug_indent, end="")
print(*args)
@@ -3627,7 +3662,7 @@ class Symbol:
# and symbol addition should be done as well
self._add_template_and_function_params()
def _add_template_and_function_params(self):
def _add_template_and_function_params(self) -> None:
if Symbol.debug_lookup:
Symbol.debug_indent += 1
Symbol.debug_print("_add_template_and_function_params:")
@@ -3663,7 +3698,7 @@ class Symbol:
if Symbol.debug_lookup:
Symbol.debug_indent -= 1
def remove(self):
def remove(self) -> None:
if self.parent is None:
return
assert self in self.parent._children
@@ -3771,7 +3806,7 @@ class Symbol:
Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs)
Symbol.debug_print("searchInSiblings: ", searchInSiblings)
def isSpecialization():
def isSpecialization() -> bool:
# the names of the template parameters must be given exactly as args
# and params that are packs must in the args be the name expanded
if len(templateParams.params) != len(templateArgs.args):
@@ -6509,7 +6544,7 @@ class CPPObject(ObjectDescription):
def describe_signature(self, signode: desc_signature, ast: Any, options: Dict) -> None:
ast.describe_signature(signode, 'lastIsName', self.env, options)
def run(self):
def run(self) -> List[Node]:
env = self.state.document.settings.env # from ObjectDescription.run
if 'cpp:parent_symbol' not in env.temp_data:
root = env.domaindata['cpp']['root_symbol']
@@ -6617,7 +6652,7 @@ class CPPClassObject(CPPObject):
object_type = 'class'
@property
def display_object_type(self):
def display_object_type(self) -> str:
# the distinction between class and struct is only cosmetic
assert self.objtype in ('class', 'struct')
return self.objtype
@@ -6733,7 +6768,8 @@ class CPPNamespacePopObject(SphinxDirective):
class AliasNode(nodes.Element):
def __init__(self, sig, env=None, parentKey=None):
def __init__(self, sig: str, env: "BuildEnvironment" = None,
parentKey: LookupKey = None) -> None:
super().__init__()
self.sig = sig
if env is not None:
@@ -6745,8 +6781,8 @@ class AliasNode(nodes.Element):
assert parentKey is not None
self.parentKey = parentKey
def copy(self):
return self.__class__(self.sig, env=None, parentKey=self.parentKey)
def copy(self: T) -> T:
return self.__class__(self.sig, env=None, parentKey=self.parentKey) # type: ignore
class AliasTransform(SphinxTransform):
@@ -6755,7 +6791,7 @@ class AliasTransform(SphinxTransform):
def apply(self, **kwargs: Any) -> None:
for node in self.document.traverse(AliasNode):
class Warner:
def warn(self, msg):
def warn(self, msg: Any) -> None:
logger.warning(msg, location=node)
warner = Warner()
sig = node.sig
@@ -6898,7 +6934,7 @@ class CPPXRefRole(XRefRole):
class CPPExprRole:
def __init__(self, asCode):
def __init__(self, asCode: bool) -> None:
if asCode:
# render the expression as inline code
self.class_type = 'cpp-expr'
@@ -6908,9 +6944,11 @@ class CPPExprRole:
self.class_type = 'cpp-texpr'
self.node_type = nodes.inline
def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]):
def __call__(self, typ: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
class Warner:
def warn(self, msg):
def warn(self, msg: str) -> None:
inliner.reporter.warning(msg, line=lineno)
text = utils.unescape(text).replace('\n', ' ')
env = inliner.document.settings.env
@@ -7056,7 +7094,7 @@ class CPPDomain(Domain):
typ: str, target: str, node: pending_xref, contnode: Element,
emitWarnings: bool = True) -> Tuple[Element, str]:
class Warner:
def warn(self, msg):
def warn(self, msg: str) -> None:
if emitWarnings:
logger.warning(msg, location=node)
warner = Warner()
@@ -7067,7 +7105,8 @@ class CPPDomain(Domain):
try:
ast, isShorthand = parser.parse_xref_object()
except DefinitionError as e:
def findWarning(e): # as arg to stop flake8 from complaining
# as arg to stop flake8 from complaining
def findWarning(e: Exception) -> Tuple[str, Exception]:
if typ != 'any' and typ != 'func':
return target, e
# hax on top of the paren hax to try to get correct errors
@@ -7138,7 +7177,7 @@ class CPPDomain(Domain):
typ = 'class'
declTyp = s.declaration.objectType
def checkType():
def checkType() -> bool:
if typ == 'any' or typ == 'identifier':
return True
if declTyp == 'templateParam':

View File

@@ -8,7 +8,10 @@
:license: BSD, see LICENSE for details.
"""
import builtins
import inspect
import re
import typing
import warnings
from inspect import Parameter
from typing import Any, Dict, Iterable, Iterator, List, Tuple
@@ -32,7 +35,7 @@ from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.docutils import SphinxDirective
from sphinx.util.inspect import signature_from_str
from sphinx.util.nodes import make_refnode
from sphinx.util.nodes import make_id, make_refnode
from sphinx.util.typing import TextlikeNode
if False:
@@ -358,19 +361,22 @@ class PyObject(ObjectDescription):
signode: desc_signature) -> None:
modname = self.options.get('module', self.env.ref_context.get('py:module'))
fullname = (modname + '.' if modname else '') + name_cls[0]
# note target
if fullname not in self.state.document.ids:
signode['names'].append(fullname)
signode['ids'].append(fullname)
self.state.document.note_explicit_target(signode)
node_id = make_id(self.env, self.state.document, modname or '', name_cls[0])
signode['ids'].append(node_id)
domain = cast(PythonDomain, self.env.get_domain('py'))
domain.note_object(fullname, self.objtype, location=signode)
# Assign old styled node_id(fullname) not to break old hyperlinks (if possible)
# Note: Will removed in Sphinx-5.0 (RemovedInSphinx50Warning)
if node_id != fullname and fullname not in self.state.document.ids:
signode['ids'].append(fullname)
self.state.document.note_explicit_target(signode)
domain = cast(PythonDomain, self.env.get_domain('py'))
domain.note_object(fullname, self.objtype, node_id, location=signode)
indextext = self.get_index_text(modname, name_cls)
if indextext:
self.indexnode['entries'].append(('single', indextext,
fullname, '', None))
self.indexnode['entries'].append(('single', indextext, node_id, '', None))
def before_content(self) -> None:
"""Handle object nesting before content
@@ -788,24 +794,43 @@ class PyModule(SphinxDirective):
ret = [] # type: List[Node]
if not noindex:
# note module to the domain
node_id = make_id(self.env, self.state.document, 'module', modname)
target = nodes.target('', '', ids=[node_id], ismod=True)
self.set_source_info(target)
# Assign old styled node_id not to break old hyperlinks (if possible)
# Note: Will removed in Sphinx-5.0 (RemovedInSphinx50Warning)
old_node_id = self.make_old_id(modname)
if node_id != old_node_id and old_node_id not in self.state.document.ids:
target['ids'].append(old_node_id)
self.state.document.note_explicit_target(target)
domain.note_module(modname,
node_id,
self.options.get('synopsis', ''),
self.options.get('platform', ''),
'deprecated' in self.options)
domain.note_object(modname, 'module', location=(self.env.docname, self.lineno))
domain.note_object(modname, 'module', node_id, location=target)
targetnode = nodes.target('', '', ids=['module-' + modname],
ismod=True)
self.state.document.note_explicit_target(targetnode)
# the platform and synopsis aren't printed; in fact, they are only
# used in the modindex currently
ret.append(targetnode)
ret.append(target)
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,
'module-' + modname, '', None)])
inode = addnodes.index(entries=[('single', indextext, node_id, '', None)])
ret.append(inode)
return ret
def make_old_id(self, name: str) -> str:
"""Generate old styled node_id.
Old styled node_id is incompatible with docutils' node_id.
It can contain dots and hyphens.
.. note:: Old styled node_id was mainly used until Sphinx-3.0.
"""
return 'module-%s' % name
class PyCurrentModule(SphinxDirective):
"""
@@ -888,7 +913,7 @@ class PythonModuleIndex(Index):
# sort out collapsable modules
prev_modname = ''
num_toplevels = 0
for modname, (docname, synopsis, platforms, deprecated) in modules:
for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:
if docnames and docname not in docnames:
continue
@@ -925,8 +950,7 @@ class PythonModuleIndex(Index):
qualifier = _('Deprecated') if deprecated else ''
entries.append(IndexEntry(stripped + modname, subtype, docname,
'module-' + stripped + modname, platforms,
qualifier, synopsis))
node_id, platforms, qualifier, synopsis))
prev_modname = modname
# apply heuristics when to collapse modindex at page load:
@@ -990,10 +1014,10 @@ class PythonDomain(Domain):
]
@property
def objects(self) -> Dict[str, Tuple[str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, objtype
def objects(self) -> Dict[str, Tuple[str, str, str]]:
return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype
def note_object(self, name: str, objtype: str, location: Any = None) -> None:
def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None:
"""Note a python object for cross reference.
.. versionadded:: 2.1
@@ -1003,39 +1027,40 @@ class PythonDomain(Domain):
logger.warning(__('duplicate object description of %s, '
'other instance in %s, use :noindex: for one of them'),
name, docname, location=location)
self.objects[name] = (self.env.docname, objtype)
self.objects[name] = (self.env.docname, node_id, objtype)
@property
def modules(self) -> Dict[str, Tuple[str, str, str, bool]]:
return self.data.setdefault('modules', {}) # modname -> docname, synopsis, platform, deprecated # NOQA
def modules(self) -> Dict[str, Tuple[str, str, str, str, bool]]:
return self.data.setdefault('modules', {}) # modname -> docname, node_id, synopsis, platform, deprecated # NOQA
def note_module(self, name: str, synopsis: str, platform: str, deprecated: bool) -> None:
def note_module(self, name: str, node_id: str, synopsis: str,
platform: str, deprecated: bool) -> None:
"""Note a python module for cross reference.
.. versionadded:: 2.1
"""
self.modules[name] = (self.env.docname, synopsis, platform, deprecated)
self.modules[name] = (self.env.docname, node_id, synopsis, platform, deprecated)
def clear_doc(self, docname: str) -> None:
for fullname, (fn, _l) in list(self.objects.items()):
for fullname, (fn, _x, _x) in list(self.objects.items()):
if fn == docname:
del self.objects[fullname]
for modname, (fn, _x, _x, _y) in list(self.modules.items()):
for modname, (fn, _x, _x, _x, _y) in list(self.modules.items()):
if fn == docname:
del self.modules[modname]
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates?
for fullname, (fn, objtype) in otherdata['objects'].items():
for fullname, (fn, node_id, objtype) in otherdata['objects'].items():
if fn in docnames:
self.objects[fullname] = (fn, objtype)
self.objects[fullname] = (fn, node_id, objtype)
for modname, data in otherdata['modules'].items():
if data[0] in docnames:
self.modules[modname] = data
def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
name: str, type: str, searchmode: int = 0
) -> List[Tuple[str, Tuple[str, str]]]:
) -> List[Tuple[str, Tuple[str, str, str]]]:
"""Find a Python object for "name", perhaps using the given module
and/or classname. Returns a list of (name, object entry) tuples.
"""
@@ -1046,7 +1071,7 @@ class PythonDomain(Domain):
if not name:
return []
matches = [] # type: List[Tuple[str, Tuple[str, str]]]
matches = [] # type: List[Tuple[str, Tuple[str, str, str]]]
newname = None
if searchmode == 1:
@@ -1057,20 +1082,20 @@ class PythonDomain(Domain):
if objtypes is not None:
if modname and classname:
fullname = modname + '.' + classname + '.' + name
if fullname in self.objects and self.objects[fullname][1] in objtypes:
if fullname in self.objects and self.objects[fullname][2] in objtypes:
newname = fullname
if not newname:
if modname and modname + '.' + name in self.objects and \
self.objects[modname + '.' + name][1] in objtypes:
self.objects[modname + '.' + name][2] in objtypes:
newname = modname + '.' + name
elif name in self.objects and self.objects[name][1] in objtypes:
elif name in self.objects and self.objects[name][2] in objtypes:
newname = name
else:
# "fuzzy" searching mode
searchname = '.' + name
matches = [(oname, self.objects[oname]) for oname in self.objects
if oname.endswith(searchname) and
self.objects[oname][1] in objtypes]
self.objects[oname][2] in objtypes]
else:
# NOTE: searching for exact match, object type is not considered
if name in self.objects:
@@ -1118,10 +1143,10 @@ class PythonDomain(Domain):
type='ref', subtype='python', location=node)
name, obj = matches[0]
if obj[1] == 'module':
if obj[2] == 'module':
return self._make_module_refnode(builder, fromdocname, name, contnode)
else:
return make_refnode(builder, fromdocname, obj[0], name, contnode, name)
return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name)
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
target: str, node: pending_xref, contnode: Element
@@ -1133,20 +1158,20 @@ class PythonDomain(Domain):
# always search in "refspecific" mode with the :any: role
matches = self.find_obj(env, modname, clsname, target, None, 1)
for name, obj in matches:
if obj[1] == 'module':
if obj[2] == 'module':
results.append(('py:mod',
self._make_module_refnode(builder, fromdocname,
name, contnode)))
else:
results.append(('py:' + self.role_for_objtype(obj[1]),
make_refnode(builder, fromdocname, obj[0], name,
results.append(('py:' + self.role_for_objtype(obj[2]),
make_refnode(builder, fromdocname, obj[0], obj[1],
contnode, name)))
return results
def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
contnode: Node) -> Element:
# get additional info for modules
docname, synopsis, platform, deprecated = self.modules[name]
docname, node_id, synopsis, platform, deprecated = self.modules[name]
title = name
if synopsis:
title += ': ' + synopsis
@@ -1154,15 +1179,14 @@ class PythonDomain(Domain):
title += _(' (deprecated)')
if platform:
title += ' (' + platform + ')'
return make_refnode(builder, fromdocname, docname,
'module-' + name, contnode, title)
return make_refnode(builder, fromdocname, docname, node_id, contnode, title)
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
for modname, info in self.modules.items():
yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
for refname, (docname, type) in self.objects.items():
yield (modname, modname, 'module', info[0], info[1], 0)
for refname, (docname, node_id, type) in self.objects.items():
if type != 'module': # modules are already handled
yield (refname, refname, type, docname, refname, 1)
yield (refname, refname, type, docname, node_id, 1)
def get_full_qualified_name(self, node: Element) -> str:
modname = node.get('py:module')
@@ -1174,15 +1198,41 @@ class PythonDomain(Domain):
return '.'.join(filter(None, [modname, clsname, target]))
def builtin_resolver(app: Sphinx, env: BuildEnvironment,
node: pending_xref, contnode: Element) -> Element:
"""Do not emit nitpicky warnings for built-in types."""
def istyping(s: str) -> bool:
if s.startswith('typing.'):
s = s.split('.', 1)[1]
return s in typing.__all__ # type: ignore
if node.get('refdomain') != 'py':
return None
elif node.get('reftype') == 'obj' and node.get('reftarget') == 'None':
return contnode
elif node.get('reftype') in ('class', 'exc'):
reftarget = node.get('reftarget')
if inspect.isclass(getattr(builtins, reftarget, None)):
# built-in class
return contnode
elif istyping(reftarget):
# typing class
return contnode
return None
def setup(app: Sphinx) -> Dict[str, Any]:
app.setup_extension('sphinx.directives')
app.add_domain(PythonDomain)
app.connect('object-description-transform', filter_meta_fields)
app.connect('missing-reference', builtin_resolver, priority=900)
return {
'version': 'builtin',
'env_version': 1,
'env_version': 2,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -214,16 +214,18 @@ class Cmdoption(ObjectDescription):
def add_target_and_index(self, firstname: str, sig: str, signode: desc_signature) -> None:
currprogram = self.env.ref_context.get('std:program')
for optname in signode.get('allnames', []):
targetname = optname.replace('/', '-')
if not targetname.startswith('-'):
targetname = '-arg-' + targetname
prefixes = ['cmdoption']
if currprogram:
targetname = '-' + currprogram + targetname
targetname = 'cmdoption' + targetname
signode['names'].append(targetname)
prefixes.append(currprogram)
if not optname.startswith(('-', '/')):
prefixes.append('arg')
prefix = '-'.join(prefixes)
node_id = make_id(self.env, self.state.document, prefix, optname)
signode['ids'].append(node_id)
self.state.document.note_explicit_target(signode)
domain = cast(StandardDomain, self.env.get_domain('std'))
self.state.document.note_explicit_target(signode)
for optname in signode.get('allnames', []):
domain.add_program_option(currprogram, optname,
self.env.docname, signode['ids'][0])
@@ -487,28 +489,36 @@ class ProductionList(SphinxDirective):
subnode = addnodes.production(rule)
subnode['tokenname'] = name.strip()
if subnode['tokenname']:
# nodes.make_id converts '_' to '-',
# so we can use '_' to delimit group from name,
# and make sure we don't clash with other IDs.
idname = 'grammar-token-%s_%s' \
% (nodes.make_id(productionGroup), nodes.make_id(name))
if idname not in self.state.document.ids:
subnode['ids'].append(idname)
prefix = 'grammar-token-%s' % productionGroup
node_id = make_id(self.env, self.state.document, prefix, name)
subnode['ids'].append(node_id)
# Assign old styled node_id not to break old hyperlinks (if possible)
# Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
old_node_id = self.make_old_id(name)
if (old_node_id not in self.state.document.ids and
old_node_id not in subnode['ids']):
subnode['ids'].append(old_node_id)
idnameOld = nodes.make_id('grammar-token-' + name)
if idnameOld not in self.state.document.ids:
subnode['ids'].append(idnameOld)
self.state.document.note_implicit_target(subnode, subnode)
if len(productionGroup) != 0:
objName = "%s:%s" % (productionGroup, name)
else:
objName = name
domain.note_object(objtype='token', name=objName, labelid=idname,
location=node)
domain.note_object('token', objName, node_id, location=node)
subnode.extend(token_xrefs(tokens, productionGroup))
node.append(subnode)
return [node]
def make_old_id(self, token: str) -> str:
"""Generate old styled node_id for tokens.
.. note:: Old Styled node_id was used until Sphinx-3.0.
This will be removed in Sphinx-5.0.
"""
return nodes.make_id('grammar-token-' + token)
class TokenXRefRole(XRefRole):
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,

View File

@@ -1837,6 +1837,7 @@
\protected\def\sphinxtitleref#1{\emph{#1}}
\protected\def\sphinxmenuselection#1{\emph{#1}}
\protected\def\sphinxguilabel#1{\emph{#1}}
\protected\def\sphinxkeyboard#1{\sphinxcode{#1}}
\protected\def\sphinxaccelerator#1{\underline{#1}}
\protected\def\sphinxcrossref#1{\emph{#1}}
\protected\def\sphinxtermref#1{\emph{#1}}

View File

@@ -23,7 +23,9 @@ from docutils.nodes import Element, Node, Text
from sphinx import addnodes
from sphinx import highlighting
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.deprecation import (
RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias
)
from sphinx.domains import IndexEntry
from sphinx.domains.std import StandardDomain
from sphinx.errors import SphinxError
@@ -43,17 +45,11 @@ except ImportError:
if False:
# For type annotation
from sphinx.builders.latex import LaTeXBuilder
from sphinx.builders.latex.theming import Theme
logger = logging.getLogger(__name__)
SHORTHANDOFF = r'''
\ifdefined\shorthandoff
\ifnum\catcode`\=\string=\active\shorthandoff{=}\fi
\ifnum\catcode`\"=\active\shorthandoff{"}\fi
\fi
'''
MAX_CITATION_LABEL_LENGTH = 8
LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection",
"subsubsection", "paragraph", "subparagraph"]
@@ -93,9 +89,16 @@ class LaTeXWriter(writers.Writer):
def __init__(self, builder: "LaTeXBuilder") -> None:
super().__init__()
self.builder = builder
self.theme = None # type: Theme
def translate(self) -> None:
visitor = self.builder.create_translator(self.document, self.builder)
try:
visitor = self.builder.create_translator(self.document, self.builder, self.theme)
except TypeError:
warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".',
RemovedInSphinx50Warning)
visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor)
self.output = cast(LaTeXTranslator, visitor).astext()
@@ -281,9 +284,15 @@ class LaTeXTranslator(SphinxTranslator):
# sphinx specific document classes
docclasses = ('howto', 'manual')
def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None:
def __init__(self, document: nodes.document, builder: "LaTeXBuilder",
theme: "Theme" = None) -> None:
super().__init__(document, builder)
self.body = [] # type: List[str]
self.theme = theme
if theme is None:
warnings.warn('LaTeXTranslator now takes 3rd argument; "theme".',
RemovedInSphinx50Warning)
# flags
self.in_title = 0
@@ -306,21 +315,32 @@ class LaTeXTranslator(SphinxTranslator):
# sort out some elements
self.elements = self.builder.context.copy()
# but some have other interface in config file
self.elements['wrapperclass'] = self.format_docclass(document.get('docclass'))
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
# initial section names
self.sectionnames = LATEXSECTIONNAMES[:]
if document.get('docclass') == 'howto':
docclass = self.config.latex_docclass.get('howto', 'article')
if docclass[0] == 'j': # Japanese class...
pass
else:
if self.theme:
# new style: control sectioning via theme's setting
#
# .. note:: template variables(elements) are already assigned in builder
docclass = self.theme.docclass
if self.theme.toplevel_sectioning == 'section':
self.sectionnames.remove('chapter')
else:
docclass = self.config.latex_docclass.get('manual', 'report')
self.elements['docclass'] = docclass
# old style: sectioning control is hard-coded
# but some have other interface in config file
self.elements['wrapperclass'] = self.format_docclass(self.settings.docclass)
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
if document.get('docclass') == 'howto':
docclass = self.config.latex_docclass.get('howto', 'article')
if docclass[0] == 'j': # Japanese class...
pass
else:
self.sectionnames.remove('chapter')
else:
docclass = self.config.latex_docclass.get('manual', 'report')
self.elements['docclass'] = docclass
# determine top section level
self.top_sectionlevel = 1
@@ -368,44 +388,6 @@ class LaTeXTranslator(SphinxTranslator):
logger.warning(__('no Babel option known for language %r'),
self.config.language)
# set up multilingual module...
if self.elements['latex_engine'] == 'pdflatex':
if not self.babel.uses_cyrillic():
if 'X2' in self.elements['fontenc']:
self.elements['substitutefont'] = '\\usepackage{substitutefont}'
self.elements['textcyrillic'] = ('\\usepackage[Xtwo]'
'{sphinxcyrillic}')
elif 'T2A' in self.elements['fontenc']:
self.elements['substitutefont'] = '\\usepackage{substitutefont}'
self.elements['textcyrillic'] = ('\\usepackage[TtwoA]'
'{sphinxcyrillic}')
if 'LGR' in self.elements['fontenc']:
self.elements['substitutefont'] = '\\usepackage{substitutefont}'
else:
self.elements['textgreek'] = ''
# 'babel' key is public and user setting must be obeyed
if self.elements['babel']:
self.elements['classoptions'] += ',' + self.babel.get_language()
# this branch is not taken for xelatex/lualatex if default settings
self.elements['multilingual'] = self.elements['babel']
if self.config.language:
self.elements['shorthandoff'] = SHORTHANDOFF
# Times fonts don't work with Cyrillic languages
if self.babel.uses_cyrillic() and 'fontpkg' not in self.config.latex_elements:
self.elements['fontpkg'] = ''
elif self.elements['polyglossia']:
self.elements['classoptions'] += ',' + self.babel.get_language()
options = self.babel.get_mainlanguage_options()
if options:
mainlanguage = r'\setmainlanguage[%s]{%s}' % (options,
self.babel.get_language())
else:
mainlanguage = r'\setmainlanguage{%s}' % self.babel.get_language()
self.elements['multilingual'] = '%s\n%s' % (self.elements['polyglossia'],
mainlanguage)
minsecnumdepth = self.secnumdepth # 2 from legacy sphinx manual/howto
if self.document.get('tocdepth'):
# reduce tocdepth if `part` or `chapter` is used for top_sectionlevel
@@ -472,6 +454,8 @@ class LaTeXTranslator(SphinxTranslator):
def format_docclass(self, docclass: str) -> str:
""" prepends prefix to sphinx document classes
"""
warnings.warn('LaTeXWriter.format_docclass() is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
if docclass in self.docclasses:
docclass = 'sphinx' + docclass
return docclass
@@ -1720,6 +1704,8 @@ class LaTeXTranslator(SphinxTranslator):
def visit_literal(self, node: Element) -> None:
if self.in_title:
self.body.append(r'\sphinxstyleliteralintitle{\sphinxupquote{')
elif 'kbd' in node['classes']:
self.body.append(r'\sphinxkeyboard{\sphinxupquote{')
else:
self.body.append(r'\sphinxcode{\sphinxupquote{')
@@ -2146,6 +2132,7 @@ deprecated_alias('sphinx.writers.latex',
'DEFAULT_SETTINGS': constants.DEFAULT_SETTINGS,
'LUALATEX_DEFAULT_FONTPKG': constants.LUALATEX_DEFAULT_FONTPKG,
'PDFLATEX_DEFAULT_FONTPKG': constants.PDFLATEX_DEFAULT_FONTPKG,
'SHORTHANDOFF': constants.SHORTHANDOFF,
'XELATEX_DEFAULT_FONTPKG': constants.XELATEX_DEFAULT_FONTPKG,
'XELATEX_GREEK_DEFAULT_FONTPKG': constants.XELATEX_GREEK_DEFAULT_FONTPKG,
'ExtBabel': ExtBabel,

View File

@@ -0,0 +1,2 @@
latex_theme = 'custom'
latex_theme_path = ['theme']

View File

@@ -0,0 +1,2 @@
latex_theme
===========

View File

@@ -0,0 +1,4 @@
[theme]
docclass = book
wrapperclass = sphinxbook
toplevel_sectioning = chapter

View File

@@ -176,8 +176,8 @@ def test_html4_output(app, status, warning):
r'-| |-'),
],
'autodoc.html': [
(".//dl[@class='py class']/dt[@id='autodoc_target.Class']", ''),
(".//dl[@class='py function']/dt[@id='autodoc_target.function']/em", r'\*\*kwds'),
(".//dl[@class='py class']/dt[@id='autodoc-target-class']", ''),
(".//dl[@class='py function']/dt[@id='autodoc-target-function']/em", r'\*\*kwds'),
(".//dd/p", r'Return spam\.'),
],
'extapi.html': [
@@ -222,7 +222,7 @@ def test_html4_output(app, status, warning):
"[@class='reference internal']/code/span[@class='pre']", 'HOME'),
(".//a[@href='#with']"
"[@class='reference internal']/code/span[@class='pre']", '^with$'),
(".//a[@href='#grammar-token-_try-stmt']"
(".//a[@href='#grammar-token-try-stmt']"
"[@class='reference internal']/code/span", '^statement$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^here$'),
(".//a[@href='#some-label'][@class='reference internal']/span", '^there$'),
@@ -254,7 +254,7 @@ def test_html4_output(app, status, warning):
(".//dl/dt[@id='term-boson']", 'boson'),
# a production list
(".//pre/strong", 'try_stmt'),
(".//pre/a[@href='#grammar-token-_try1-stmt']/code/span", 'try1_stmt'),
(".//pre/a[@href='#grammar-token-try1-stmt']/code/span", 'try1_stmt'),
# tests for ``only`` directive
(".//p", 'A global substitution.'),
(".//p", 'In HTML.'),
@@ -262,7 +262,7 @@ def test_html4_output(app, status, warning):
(".//p", 'Always present'),
# tests for ``any`` role
(".//a[@href='#with']/span", 'headings'),
(".//a[@href='objects.html#func_without_body']/code/span", 'objects'),
(".//a[@href='objects.html#func-without-body']/code/span", 'objects'),
# tests for numeric labels
(".//a[@href='#id1'][@class='reference internal']/span", 'Testing various markup'),
# tests for smartypants
@@ -274,18 +274,18 @@ def test_html4_output(app, status, warning):
(".//p", 'Il dit : « Cest “super” ! »'),
],
'objects.html': [
(".//dt[@id='mod.Cls.meth1']", ''),
(".//dt[@id='errmod.Error']", ''),
(".//dt[@id='mod-cls-meth1']", ''),
(".//dt[@id='errmod-error']", ''),
(".//dt/code", r'long\(parameter,\s* list\)'),
(".//dt/code", 'another one'),
(".//a[@href='#mod.Cls'][@class='reference internal']", ''),
(".//a[@href='#mod-cls'][@class='reference internal']", ''),
(".//dl[@class='std userdesc']", ''),
(".//dt[@id='userdesc-myobj']", ''),
(".//a[@href='#userdesc-myobj'][@class='reference internal']", ''),
# docfields
(".//a[@class='reference internal'][@href='#TimeInt']/em", 'TimeInt'),
(".//a[@class='reference internal'][@href='#Time']", 'Time'),
(".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'),
(".//a[@class='reference internal'][@href='#timeint']/em", 'TimeInt'),
(".//a[@class='reference internal'][@href='#time']", 'Time'),
(".//a[@class='reference internal'][@href='#errmod-error']/strong", 'Error'),
# C references
(".//span[@class='pre']", 'CFunction()'),
(".//a[@href='#c.Sphinx_DoSomething']", ''),

View File

@@ -20,7 +20,7 @@ from test_build_html import ENV_WARNINGS
from sphinx.builders.latex import default_latex_documents
from sphinx.config import Config
from sphinx.errors import SphinxError
from sphinx.errors import SphinxError, ThemeError
from sphinx.testing.util import strip_escseq
from sphinx.util import docutils
from sphinx.util.osutil import cd, ensuredir
@@ -99,7 +99,7 @@ def skip_if_stylefiles_notfound(testfunc):
def test_build_latex_doc(app, status, warning, engine, docclass):
app.config.latex_engine = engine
app.config.latex_documents = [app.config.latex_documents[0][:4] + (docclass,)]
app.builder.init_context()
app.builder.init()
LaTeXTranslator.ignore_missing_images = True
app.builder.build_all()
@@ -165,6 +165,65 @@ def test_latex_basic(app, status, warning):
assert r'\renewcommand{\releasename}{}' in result
@pytest.mark.sphinx('latex', testroot='basic',
confoverrides={
'latex_documents': [('index', 'test.tex', 'title', 'author', 'manual')]
})
def test_latex_basic_manual(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'test.tex').read_text(encoding='utf8')
print(result)
assert r'\def\sphinxdocclass{report}' in result
assert r'\documentclass[letterpaper,10pt,english]{sphinxmanual}' in result
@pytest.mark.sphinx('latex', testroot='basic',
confoverrides={
'latex_documents': [('index', 'test.tex', 'title', 'author', 'howto')]
})
def test_latex_basic_howto(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'test.tex').read_text(encoding='utf8')
print(result)
assert r'\def\sphinxdocclass{article}' in result
assert r'\documentclass[letterpaper,10pt,english]{sphinxhowto}' in result
@pytest.mark.sphinx('latex', testroot='basic',
confoverrides={
'language': 'ja',
'latex_documents': [('index', 'test.tex', 'title', 'author', 'manual')]
})
def test_latex_basic_manual_ja(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'test.tex').read_text(encoding='utf8')
print(result)
assert r'\def\sphinxdocclass{jsbook}' in result
assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxmanual}' in result
@pytest.mark.sphinx('latex', testroot='basic',
confoverrides={
'language': 'ja',
'latex_documents': [('index', 'test.tex', 'title', 'author', 'howto')]
})
def test_latex_basic_howto_ja(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'test.tex').read_text(encoding='utf8')
print(result)
assert r'\def\sphinxdocclass{jreport}' in result
assert r'\documentclass[letterpaper,10pt,dvipdfmx]{sphinxhowto}' in result
@pytest.mark.sphinx('latex', testroot='latex-theme')
def test_latex_theme(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'python.tex').read_text(encoding='utf8')
print(result)
assert r'\def\sphinxdocclass{book}' in result
assert r'\documentclass[letterpaper,10pt,english]{sphinxbook}' in result
@pytest.mark.sphinx('latex', testroot='basic', confoverrides={'language': 'zh'})
def test_latex_additional_settings_for_language_code(app, status, warning):
app.builder.build_all()
@@ -1415,6 +1474,7 @@ def test_default_latex_documents():
'author': "Wolfgang Schäuble & G'Beckstein."})
config.init_values()
config.add('latex_engine', None, True, None)
config.add('latex_theme', 'manual', True, None)
expected = [('index', 'stasi.tex', 'STASI™ Documentation',
r"Wolfgang Schäuble \& G\textquotesingle{}Beckstein.\@{}", 'manual')]
assert default_latex_documents(config) == expected

View File

@@ -145,24 +145,24 @@ def test_domain_py_objects(app, status, warning):
assert 'module_b.submodule' in modules
assert 'module_b.submodule' in objects
assert objects['module_a.submodule.ModTopLevel'] == ('module', 'class')
assert objects['module_a.submodule.ModTopLevel.mod_child_1'] == ('module', 'method')
assert objects['module_a.submodule.ModTopLevel.mod_child_2'] == ('module', 'method')
assert objects['module_a.submodule.ModTopLevel'][2] == 'class'
assert objects['module_a.submodule.ModTopLevel.mod_child_1'][2] == 'method'
assert objects['module_a.submodule.ModTopLevel.mod_child_2'][2] == 'method'
assert 'ModTopLevel.ModNoModule' not in objects
assert objects['ModNoModule'] == ('module', 'class')
assert objects['module_b.submodule.ModTopLevel'] == ('module', 'class')
assert objects['ModNoModule'][2] == 'class'
assert objects['module_b.submodule.ModTopLevel'][2] == 'class'
assert objects['TopLevel'] == ('roles', 'class')
assert objects['top_level'] == ('roles', 'method')
assert objects['NestedParentA'] == ('roles', 'class')
assert objects['NestedParentA.child_1'] == ('roles', 'method')
assert objects['NestedParentA.any_child'] == ('roles', 'method')
assert objects['NestedParentA.NestedChildA'] == ('roles', 'class')
assert objects['NestedParentA.NestedChildA.subchild_1'] == ('roles', 'method')
assert objects['NestedParentA.NestedChildA.subchild_2'] == ('roles', 'method')
assert objects['NestedParentA.child_2'] == ('roles', 'method')
assert objects['NestedParentB'] == ('roles', 'class')
assert objects['NestedParentB.child_1'] == ('roles', 'method')
assert objects['TopLevel'][2] == 'class'
assert objects['top_level'][2] == 'method'
assert objects['NestedParentA'][2] == 'class'
assert objects['NestedParentA.child_1'][2] == 'method'
assert objects['NestedParentA.any_child'][2] == 'method'
assert objects['NestedParentA.NestedChildA'][2] == 'class'
assert objects['NestedParentA.NestedChildA.subchild_1'][2] == 'method'
assert objects['NestedParentA.NestedChildA.subchild_2'][2] == 'method'
assert objects['NestedParentA.child_2'][2] == 'method'
assert objects['NestedParentB'][2] == 'class'
assert objects['NestedParentB.child_1'][2] == 'method'
@pytest.mark.sphinx('html', testroot='domain-py')
@@ -170,11 +170,11 @@ def test_resolve_xref_for_properties(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'module.html').read_text()
assert ('Link to <a class="reference internal" href="#module_a.submodule.ModTopLevel.prop"'
assert ('Link to <a class="reference internal" href="#module-a-submodule-modtoplevel-prop"'
' title="module_a.submodule.ModTopLevel.prop">'
'<code class="xref py py-attr docutils literal notranslate"><span class="pre">'
'prop</span> <span class="pre">attribute</span></code></a>' in content)
assert ('Link to <a class="reference internal" href="#module_a.submodule.ModTopLevel.prop"'
assert ('Link to <a class="reference internal" href="#module-a-submodule-modtoplevel-prop"'
' title="module_a.submodule.ModTopLevel.prop">'
'<code class="xref py py-meth docutils literal notranslate"><span class="pre">'
'prop</span> <span class="pre">method</span></code></a>' in content)
@@ -191,17 +191,20 @@ def test_domain_py_find_obj(app, status, warning):
assert (find_obj(None, None, 'NONEXISTANT', 'class') == [])
assert (find_obj(None, None, 'NestedParentA', 'class') ==
[('NestedParentA', ('roles', 'class'))])
[('NestedParentA', ('roles', 'nestedparenta', 'class'))])
assert (find_obj(None, None, 'NestedParentA.NestedChildA', 'class') ==
[('NestedParentA.NestedChildA', ('roles', 'class'))])
[('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))])
assert (find_obj(None, 'NestedParentA', 'NestedChildA', 'class') ==
[('NestedParentA.NestedChildA', ('roles', 'class'))])
[('NestedParentA.NestedChildA', ('roles', 'nestedparenta-nestedchilda', 'class'))])
assert (find_obj(None, None, 'NestedParentA.NestedChildA.subchild_1', 'meth') ==
[('NestedParentA.NestedChildA.subchild_1', ('roles', 'method'))])
[('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))])
assert (find_obj(None, 'NestedParentA', 'NestedChildA.subchild_1', 'meth') ==
[('NestedParentA.NestedChildA.subchild_1', ('roles', 'method'))])
[('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))])
assert (find_obj(None, 'NestedParentA.NestedChildA', 'subchild_1', 'meth') ==
[('NestedParentA.NestedChildA.subchild_1', ('roles', 'method'))])
[('NestedParentA.NestedChildA.subchild_1',
('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))])
def test_get_full_qualified_name():
@@ -402,7 +405,7 @@ def test_pydata(app):
[desc, ([desc_signature, desc_name, "var"],
[desc_content, ()])]))
assert 'var' in domain.objects
assert domain.objects['var'] == ('index', 'data')
assert domain.objects['var'] == ('index', 'var', 'data')
def test_pyfunction(app):
@@ -421,9 +424,9 @@ def test_pyfunction(app):
[desc_parameterlist, ()])],
[desc_content, ()])]))
assert 'func1' in domain.objects
assert domain.objects['func1'] == ('index', 'function')
assert domain.objects['func1'] == ('index', 'func1', 'function')
assert 'func2' in domain.objects
assert domain.objects['func2'] == ('index', 'function')
assert domain.objects['func2'] == ('index', 'func2', 'function')
def test_pymethod_options(app):
@@ -460,61 +463,61 @@ def test_pymethod_options(app):
# method
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth1() (Class method)', 'Class.meth1', '', None)])
entries=[('single', 'meth1() (Class method)', 'class-meth1', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "meth1"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth1' in domain.objects
assert domain.objects['Class.meth1'] == ('index', 'method')
assert domain.objects['Class.meth1'] == ('index', 'class-meth1', 'method')
# :classmethod:
assert_node(doctree[1][1][2], addnodes.index,
entries=[('single', 'meth2() (Class class method)', 'Class.meth2', '', None)])
entries=[('single', 'meth2() (Class class method)', 'class-meth2', '', None)])
assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, "classmethod "],
[desc_name, "meth2"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth2' in domain.objects
assert domain.objects['Class.meth2'] == ('index', 'method')
assert domain.objects['Class.meth2'] == ('index', 'class-meth2', 'method')
# :staticmethod:
assert_node(doctree[1][1][4], addnodes.index,
entries=[('single', 'meth3() (Class static method)', 'Class.meth3', '', None)])
entries=[('single', 'meth3() (Class static method)', 'class-meth3', '', None)])
assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, "static "],
[desc_name, "meth3"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth3' in domain.objects
assert domain.objects['Class.meth3'] == ('index', 'method')
assert domain.objects['Class.meth3'] == ('index', 'class-meth3', 'method')
# :async:
assert_node(doctree[1][1][6], addnodes.index,
entries=[('single', 'meth4() (Class method)', 'Class.meth4', '', None)])
entries=[('single', 'meth4() (Class method)', 'class-meth4', '', None)])
assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, "async "],
[desc_name, "meth4"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth4' in domain.objects
assert domain.objects['Class.meth4'] == ('index', 'method')
assert domain.objects['Class.meth4'] == ('index', 'class-meth4', 'method')
# :property:
assert_node(doctree[1][1][8], addnodes.index,
entries=[('single', 'meth5() (Class property)', 'Class.meth5', '', None)])
entries=[('single', 'meth5() (Class property)', 'class-meth5', '', None)])
assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "],
[desc_name, "meth5"])],
[desc_content, ()]))
assert 'Class.meth5' in domain.objects
assert domain.objects['Class.meth5'] == ('index', 'method')
assert domain.objects['Class.meth5'] == ('index', 'class-meth5', 'method')
# :abstractmethod:
assert_node(doctree[1][1][10], addnodes.index,
entries=[('single', 'meth6() (Class method)', 'Class.meth6', '', None)])
entries=[('single', 'meth6() (Class method)', 'class-meth6', '', None)])
assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, "abstract "],
[desc_name, "meth6"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth6' in domain.objects
assert domain.objects['Class.meth6'] == ('index', 'method')
assert domain.objects['Class.meth6'] == ('index', 'class-meth6', 'method')
def test_pyclassmethod(app):
@@ -529,13 +532,13 @@ def test_pyclassmethod(app):
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class class method)', 'Class.meth', '', None)])
entries=[('single', 'meth() (Class class method)', 'class-meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "classmethod "],
[desc_name, "meth"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'method')
assert domain.objects['Class.meth'] == ('index', 'class-meth', 'method')
def test_pystaticmethod(app):
@@ -550,13 +553,13 @@ def test_pystaticmethod(app):
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class static method)', 'Class.meth', '', None)])
entries=[('single', 'meth() (Class static method)', 'class-meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "static "],
[desc_name, "meth"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'method')
assert domain.objects['Class.meth'] == ('index', 'class-meth', 'method')
def test_pyattribute(app):
@@ -573,13 +576,13 @@ def test_pyattribute(app):
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)])
entries=[('single', 'attr (Class attribute)', 'class-attr', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"],
[desc_annotation, ": str"],
[desc_annotation, " = ''"])],
[desc_content, ()]))
assert 'Class.attr' in domain.objects
assert domain.objects['Class.attr'] == ('index', 'attribute')
assert domain.objects['Class.attr'] == ('index', 'class-attr', 'attribute')
@pytest.mark.sphinx(freshenv=True)
@@ -595,10 +598,10 @@ def test_module_index(app):
assert index.generate() == (
[('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
('s', [IndexEntry('sphinx', 1, 'index', 'module-sphinx', '', '', ''),
IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', ''), # NOQA
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
IndexEntry('sphinx.builders', 2, 'index', 'module-sphinx-builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', ''), # NOQA
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])],
False
)
@@ -610,7 +613,7 @@ def test_module_index_submodule(app):
index = PythonModuleIndex(app.env.get_domain('py'))
assert index.generate() == (
[('s', [IndexEntry('sphinx', 1, '', '', '', '', ''),
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx.config', '', '', '')])],
IndexEntry('sphinx.config', 2, 'index', 'module-sphinx-config', '', '', '')])],
False
)
@@ -639,12 +642,12 @@ def test_modindex_common_prefix(app):
restructuredtext.parse(app, text)
index = PythonModuleIndex(app.env.get_domain('py'))
assert index.generate() == (
[('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx.builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx.builders.html', '', '', '')]), # NOQA
('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx.config', '', '', '')]),
[('b', [IndexEntry('sphinx.builders', 1, 'index', 'module-sphinx-builders', '', '', ''), # NOQA
IndexEntry('sphinx.builders.html', 2, 'index', 'module-sphinx-builders-html', '', '', '')]), # NOQA
('c', [IndexEntry('sphinx.config', 0, 'index', 'module-sphinx-config', '', '', '')]),
('d', [IndexEntry('docutils', 0, 'index', 'module-docutils', '', '', '')]),
('s', [IndexEntry('sphinx', 0, 'index', 'module-sphinx', '', '', ''),
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx_intl', '', '', '')])],
IndexEntry('sphinx_intl', 0, 'index', 'module-sphinx-intl', '', '', '')])],
True
)

View File

@@ -352,23 +352,23 @@ def test_productionlist(app, status, warning):
linkText = span.text.strip()
cases.append((text, link, linkText))
assert cases == [
('A', 'Bare.html#grammar-token-_a', 'A'),
('B', 'Bare.html#grammar-token-_b', 'B'),
('P1:A', 'P1.html#grammar-token-p1_a', 'P1:A'),
('P1:B', 'P1.html#grammar-token-p1_b', 'P1:B'),
('P2:A', 'P1.html#grammar-token-p1_a', 'P1:A'),
('P2:B', 'P2.html#grammar-token-p2_b', 'P2:B'),
('Explicit title A, plain', 'Bare.html#grammar-token-_a', 'MyTitle'),
('Explicit title A, colon', 'Bare.html#grammar-token-_a', 'My:Title'),
('Explicit title P1:A, plain', 'P1.html#grammar-token-p1_a', 'MyTitle'),
('Explicit title P1:A, colon', 'P1.html#grammar-token-p1_a', 'My:Title'),
('Tilde A', 'Bare.html#grammar-token-_a', 'A'),
('Tilde P1:A', 'P1.html#grammar-token-p1_a', 'A'),
('Tilde explicit title P1:A', 'P1.html#grammar-token-p1_a', '~MyTitle'),
('Tilde, explicit title P1:A', 'P1.html#grammar-token-p1_a', 'MyTitle'),
('Dup', 'Dup2.html#grammar-token-_dup', 'Dup'),
('FirstLine', 'firstLineRule.html#grammar-token-_firstline', 'FirstLine'),
('SecondLine', 'firstLineRule.html#grammar-token-_secondline', 'SecondLine'),
('A', 'Bare.html#grammar-token-a', 'A'),
('B', 'Bare.html#grammar-token-b', 'B'),
('P1:A', 'P1.html#grammar-token-p1-a', 'P1:A'),
('P1:B', 'P1.html#grammar-token-p1-b', 'P1:B'),
('P2:A', 'P1.html#grammar-token-p1-a', 'P1:A'),
('P2:B', 'P2.html#grammar-token-p2-b', 'P2:B'),
('Explicit title A, plain', 'Bare.html#grammar-token-a', 'MyTitle'),
('Explicit title A, colon', 'Bare.html#grammar-token-a', 'My:Title'),
('Explicit title P1:A, plain', 'P1.html#grammar-token-p1-a', 'MyTitle'),
('Explicit title P1:A, colon', 'P1.html#grammar-token-p1-a', 'My:Title'),
('Tilde A', 'Bare.html#grammar-token-a', 'A'),
('Tilde P1:A', 'P1.html#grammar-token-p1-a', 'A'),
('Tilde explicit title P1:A', 'P1.html#grammar-token-p1-a', '~MyTitle'),
('Tilde, explicit title P1:A', 'P1.html#grammar-token-p1-a', 'MyTitle'),
('Dup', 'Dup2.html#grammar-token-dup', 'Dup'),
('FirstLine', 'firstLineRule.html#grammar-token-firstline', 'FirstLine'),
('SecondLine', 'firstLineRule.html#grammar-token-secondline', 'SecondLine'),
]
text = (app.outdir / 'LineContinuation.html').read_text()

View File

@@ -84,7 +84,7 @@ def test_object_inventory(app):
refs = app.env.domaindata['py']['objects']
assert 'func_without_module' in refs
assert refs['func_without_module'] == ('objects', 'function')
assert refs['func_without_module'] == ('objects', 'func-without-module', 'function')
assert 'func_without_module2' in refs
assert 'mod.func_in_module' in refs
assert 'mod.Cls' in refs
@@ -99,7 +99,7 @@ def test_object_inventory(app):
assert 'func_noindex' not in refs
assert app.env.domaindata['py']['modules']['mod'] == \
('objects', 'Module synopsis.', 'UNIX', False)
('objects', 'module-mod', 'Module synopsis.', 'UNIX', False)
assert app.env.domains['py'].data is app.env.domaindata['py']
assert app.env.domains['c'].data is app.env.domaindata['c']

View File

@@ -870,7 +870,7 @@ def test_xml_refs_in_python_domain(app):
assert_elem(
para0[0],
['SEE THIS DECORATOR:', 'sensitive_variables()', '.'],
['sensitive.sensitive_variables'])
['sensitive-sensitive-variables'])
@sphinx_intl

View File

@@ -230,6 +230,13 @@ def get_verifier(verify, verify_re):
'<p><span class="guilabel">Foo</span></p>',
r'\sphinxguilabel{Foo}',
),
(
# kbd role
'verify',
':kbd:`space`',
'<p><kbd class="kbd docutils literal notranslate">space</kbd></p>',
'\\sphinxkeyboard{\\sphinxupquote{space}}',
),
(
# non-interpolation of dashes in option role
'verify_re',