mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '2.0'
This commit is contained in:
8
CHANGES
8
CHANGES
@@ -52,6 +52,7 @@ Deprecated
|
||||
|
||||
* The ``decode`` argument of ``sphinx.pycode.ModuleAnalyzer()``
|
||||
* ``sphinx.directives.other.Index``
|
||||
* ``sphinx.environment.temp_data['gloss_entries']``
|
||||
* ``sphinx.environment.BuildEnvironment.indexentries``
|
||||
* ``sphinx.environment.collectors.indexentries.IndexEntriesCollector``
|
||||
* ``sphinx.io.FiletypeNotFoundError``
|
||||
@@ -60,6 +61,9 @@ Deprecated
|
||||
* ``sphinx.roles.Index``
|
||||
* ``sphinx.util.detect_encoding()``
|
||||
* ``sphinx.util.get_module_source()``
|
||||
* ``sphinx.util.inspect.Signature.format_annotation()``
|
||||
* ``sphinx.util.inspect.Signature.format_annotation_new()``
|
||||
* ``sphinx.util.inspect.Signature.format_annotation_old()``
|
||||
|
||||
Features added
|
||||
--------------
|
||||
@@ -69,6 +73,8 @@ Features added
|
||||
down the build
|
||||
* #6837: LaTeX: Support a nested table
|
||||
* #6966: graphviz: Support ``:class:`` option
|
||||
* #6696: html: ``:scale:`` option of image/figure directive not working for SVG
|
||||
images (imagesize-1.2.0 or above is required)
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
@@ -76,6 +82,8 @@ Bugs fixed
|
||||
* #6925: html: Remove redundant type="text/javascript" from <script> elements
|
||||
* #6906, #6907: autodoc: failed to read the source codes encoeded in cp1251
|
||||
* #6961: latex: warning for babel shown twice
|
||||
* #6559: Wrong node-ids are generated in glossary directive
|
||||
* #6986: apidoc: misdetects module name for .so file inside module
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
@@ -41,6 +41,11 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``sphinx.domains.index.IndexDirective``
|
||||
|
||||
* - ``sphinx.environment.temp_data['gloss_entries']``
|
||||
- 2.4
|
||||
- 4.0
|
||||
- ``documents.nameids``
|
||||
|
||||
* - ``sphinx.environment.BuildEnvironment.indexentries``
|
||||
- 2.4
|
||||
- 4.0
|
||||
@@ -81,6 +86,21 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.util.inspect.Signature.format_annotation()``
|
||||
- 2.4
|
||||
- 4.0
|
||||
- ``sphinx.util.typing.stringify()``
|
||||
|
||||
* - ``sphinx.util.inspect.Signature.format_annotation_new()``
|
||||
- 2.4
|
||||
- 4.0
|
||||
- ``sphinx.util.typing.stringify()``
|
||||
|
||||
* - ``sphinx.util.inspect.Signature.format_annotation_old()``
|
||||
- 2.4
|
||||
- 4.0
|
||||
- ``sphinx.util.typing.stringify()``
|
||||
|
||||
* - ``sphinx.builders.gettext.POHEADER``
|
||||
- 2.3
|
||||
- 4.0
|
||||
|
||||
@@ -138,7 +138,7 @@ class LaTeXBuilder(Builder):
|
||||
|
||||
def get_target_uri(self, docname: str, typ: str = None) -> str:
|
||||
if docname not in self.docnames:
|
||||
raise NoUri
|
||||
raise NoUri(docname, typ)
|
||||
else:
|
||||
return '%' + docname
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class ManualPageBuilder(Builder):
|
||||
def get_target_uri(self, docname: str, typ: str = None) -> str:
|
||||
if typ == 'token':
|
||||
return ''
|
||||
raise NoUri
|
||||
raise NoUri(docname, typ)
|
||||
|
||||
@progress_message(__('writing'))
|
||||
def write(self, *ignored: Any) -> None:
|
||||
|
||||
@@ -63,7 +63,7 @@ class TexinfoBuilder(Builder):
|
||||
|
||||
def get_target_uri(self, docname: str, typ: str = None) -> str:
|
||||
if docname not in self.docnames:
|
||||
raise NoUri
|
||||
raise NoUri(docname, typ)
|
||||
else:
|
||||
return '%' + docname
|
||||
|
||||
|
||||
@@ -6857,7 +6857,7 @@ class CPPDomain(Domain):
|
||||
if s is None or s.declaration is None:
|
||||
txtName = str(name)
|
||||
if txtName.startswith('std::') or txtName == 'std':
|
||||
raise NoUri()
|
||||
raise NoUri(txtName, typ)
|
||||
return None, None
|
||||
|
||||
if typ.startswith('cpp:'):
|
||||
|
||||
@@ -29,7 +29,7 @@ from sphinx.locale import _, __
|
||||
from sphinx.roles import XRefRole
|
||||
from sphinx.util import ws_re, logging, docname_join
|
||||
from sphinx.util.docutils import SphinxDirective
|
||||
from sphinx.util.nodes import clean_astext, make_refnode
|
||||
from sphinx.util.nodes import clean_astext, make_id, make_refnode
|
||||
from sphinx.util.typing import RoleFunction
|
||||
|
||||
if False:
|
||||
@@ -243,34 +243,44 @@ def split_term_classifiers(line: str) -> List[Optional[str]]:
|
||||
|
||||
|
||||
def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index_key: str,
|
||||
source: str, lineno: int, new_id: str = None) -> nodes.term:
|
||||
source: str, lineno: int, node_id: str = None,
|
||||
document: nodes.document = None) -> nodes.term:
|
||||
# get a text-only representation of the term and register it
|
||||
# as a cross-reference target
|
||||
term = nodes.term('', '', *textnodes)
|
||||
term.source = source
|
||||
term.line = lineno
|
||||
|
||||
gloss_entries = env.temp_data.setdefault('gloss_entries', set())
|
||||
termtext = term.astext()
|
||||
if new_id is None:
|
||||
new_id = nodes.make_id('term-' + termtext)
|
||||
if new_id == 'term':
|
||||
# the term is not good for node_id. Generate it by sequence number instead.
|
||||
new_id = 'term-%d' % env.new_serialno('glossary')
|
||||
while new_id in gloss_entries:
|
||||
new_id = 'term-%d' % env.new_serialno('glossary')
|
||||
gloss_entries.add(new_id)
|
||||
|
||||
if node_id:
|
||||
# node_id is given from outside (mainly i18n module), use it forcedly
|
||||
pass
|
||||
elif document:
|
||||
node_id = make_id(env, document, 'term', termtext)
|
||||
term['ids'].append(node_id)
|
||||
document.note_explicit_target(term)
|
||||
else:
|
||||
warnings.warn('make_glossary_term() expects document is passed as an argument.',
|
||||
RemovedInSphinx40Warning)
|
||||
gloss_entries = env.temp_data.setdefault('gloss_entries', set())
|
||||
node_id = nodes.make_id('term-' + termtext)
|
||||
if node_id == 'term':
|
||||
# "term" is not good for node_id. Generate it by sequence number instead.
|
||||
node_id = 'term-%d' % env.new_serialno('glossary')
|
||||
|
||||
while node_id in gloss_entries:
|
||||
node_id = 'term-%d' % env.new_serialno('glossary')
|
||||
gloss_entries.add(node_id)
|
||||
term['ids'].append(node_id)
|
||||
|
||||
std = cast(StandardDomain, env.get_domain('std'))
|
||||
std.note_object('term', termtext.lower(), new_id, location=(env.docname, lineno))
|
||||
std.note_object('term', termtext.lower(), node_id, location=(env.docname, lineno))
|
||||
|
||||
# add an index entry too
|
||||
indexnode = addnodes.index()
|
||||
indexnode['entries'] = [('single', termtext, new_id, 'main', index_key)]
|
||||
indexnode['entries'] = [('single', termtext, node_id, 'main', index_key)]
|
||||
indexnode.source, indexnode.line = term.source, term.line
|
||||
term.append(indexnode)
|
||||
term['ids'].append(new_id)
|
||||
term['names'].append(new_id)
|
||||
|
||||
return term
|
||||
|
||||
@@ -368,7 +378,8 @@ class Glossary(SphinxDirective):
|
||||
textnodes, sysmsg = self.state.inline_text(parts[0], lineno)
|
||||
|
||||
# use first classifier as a index key
|
||||
term = make_glossary_term(self.env, textnodes, parts[1], source, lineno)
|
||||
term = make_glossary_term(self.env, textnodes, parts[1], source, lineno,
|
||||
document=self.state.document)
|
||||
term.rawsource = line
|
||||
system_messages.extend(sysmsg)
|
||||
termtexts.append(term.astext())
|
||||
|
||||
@@ -128,7 +128,7 @@ def create_package_file(root: str, master_package: str, subroot: str, py_files:
|
||||
subpackages = [module_join(master_package, subroot, pkgname)
|
||||
for pkgname in subpackages]
|
||||
# build a list of sub modules
|
||||
submodules = [path.splitext(sub)[0] for sub in py_files
|
||||
submodules = [sub.split('.')[0] for sub in py_files
|
||||
if not is_skipped_module(path.join(root, sub), opts, excludes) and
|
||||
sub != INITPY]
|
||||
submodules = [module_join(master_package, subroot, modname)
|
||||
|
||||
@@ -202,18 +202,13 @@ class Locale(SphinxTransform):
|
||||
|
||||
# glossary terms update refid
|
||||
if isinstance(node, nodes.term):
|
||||
gloss_entries = self.env.temp_data.setdefault('gloss_entries', set())
|
||||
for _id in node['names']:
|
||||
if _id in gloss_entries:
|
||||
gloss_entries.remove(_id)
|
||||
|
||||
for _id in node['ids']:
|
||||
parts = split_term_classifiers(msgstr)
|
||||
patch = publish_msgstr(self.app, parts[0], source,
|
||||
node.line, self.config, settings)
|
||||
patch = make_glossary_term(self.env, patch, parts[1],
|
||||
source, node.line, _id)
|
||||
node['ids'] = patch['ids']
|
||||
node['names'] = patch['names']
|
||||
source, node.line, _id,
|
||||
self.document)
|
||||
processed = True
|
||||
|
||||
# update leaves with processed nodes
|
||||
|
||||
@@ -82,7 +82,7 @@ class ReferencesResolver(SphinxPostTransform):
|
||||
try:
|
||||
domain = self.env.domains[node['refdomain']]
|
||||
except KeyError:
|
||||
raise NoUri
|
||||
raise NoUri(target, typ)
|
||||
newnode = domain.resolve_xref(self.env, refdoc, self.app.builder,
|
||||
typ, target, node, contnode)
|
||||
# really hardwired reference types
|
||||
|
||||
@@ -21,8 +21,9 @@ from inspect import ( # NOQA
|
||||
from io import StringIO
|
||||
from typing import Any, Callable, Mapping, List, Tuple
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.typing import NoneType
|
||||
from sphinx.util.typing import stringify as stringify_annotation
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
from types import (
|
||||
@@ -381,11 +382,11 @@ class Signature:
|
||||
return None
|
||||
|
||||
def format_args(self, show_annotation: bool = True) -> str:
|
||||
def format_param_annotation(param: inspect.Parameter) -> str:
|
||||
def get_annotation(param: inspect.Parameter) -> Any:
|
||||
if isinstance(param.annotation, str) and param.name in self.annotations:
|
||||
return self.format_annotation(self.annotations[param.name])
|
||||
return self.annotations[param.name]
|
||||
else:
|
||||
return self.format_annotation(param.annotation)
|
||||
return param.annotation
|
||||
|
||||
args = []
|
||||
last_kind = None
|
||||
@@ -409,7 +410,7 @@ class Signature:
|
||||
arg.write(param.name)
|
||||
if show_annotation and param.annotation is not param.empty:
|
||||
arg.write(': ')
|
||||
arg.write(format_param_annotation(param))
|
||||
arg.write(stringify_annotation(get_annotation(param)))
|
||||
if param.default is not param.empty:
|
||||
if param.annotation is param.empty or show_annotation is False:
|
||||
arg.write('=')
|
||||
@@ -422,13 +423,13 @@ class Signature:
|
||||
arg.write(param.name)
|
||||
if show_annotation and param.annotation is not param.empty:
|
||||
arg.write(': ')
|
||||
arg.write(format_param_annotation(param))
|
||||
arg.write(stringify_annotation(get_annotation(param)))
|
||||
elif param.kind == param.VAR_KEYWORD:
|
||||
arg.write('**')
|
||||
arg.write(param.name)
|
||||
if show_annotation and param.annotation is not param.empty:
|
||||
arg.write(': ')
|
||||
arg.write(format_param_annotation(param))
|
||||
arg.write(stringify_annotation(get_annotation(param)))
|
||||
|
||||
args.append(arg.getvalue())
|
||||
last_kind = param.kind
|
||||
@@ -437,164 +438,29 @@ class Signature:
|
||||
return '(%s)' % ', '.join(args)
|
||||
else:
|
||||
if 'return' in self.annotations:
|
||||
annotation = self.format_annotation(self.annotations['return'])
|
||||
annotation = stringify_annotation(self.annotations['return'])
|
||||
else:
|
||||
annotation = self.format_annotation(self.return_annotation)
|
||||
annotation = stringify_annotation(self.return_annotation)
|
||||
|
||||
return '(%s) -> %s' % (', '.join(args), annotation)
|
||||
|
||||
def format_annotation(self, annotation: Any) -> str:
|
||||
"""Return formatted representation of a type annotation.
|
||||
|
||||
Show qualified names for types and additional details for types from
|
||||
the ``typing`` module.
|
||||
|
||||
Displaying complex types from ``typing`` relies on its private API.
|
||||
"""
|
||||
if isinstance(annotation, str):
|
||||
return annotation
|
||||
elif isinstance(annotation, typing.TypeVar): # type: ignore
|
||||
return annotation.__name__
|
||||
elif not annotation:
|
||||
return repr(annotation)
|
||||
elif annotation is NoneType: # type: ignore
|
||||
return 'None'
|
||||
elif getattr(annotation, '__module__', None) == 'builtins':
|
||||
return annotation.__qualname__
|
||||
elif annotation is Ellipsis:
|
||||
return '...'
|
||||
|
||||
if sys.version_info >= (3, 7): # py37+
|
||||
return self.format_annotation_new(annotation)
|
||||
else:
|
||||
return self.format_annotation_old(annotation)
|
||||
"""Return formatted representation of a type annotation."""
|
||||
warnings.warn('format_annotation() is deprecated',
|
||||
RemovedInSphinx40Warning)
|
||||
return stringify_annotation(annotation)
|
||||
|
||||
def format_annotation_new(self, annotation: Any) -> str:
|
||||
"""format_annotation() for py37+"""
|
||||
module = getattr(annotation, '__module__', None)
|
||||
if module == 'typing':
|
||||
if getattr(annotation, '_name', None):
|
||||
qualname = annotation._name
|
||||
elif getattr(annotation, '__qualname__', None):
|
||||
qualname = annotation.__qualname__
|
||||
elif getattr(annotation, '__forward_arg__', None):
|
||||
qualname = annotation.__forward_arg__
|
||||
else:
|
||||
qualname = self.format_annotation(annotation.__origin__) # ex. Union
|
||||
elif hasattr(annotation, '__qualname__'):
|
||||
qualname = '%s.%s' % (module, annotation.__qualname__)
|
||||
else:
|
||||
qualname = repr(annotation)
|
||||
|
||||
if getattr(annotation, '__args__', None):
|
||||
if qualname == 'Union':
|
||||
if len(annotation.__args__) == 2 and annotation.__args__[1] is NoneType: # type: ignore # NOQA
|
||||
return 'Optional[%s]' % self.format_annotation(annotation.__args__[0])
|
||||
else:
|
||||
args = ', '.join(self.format_annotation(a) for a in annotation.__args__)
|
||||
return '%s[%s]' % (qualname, args)
|
||||
elif qualname == 'Callable':
|
||||
args = ', '.join(self.format_annotation(a) for a in annotation.__args__[:-1])
|
||||
returns = self.format_annotation(annotation.__args__[-1])
|
||||
return '%s[[%s], %s]' % (qualname, args, returns)
|
||||
elif annotation._special:
|
||||
return qualname
|
||||
else:
|
||||
args = ', '.join(self.format_annotation(a) for a in annotation.__args__)
|
||||
return '%s[%s]' % (qualname, args)
|
||||
|
||||
return qualname
|
||||
warnings.warn('format_annotation_new() is deprecated',
|
||||
RemovedInSphinx40Warning)
|
||||
return stringify_annotation(annotation)
|
||||
|
||||
def format_annotation_old(self, annotation: Any) -> str:
|
||||
"""format_annotation() for py36 or below"""
|
||||
module = getattr(annotation, '__module__', None)
|
||||
if module == 'typing':
|
||||
if getattr(annotation, '_name', None):
|
||||
qualname = annotation._name
|
||||
elif getattr(annotation, '__qualname__', None):
|
||||
qualname = annotation.__qualname__
|
||||
elif getattr(annotation, '__forward_arg__', None):
|
||||
qualname = annotation.__forward_arg__
|
||||
elif getattr(annotation, '__origin__', None):
|
||||
qualname = self.format_annotation(annotation.__origin__) # ex. Union
|
||||
else:
|
||||
qualname = repr(annotation).replace('typing.', '')
|
||||
elif hasattr(annotation, '__qualname__'):
|
||||
qualname = '%s.%s' % (module, annotation.__qualname__)
|
||||
else:
|
||||
qualname = repr(annotation)
|
||||
|
||||
if (isinstance(annotation, typing.TupleMeta) and # type: ignore
|
||||
not hasattr(annotation, '__tuple_params__')): # for Python 3.6
|
||||
params = annotation.__args__
|
||||
if params:
|
||||
param_str = ', '.join(self.format_annotation(p) for p in params)
|
||||
return '%s[%s]' % (qualname, param_str)
|
||||
else:
|
||||
return qualname
|
||||
elif isinstance(annotation, typing.GenericMeta):
|
||||
params = None
|
||||
if hasattr(annotation, '__args__'):
|
||||
# for Python 3.5.2+
|
||||
if annotation.__args__ is None or len(annotation.__args__) <= 2: # type: ignore # NOQA
|
||||
params = annotation.__args__ # type: ignore
|
||||
else: # typing.Callable
|
||||
args = ', '.join(self.format_annotation(arg) for arg
|
||||
in annotation.__args__[:-1]) # type: ignore
|
||||
result = self.format_annotation(annotation.__args__[-1]) # type: ignore
|
||||
return '%s[[%s], %s]' % (qualname, args, result)
|
||||
elif hasattr(annotation, '__parameters__'):
|
||||
# for Python 3.5.0 and 3.5.1
|
||||
params = annotation.__parameters__ # type: ignore
|
||||
if params is not None:
|
||||
param_str = ', '.join(self.format_annotation(p) for p in params)
|
||||
return '%s[%s]' % (qualname, param_str)
|
||||
elif (hasattr(typing, 'UnionMeta') and
|
||||
isinstance(annotation, typing.UnionMeta) and # type: ignore
|
||||
hasattr(annotation, '__union_params__')): # for Python 3.5
|
||||
params = annotation.__union_params__
|
||||
if params is not None:
|
||||
if len(params) == 2 and params[1] is NoneType: # type: ignore
|
||||
return 'Optional[%s]' % self.format_annotation(params[0])
|
||||
else:
|
||||
param_str = ', '.join(self.format_annotation(p) for p in params)
|
||||
return '%s[%s]' % (qualname, param_str)
|
||||
elif (hasattr(annotation, '__origin__') and
|
||||
annotation.__origin__ is typing.Union): # for Python 3.5.2+
|
||||
params = annotation.__args__
|
||||
if params is not None:
|
||||
if len(params) == 2 and params[1] is NoneType: # type: ignore
|
||||
return 'Optional[%s]' % self.format_annotation(params[0])
|
||||
else:
|
||||
param_str = ', '.join(self.format_annotation(p) for p in params)
|
||||
return 'Union[%s]' % param_str
|
||||
elif (isinstance(annotation, typing.CallableMeta) and # type: ignore
|
||||
getattr(annotation, '__args__', None) is not None and
|
||||
hasattr(annotation, '__result__')): # for Python 3.5
|
||||
# Skipped in the case of plain typing.Callable
|
||||
args = annotation.__args__
|
||||
if args is None:
|
||||
return qualname
|
||||
elif args is Ellipsis:
|
||||
args_str = '...'
|
||||
else:
|
||||
formatted_args = (self.format_annotation(a) for a in args)
|
||||
args_str = '[%s]' % ', '.join(formatted_args)
|
||||
return '%s[%s, %s]' % (qualname,
|
||||
args_str,
|
||||
self.format_annotation(annotation.__result__))
|
||||
elif (isinstance(annotation, typing.TupleMeta) and # type: ignore
|
||||
hasattr(annotation, '__tuple_params__') and
|
||||
hasattr(annotation, '__tuple_use_ellipsis__')): # for Python 3.5
|
||||
params = annotation.__tuple_params__
|
||||
if params is not None:
|
||||
param_strings = [self.format_annotation(p) for p in params]
|
||||
if annotation.__tuple_use_ellipsis__:
|
||||
param_strings.append('...')
|
||||
return '%s[%s]' % (qualname,
|
||||
', '.join(param_strings))
|
||||
|
||||
return qualname
|
||||
warnings.warn('format_annotation_old() is deprecated',
|
||||
RemovedInSphinx40Warning)
|
||||
return stringify_annotation(annotation)
|
||||
|
||||
|
||||
def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
|
||||
|
||||
@@ -28,6 +28,7 @@ if False:
|
||||
# For type annotation
|
||||
from typing import Type # for python3.5.1
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.utils.tags import Tags
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -435,6 +436,28 @@ def inline_all_toctrees(builder: "Builder", docnameset: Set[str], docname: str,
|
||||
return tree
|
||||
|
||||
|
||||
def make_id(env: "BuildEnvironment", document: nodes.document,
|
||||
prefix: str = '', term: str = None) -> str:
|
||||
"""Generate an appropriate node_id for given *prefix* and *term*."""
|
||||
node_id = None
|
||||
if prefix:
|
||||
idformat = prefix + "-%s"
|
||||
else:
|
||||
idformat = document.settings.id_prefix + "%s"
|
||||
|
||||
# try to generate node_id by *term*
|
||||
if prefix and term:
|
||||
node_id = nodes.make_id(idformat % term)
|
||||
if node_id == prefix:
|
||||
# *term* is not good to generate a node_id.
|
||||
node_id = None
|
||||
|
||||
while node_id is None or node_id in document.ids:
|
||||
node_id = idformat % env.new_serialno(prefix)
|
||||
|
||||
return node_id
|
||||
|
||||
|
||||
def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str,
|
||||
child: Node, title: str = None) -> nodes.reference:
|
||||
"""Shortcut to create a reference node."""
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, List, Tuple, Union
|
||||
import sys
|
||||
import typing
|
||||
from typing import Any, Callable, Dict, List, Tuple, TypeVar, Union
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.states import Inliner
|
||||
@@ -35,3 +37,153 @@ TitleGetter = Callable[[nodes.Node], str]
|
||||
|
||||
# inventory data on memory
|
||||
Inventory = Dict[str, Dict[str, Tuple[str, str, str, str]]]
|
||||
|
||||
|
||||
def stringify(annotation: Any) -> str:
|
||||
"""Stringify type annotation object."""
|
||||
if isinstance(annotation, str):
|
||||
return annotation
|
||||
elif isinstance(annotation, TypeVar): # type: ignore
|
||||
return annotation.__name__
|
||||
elif not annotation:
|
||||
return repr(annotation)
|
||||
elif annotation is NoneType: # type: ignore
|
||||
return 'None'
|
||||
elif getattr(annotation, '__module__', None) == 'builtins':
|
||||
return annotation.__qualname__
|
||||
elif annotation is Ellipsis:
|
||||
return '...'
|
||||
|
||||
if sys.version_info >= (3, 7): # py37+
|
||||
return _stringify_py37(annotation)
|
||||
else:
|
||||
return _stringify_py36(annotation)
|
||||
|
||||
|
||||
def _stringify_py37(annotation: Any) -> str:
|
||||
"""stringify() for py37+."""
|
||||
module = getattr(annotation, '__module__', None)
|
||||
if module == 'typing':
|
||||
if getattr(annotation, '_name', None):
|
||||
qualname = annotation._name
|
||||
elif getattr(annotation, '__qualname__', None):
|
||||
qualname = annotation.__qualname__
|
||||
elif getattr(annotation, '__forward_arg__', None):
|
||||
qualname = annotation.__forward_arg__
|
||||
else:
|
||||
qualname = stringify(annotation.__origin__) # ex. Union
|
||||
elif hasattr(annotation, '__qualname__'):
|
||||
qualname = '%s.%s' % (module, annotation.__qualname__)
|
||||
else:
|
||||
qualname = repr(annotation)
|
||||
|
||||
if getattr(annotation, '__args__', None):
|
||||
if qualname == 'Union':
|
||||
if len(annotation.__args__) == 2 and annotation.__args__[1] is NoneType: # type: ignore # NOQA
|
||||
return 'Optional[%s]' % stringify(annotation.__args__[0])
|
||||
else:
|
||||
args = ', '.join(stringify(a) for a in annotation.__args__)
|
||||
return '%s[%s]' % (qualname, args)
|
||||
elif qualname == 'Callable':
|
||||
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
|
||||
returns = stringify(annotation.__args__[-1])
|
||||
return '%s[[%s], %s]' % (qualname, args, returns)
|
||||
elif annotation._special:
|
||||
return qualname
|
||||
else:
|
||||
args = ', '.join(stringify(a) for a in annotation.__args__)
|
||||
return '%s[%s]' % (qualname, args)
|
||||
|
||||
return qualname
|
||||
|
||||
|
||||
def _stringify_py36(annotation: Any) -> str:
|
||||
"""stringify() for py35 and py36."""
|
||||
module = getattr(annotation, '__module__', None)
|
||||
if module == 'typing':
|
||||
if getattr(annotation, '_name', None):
|
||||
qualname = annotation._name
|
||||
elif getattr(annotation, '__qualname__', None):
|
||||
qualname = annotation.__qualname__
|
||||
elif getattr(annotation, '__forward_arg__', None):
|
||||
qualname = annotation.__forward_arg__
|
||||
elif getattr(annotation, '__origin__', None):
|
||||
qualname = stringify(annotation.__origin__) # ex. Union
|
||||
else:
|
||||
qualname = repr(annotation).replace('typing.', '')
|
||||
elif hasattr(annotation, '__qualname__'):
|
||||
qualname = '%s.%s' % (module, annotation.__qualname__)
|
||||
else:
|
||||
qualname = repr(annotation)
|
||||
|
||||
if (isinstance(annotation, typing.TupleMeta) and # type: ignore
|
||||
not hasattr(annotation, '__tuple_params__')): # for Python 3.6
|
||||
params = annotation.__args__
|
||||
if params:
|
||||
param_str = ', '.join(stringify(p) for p in params)
|
||||
return '%s[%s]' % (qualname, param_str)
|
||||
else:
|
||||
return qualname
|
||||
elif isinstance(annotation, typing.GenericMeta):
|
||||
params = None
|
||||
if hasattr(annotation, '__args__'):
|
||||
# for Python 3.5.2+
|
||||
if annotation.__args__ is None or len(annotation.__args__) <= 2: # type: ignore # NOQA
|
||||
params = annotation.__args__ # type: ignore
|
||||
else: # typing.Callable
|
||||
args = ', '.join(stringify(arg) for arg
|
||||
in annotation.__args__[:-1]) # type: ignore
|
||||
result = stringify(annotation.__args__[-1]) # type: ignore
|
||||
return '%s[[%s], %s]' % (qualname, args, result)
|
||||
elif hasattr(annotation, '__parameters__'):
|
||||
# for Python 3.5.0 and 3.5.1
|
||||
params = annotation.__parameters__ # type: ignore
|
||||
if params is not None:
|
||||
param_str = ', '.join(stringify(p) for p in params)
|
||||
return '%s[%s]' % (qualname, param_str)
|
||||
elif (hasattr(typing, 'UnionMeta') and
|
||||
isinstance(annotation, typing.UnionMeta) and # type: ignore
|
||||
hasattr(annotation, '__union_params__')): # for Python 3.5
|
||||
params = annotation.__union_params__
|
||||
if params is not None:
|
||||
if len(params) == 2 and params[1] is NoneType: # type: ignore
|
||||
return 'Optional[%s]' % stringify(params[0])
|
||||
else:
|
||||
param_str = ', '.join(stringify(p) for p in params)
|
||||
return '%s[%s]' % (qualname, param_str)
|
||||
elif (hasattr(annotation, '__origin__') and
|
||||
annotation.__origin__ is typing.Union): # for Python 3.5.2+
|
||||
params = annotation.__args__
|
||||
if params is not None:
|
||||
if len(params) == 2 and params[1] is NoneType: # type: ignore
|
||||
return 'Optional[%s]' % stringify(params[0])
|
||||
else:
|
||||
param_str = ', '.join(stringify(p) for p in params)
|
||||
return 'Union[%s]' % param_str
|
||||
elif (isinstance(annotation, typing.CallableMeta) and # type: ignore
|
||||
getattr(annotation, '__args__', None) is not None and
|
||||
hasattr(annotation, '__result__')): # for Python 3.5
|
||||
# Skipped in the case of plain typing.Callable
|
||||
args = annotation.__args__
|
||||
if args is None:
|
||||
return qualname
|
||||
elif args is Ellipsis:
|
||||
args_str = '...'
|
||||
else:
|
||||
formatted_args = (stringify(a) for a in args)
|
||||
args_str = '[%s]' % ', '.join(formatted_args)
|
||||
return '%s[%s, %s]' % (qualname,
|
||||
args_str,
|
||||
stringify(annotation.__result__))
|
||||
elif (isinstance(annotation, typing.TupleMeta) and # type: ignore
|
||||
hasattr(annotation, '__tuple_params__') and
|
||||
hasattr(annotation, '__tuple_use_ellipsis__')): # for Python 3.5
|
||||
params = annotation.__tuple_params__
|
||||
if params is not None:
|
||||
param_strings = [stringify(p) for p in params]
|
||||
if annotation.__tuple_use_ellipsis__:
|
||||
param_strings.append('...')
|
||||
return '%s[%s]' % (qualname,
|
||||
', '.join(param_strings))
|
||||
|
||||
return qualname
|
||||
|
||||
@@ -578,23 +578,6 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
|
||||
node['uri'] = posixpath.join(self.builder.imgpath,
|
||||
self.builder.images[olduri])
|
||||
|
||||
uri = node['uri']
|
||||
if uri.lower().endswith(('svg', 'svgz')):
|
||||
atts = {'src': uri}
|
||||
if 'width' in node:
|
||||
atts['width'] = node['width']
|
||||
if 'height' in node:
|
||||
atts['height'] = node['height']
|
||||
atts['alt'] = node.get('alt', uri)
|
||||
if 'align' in node:
|
||||
self.body.append('<div align="%s" class="align-%s">' %
|
||||
(node['align'], node['align']))
|
||||
self.context.append('</div>\n')
|
||||
else:
|
||||
self.context.append('')
|
||||
self.body.append(self.emptytag(node, 'img', '', **atts))
|
||||
return
|
||||
|
||||
if 'scale' in node:
|
||||
# Try to figure out image height and width. Docutils does that too,
|
||||
# but it tries the final file name, which does not necessarily exist
|
||||
@@ -609,6 +592,30 @@ class HTMLTranslator(SphinxTranslator, BaseTranslator):
|
||||
node['width'] = str(size[0])
|
||||
if 'height' not in node:
|
||||
node['height'] = str(size[1])
|
||||
|
||||
uri = node['uri']
|
||||
if uri.lower().endswith(('svg', 'svgz')):
|
||||
atts = {'src': uri}
|
||||
if 'width' in node:
|
||||
atts['width'] = node['width']
|
||||
if 'height' in node:
|
||||
atts['height'] = node['height']
|
||||
if 'scale' in node:
|
||||
scale = node['scale'] / 100.0
|
||||
if 'width' in atts:
|
||||
atts['width'] = int(atts['width']) * scale
|
||||
if 'height' in atts:
|
||||
atts['height'] = int(atts['height']) * scale
|
||||
atts['alt'] = node.get('alt', uri)
|
||||
if 'align' in node:
|
||||
self.body.append('<div align="%s" class="align-%s">' %
|
||||
(node['align'], node['align']))
|
||||
self.context.append('</div>\n')
|
||||
else:
|
||||
self.context.append('')
|
||||
self.body.append(self.emptytag(node, 'img', '', **atts))
|
||||
return
|
||||
|
||||
super().visit_image(node)
|
||||
|
||||
# overwritten
|
||||
|
||||
@@ -519,23 +519,6 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
||||
node['uri'] = posixpath.join(self.builder.imgpath,
|
||||
self.builder.images[olduri])
|
||||
|
||||
uri = node['uri']
|
||||
if uri.lower().endswith(('svg', 'svgz')):
|
||||
atts = {'src': uri}
|
||||
if 'width' in node:
|
||||
atts['width'] = node['width']
|
||||
if 'height' in node:
|
||||
atts['height'] = node['height']
|
||||
atts['alt'] = node.get('alt', uri)
|
||||
if 'align' in node:
|
||||
self.body.append('<div align="%s" class="align-%s">' %
|
||||
(node['align'], node['align']))
|
||||
self.context.append('</div>\n')
|
||||
else:
|
||||
self.context.append('')
|
||||
self.body.append(self.emptytag(node, 'img', '', **atts))
|
||||
return
|
||||
|
||||
if 'scale' in node:
|
||||
# Try to figure out image height and width. Docutils does that too,
|
||||
# but it tries the final file name, which does not necessarily exist
|
||||
@@ -550,6 +533,30 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
||||
node['width'] = str(size[0])
|
||||
if 'height' not in node:
|
||||
node['height'] = str(size[1])
|
||||
|
||||
uri = node['uri']
|
||||
if uri.lower().endswith(('svg', 'svgz')):
|
||||
atts = {'src': uri}
|
||||
if 'width' in node:
|
||||
atts['width'] = node['width']
|
||||
if 'height' in node:
|
||||
atts['height'] = node['height']
|
||||
if 'scale' in node:
|
||||
scale = node['scale'] / 100.0
|
||||
if 'width' in atts:
|
||||
atts['width'] = int(atts['width']) * scale
|
||||
if 'height' in atts:
|
||||
atts['height'] = int(atts['height']) * scale
|
||||
atts['alt'] = node.get('alt', uri)
|
||||
if 'align' in node:
|
||||
self.body.append('<div align="%s" class="align-%s">' %
|
||||
(node['align'], node['align']))
|
||||
self.context.append('</div>\n')
|
||||
else:
|
||||
self.context.append('')
|
||||
self.body.append(self.emptytag(node, 'img', '', **atts))
|
||||
return
|
||||
|
||||
super().visit_image(node)
|
||||
|
||||
# overwritten
|
||||
|
||||
@@ -264,6 +264,16 @@ def test_glossary_alphanumeric(app):
|
||||
assert ("/", "/", "term", "index", "term-0", -1) in objects
|
||||
|
||||
|
||||
def test_glossary_conflicted_labels(app):
|
||||
text = (".. _term-foo:\n"
|
||||
".. glossary::\n"
|
||||
"\n"
|
||||
" foo\n")
|
||||
restructuredtext.parse(app, text)
|
||||
objects = list(app.env.get_domain("std").get_objects())
|
||||
assert ("foo", "foo", "term", "index", "term-0", -1) in objects
|
||||
|
||||
|
||||
def test_cmdoption(app):
|
||||
text = (".. program:: ls\n"
|
||||
"\n"
|
||||
|
||||
@@ -17,7 +17,9 @@ from docutils.parsers import rst
|
||||
from docutils.utils import new_document
|
||||
|
||||
from sphinx.transforms import ApplySourceWorkaround
|
||||
from sphinx.util.nodes import NodeMatcher, extract_messages, clean_astext, split_explicit_title
|
||||
from sphinx.util.nodes import (
|
||||
NodeMatcher, extract_messages, clean_astext, make_id, split_explicit_title
|
||||
)
|
||||
|
||||
|
||||
def _transform(doctree):
|
||||
@@ -27,6 +29,7 @@ def _transform(doctree):
|
||||
def create_new_document():
|
||||
settings = frontend.OptionParser(
|
||||
components=(rst.Parser,)).get_default_values()
|
||||
settings.id_prefix = 'id'
|
||||
document = new_document('dummy.txt', settings)
|
||||
return document
|
||||
|
||||
@@ -180,6 +183,20 @@ def test_clean_astext():
|
||||
assert 'hello world' == clean_astext(node)
|
||||
|
||||
|
||||
def test_make_id(app):
|
||||
document = create_new_document()
|
||||
assert make_id(app.env, document) == 'id0'
|
||||
assert make_id(app.env, document, 'term') == 'term-0'
|
||||
assert make_id(app.env, document, 'term', 'Sphinx') == 'term-sphinx'
|
||||
|
||||
# when same ID is already registered
|
||||
document.ids['term-sphinx'] = True
|
||||
assert make_id(app.env, document, 'term', 'Sphinx') == 'term-1'
|
||||
|
||||
document.ids['term-2'] = True
|
||||
assert make_id(app.env, document, 'term') == 'term-3'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'title, expected',
|
||||
[
|
||||
|
||||
98
tests/test_util_typing.py
Normal file
98
tests/test_util_typing.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
test_util_typing
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests util.typing functions.
|
||||
|
||||
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from numbers import Integral
|
||||
from typing import Any, Dict, List, TypeVar, Union, Callable, Tuple, Optional
|
||||
|
||||
from sphinx.util.typing import stringify
|
||||
|
||||
|
||||
class MyClass1:
|
||||
pass
|
||||
|
||||
|
||||
class MyClass2(MyClass1):
|
||||
__qualname__ = '<MyClass2>'
|
||||
|
||||
|
||||
def test_stringify():
|
||||
assert stringify(int) == "int"
|
||||
assert stringify(str) == "str"
|
||||
assert stringify(None) == "None"
|
||||
assert stringify(Integral) == "numbers.Integral"
|
||||
assert stringify(Any) == "Any"
|
||||
|
||||
|
||||
def test_stringify_type_hints_containers():
|
||||
assert stringify(List) == "List"
|
||||
assert stringify(Dict) == "Dict"
|
||||
assert stringify(List[int]) == "List[int]"
|
||||
assert stringify(List[str]) == "List[str]"
|
||||
assert stringify(Dict[str, float]) == "Dict[str, float]"
|
||||
assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]"
|
||||
assert stringify(Tuple[str, ...]) == "Tuple[str, ...]"
|
||||
assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]"
|
||||
|
||||
|
||||
def test_stringify_type_hints_string():
|
||||
assert stringify("int") == "int"
|
||||
assert stringify("str") == "str"
|
||||
assert stringify(List["int"]) == "List[int]"
|
||||
assert stringify("Tuple[str]") == "Tuple[str]"
|
||||
assert stringify("unknown") == "unknown"
|
||||
|
||||
|
||||
def test_stringify_type_hints_Callable():
|
||||
assert stringify(Callable) == "Callable"
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
assert stringify(Callable[[str], int]) == "Callable[[str], int]"
|
||||
assert stringify(Callable[..., int]) == "Callable[[...], int]"
|
||||
else:
|
||||
assert stringify(Callable[[str], int]) == "Callable[str, int]"
|
||||
assert stringify(Callable[..., int]) == "Callable[..., int]"
|
||||
|
||||
|
||||
def test_stringify_type_hints_Union():
|
||||
assert stringify(Optional[int]) == "Optional[int]"
|
||||
assert stringify(Union[str, None]) == "Optional[str]"
|
||||
assert stringify(Union[int, str]) == "Union[int, str]"
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
assert stringify(Union[int, Integral]) == "Union[int, numbers.Integral]"
|
||||
assert (stringify(Union[MyClass1, MyClass2]) ==
|
||||
"Union[test_util_typing.MyClass1, test_util_typing.<MyClass2>]")
|
||||
else:
|
||||
assert stringify(Union[int, Integral]) == "numbers.Integral"
|
||||
assert stringify(Union[MyClass1, MyClass2]) == "test_util_typing.MyClass1"
|
||||
|
||||
|
||||
def test_stringify_type_hints_typevars():
|
||||
T = TypeVar('T')
|
||||
T_co = TypeVar('T_co', covariant=True)
|
||||
T_contra = TypeVar('T_contra', contravariant=True)
|
||||
|
||||
assert stringify(T) == "T"
|
||||
assert stringify(T_co) == "T_co"
|
||||
assert stringify(T_contra) == "T_contra"
|
||||
assert stringify(List[T]) == "List[T]"
|
||||
|
||||
|
||||
def test_stringify_type_hints_custom_class():
|
||||
assert stringify(MyClass1) == "test_util_typing.MyClass1"
|
||||
assert stringify(MyClass2) == "test_util_typing.<MyClass2>"
|
||||
|
||||
|
||||
def test_stringify_type_hints_alias():
|
||||
MyStr = str
|
||||
MyTuple = Tuple[str, str]
|
||||
assert stringify(MyStr) == "str"
|
||||
assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore
|
||||
Reference in New Issue
Block a user