Merge branch 'master' into integrate_source_suffix_and_source_parsers

This commit is contained in:
Takeshi KOMIYA
2018-01-31 00:04:11 +09:00
36 changed files with 336 additions and 110 deletions

11
CHANGES
View File

@@ -7,6 +7,9 @@ Dependencies
Incompatible changes
--------------------
* #4460: extensions which stores any data to environment should return the
version of its env data structure as metadata. In detail, please see
:ref:`ext-metadata`.
* Sphinx expects source parser modules to have supported file formats as
``Parser.supported`` attribute
@@ -56,6 +59,11 @@ Bugs fixed
* #4415: autodoc classifies inherited classmethods as regular methods
* #4415: autodoc classifies inherited staticmethods as regular methods
* #4472: DOCUMENTATION_OPTIONS is not defined
* #4491: autodoc: prefer _MockImporter over other importers in sys.meta_path
* #4490: autodoc: type annotation is broken with python 3.7.0a4+
* utils package is no longer installed
* #3952: apidoc: module header is too escaped
* #4275: Formats accepted by sphinx.util.i18n.format_date are limited
Testing
--------
@@ -235,6 +243,9 @@ Bugs fixed
* #4438: math: math with labels with whitespace cause html error
* #2437: make full reference for classes, aliased with "alias of"
* #4434: pure numbers as link targets produce warning
* #4477: Build fails after building specific files
* #4449: apidoc: include "empty" packages that contain modules
* #3917: citation labels are tranformed to ellipsis
Testing
--------

View File

@@ -207,16 +207,10 @@ The builder's "name" must be given to the **-b** command-line option of
.. autoattribute:: supported_image_types
Note that a direct PDF builder is being provided by `rinohtype`_. The builder's
name is ``rinoh``. Refer to the `rinohtype manual`_ for details. There is also
PDF builder using ReportLab in `rst2pdf`_ version 0.12 or greater. However,
rst2pdf is no longer being actively maintained and suffers from some problems
when used with recent Sphinx versions. See the `rst2pdf manual`_ for usage
instructions.
name is ``rinoh``. Refer to the `rinohtype manual`_ for details.
.. _rinohtype: https://github.com/brechtm/rinohtype
.. _rinohtype manual: http://www.mos6581.org/rinohtype/quickstart.html#sphinx-builder
.. _rst2pdf: https://github.com/rst2pdf/rst2pdf
.. _rst2pdf manual: https://ralsina.me/static/manual.pdf
.. module:: sphinx.builders.text
.. class:: TextBuilder

View File

@@ -17,6 +17,9 @@ Builder API
.. autoattribute:: format
.. autoattribute:: epilog
.. autoattribute:: supported_image_types
.. autoattribute:: supported_remote_images
.. autoattribute:: supported_data_uri_images
.. autoattribute:: default_translator_class
These methods are predefined and will be called from the application:

View File

@@ -52,6 +52,8 @@ Note that it is still necessary to register the builder using
.. _entry points: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins
.. _ext-metadata:
Extension metadata
------------------
@@ -63,6 +65,11 @@ as metadata of the extension. Metadata keys currently recognized are:
* ``'version'``: a string that identifies the extension version. It is used for
extension version requirement checking (see :confval:`needs_extensions`) and
informational purposes. If not given, ``"unknown version"`` is substituted.
* ``'env_version'``: an integer that identifies the version of env data
structure if the extension stores any data to environment. It is used to
detect the data structure has been changed from last build. The extensions
have to increment the version when data structure has changed. If not given,
Sphinx considers the extension does not stores any data to environment.
* ``'parallel_read_safe'``: a boolean that specifies if parallel reading of
source files can be used when the extension is loaded. It defaults to
``False``, i.e. you have to explicitly specify your extension to be

View File

@@ -11,12 +11,9 @@ How do I...
... create PDF files without LaTeX?
`rinohtype`_ provides a PDF builder that can be used as a drop-in
replacement for the LaTeX builder. Alternatively, you can use `rst2pdf`_
version 0.12 or greater which comes with built-in Sphinx integration. See
the :ref:`builders` section for details.
replacement for the LaTeX builder.
.. _rinohtype: https://github.com/brechtm/rinohtype
.. _rst2pdf: https://github.com/rst2pdf/rst2pdf
... get section numbers?
They are automatic in LaTeX output; for HTML, give a ``:numbered:`` option to

View File

@@ -8,7 +8,7 @@ you have a directory containing a bunch of reST-formatted documents (and
possibly subdirectories of docs in there as well), Sphinx can generate a
nicely-organized arrangement of HTML files (in some other directory) for easy
browsing and navigation. But from the same source, it can also generate a PDF
file using LaTeX, `rinohtype`_ or `rst2pdf`_ (see :ref:`builders`).
file using LaTeX.
The focus is on hand-written documentation, rather than auto-generated API docs.
Though there is support for that kind of documentation as well (which is
@@ -21,7 +21,6 @@ also `Write the docs <https://write-the-docs.readthedocs.io/>`_, written by Eric
Holscher.
.. _rinohtype: https://github.com/brechtm/rinohtype
.. _rst2pdf: https://github.com/rst2pdf/rst2pdf
Conversion from other systems
-----------------------------

View File

@@ -422,3 +422,11 @@ Third Party Themes
.. versionchanged:: 1.4
**sphinx_rtd_theme** has become optional.
Besides this, there are a lot of third party themes. You can find them on
PyPI__, GitHub__, sphinx-themes.org__ and so on.
.. __: https://pypi.python.org/pypi?:action=browse&c=599
.. __: https://github.com/search?utf8=%E2%9C%93&q=sphinx+theme&type=
.. __: https://sphinx-themes.org/

View File

@@ -34,6 +34,12 @@ max-line-length = 95
ignore = E116,E241,E251,E741
exclude = .git,.tox,.venv,tests/*,build/*,doc/_build/*,sphinx/search/*,sphinx/pycode/pgen2/*,doc/ext/example*.py
[flake8:local-plugins]
extension =
X101 = utils.checks:sphinx_has_header
paths =
.
[mypy]
python_version = 2.7
show_column_numbers = True

View File

@@ -15,7 +15,7 @@ if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 4):
print('ERROR: Sphinx requires at least Python 2.7 or 3.4 to run.')
sys.exit(1)
requires = [
install_requires = [
'six>=1.5',
'Jinja2>=2.3',
'Pygments>=2.0',
@@ -47,7 +47,7 @@ extras_require = {
'pytest',
'pytest-cov',
'html5lib',
'flake8',
'flake8>=3.5.0',
],
'test:python_version<"3"': [
'enum34',
@@ -226,15 +226,8 @@ setup(
'distutils.commands': [
'build_sphinx = sphinx.setup_command:BuildDoc',
],
# consider moving this to 'flake8:local-plugins' once flake8 3.5.0 is
# in the wild:
# http://flake8.pycqa.org/en/latest/user/configuration.html\
# #using-local-plugins
'flake8.extension': [
'X101 = utils.checks:sphinx_has_header',
],
},
install_requires=requires,
install_requires=install_requires,
extras_require=extras_require,
cmdclass=cmdclass,
)

View File

@@ -226,8 +226,7 @@ class Sphinx(object):
# the config file itself can be an extension
if self.config.setup:
self._setting_up_extension = ['conf.py']
# py31 doesn't have 'callable' function for below check
if hasattr(self.config.setup, '__call__'):
if callable(self.config.setup):
self.config.setup(self)
else:
raise ConfigError(
@@ -297,6 +296,9 @@ class Sphinx(object):
logger.info(bold(__('loading pickled environment... ')), nonl=True)
filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
self.env = BuildEnvironment.frompickle(filename, self)
needed, reason = self.env.need_refresh(self)
if needed:
raise IOError(reason)
self.env.domains = {}
for domain in self.registry.create_domains(self.env):
# this can raise if the data version doesn't fit

View File

@@ -59,8 +59,8 @@ class Builder(object):
#: ``project``
epilog = '' # type: unicode
# default translator class for the builder. This will be overrided by
# ``app.set_translator()``.
#: default translator class for the builder. This can be overrided by
#: :py:meth:`app.set_translator()`.
default_translator_class = None # type: nodes.NodeVisitor
# doctree versioning method
versioning_method = 'none' # type: unicode
@@ -73,7 +73,9 @@ class Builder(object):
#: The list of MIME types of image formats supported by the builder.
#: Image files are searched in the order in which they appear here.
supported_image_types = [] # type: List[unicode]
#: The builder supports remote images or not.
supported_remote_images = False
#: The builder supports data URIs or not.
supported_data_uri_images = False
def __init__(self, app):

View File

@@ -54,9 +54,11 @@ from sphinx.environment.adapters.indexentries import IndexEntries
if False:
# For type annotation
from typing import Any, Dict, Iterable, Iterator, List, Type, Tuple, Union # NOQA
from sphinx.domains import Domain, Index # NOQA
from typing import Any, Dict, IO, Iterable, Iterator, List, Type, Tuple, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.domains import Domain, Index # NOQA
from sphinx.util.tags import Tags # NOQA
# Experimental HTML5 Writer
if is_html5_writer_available():
@@ -147,6 +149,56 @@ class Stylesheet(text_type):
return self
class BuildInfo(object):
"""buildinfo file manipulator.
HTMLBuilder and its family are storing their own envdata to ``.buildinfo``.
This class is a manipulator for the file.
"""
@classmethod
def load(cls, f):
# type: (IO) -> BuildInfo
try:
lines = f.readlines()
assert lines[0].rstrip() == '# Sphinx build info version 1'
assert lines[2].startswith('config: ')
assert lines[3].startswith('tags: ')
build_info = BuildInfo()
build_info.config_hash = lines[2].split()[1].strip()
build_info.tags_hash = lines[3].split()[1].strip()
return build_info
except Exception as exc:
raise ValueError('build info file is broken: %r' % exc)
def __init__(self, config=None, tags=None):
# type: (Config, Tags) -> None
self.config_hash = u''
self.tags_hash = u''
if config:
values = dict((c.name, c.value) for c in config.filter('html'))
self.config_hash = get_stable_hash(values)
if tags:
self.tags_hash = get_stable_hash(sorted(tags))
def __eq__(self, other): # type: ignore
# type: (BuildInfo) -> bool
return (self.config_hash == other.config_hash and
self.tags_hash == other.tags_hash)
def dump(self, f):
# type: (IO) -> None
f.write('# Sphinx build info version 1\n'
'# This file hashes the configuration used when building these files.'
' When it is not found, a full rebuild will be done.\n'
'config: %s\n'
'tags: %s\n' %
(self.config_hash, self.tags_hash))
class StandaloneHTMLBuilder(Builder):
"""
Builds standalone HTML docs.
@@ -191,9 +243,7 @@ class StandaloneHTMLBuilder(Builder):
def init(self):
# type: () -> None
# a hash of all config values that, if changed, cause a full rebuild
self.config_hash = '' # type: unicode
self.tags_hash = '' # type: unicode
self.build_info = BuildInfo(self.config, self.tags)
# basename of images directory
self.imagedir = '_images'
# section numbers for headings in the currently visited document
@@ -274,32 +324,19 @@ class StandaloneHTMLBuilder(Builder):
def get_outdated_docs(self):
# type: () -> Iterator[unicode]
cfgdict = dict((confval.name, confval.value) for confval in self.config.filter('html'))
self.config_hash = get_stable_hash(cfgdict)
self.tags_hash = get_stable_hash(sorted(self.tags))
old_config_hash = old_tags_hash = ''
try:
with open(path.join(self.outdir, '.buildinfo')) as fp:
version = fp.readline()
if version.rstrip() != '# Sphinx build info version 1':
raise ValueError
fp.readline() # skip commentary
cfg, old_config_hash = fp.readline().strip().split(': ')
if cfg != 'config':
raise ValueError
tag, old_tags_hash = fp.readline().strip().split(': ')
if tag != 'tags':
raise ValueError
except ValueError:
logger.warning('unsupported build info format in %r, building all',
path.join(self.outdir, '.buildinfo'))
except Exception:
buildinfo = BuildInfo.load(fp)
if self.build_info != buildinfo:
for docname in self.env.found_docs:
yield docname
return
except ValueError as exc:
logger.warning('Failed to read build info file: %r', exc)
except IOError:
# ignore errors on reading
pass
if old_config_hash != self.config_hash or \
old_tags_hash != self.tags_hash:
for docname in self.env.found_docs:
yield docname
return
if self.templates:
template_mtime = self.templates.newest_template_mtime()
@@ -777,14 +814,9 @@ class StandaloneHTMLBuilder(Builder):
def write_buildinfo(self):
# type: () -> None
# write build info file
try:
with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
fp.write('# Sphinx build info version 1\n'
'# This file hashes the configuration used when building'
' these files. When it is not found, a full rebuild will'
' be done.\nconfig: %s\ntags: %s\n' %
(self.config_hash, self.tags_hash))
self.build_info.dump(fp)
except IOError as exc:
logger.warning('Failed to write build info file: %r', exc)
@@ -1257,8 +1289,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
def init(self):
# type: () -> None
self.config_hash = ''
self.tags_hash = ''
self.build_info = BuildInfo(self.config, self.tags)
self.imagedir = '_images'
self.current_docname = None
self.theme = None # no theme necessary

View File

@@ -330,6 +330,7 @@ def setup(app):
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -6099,6 +6099,7 @@ def setup(app):
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -415,6 +415,7 @@ def setup(app):
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -912,6 +912,7 @@ def setup(app):
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -182,6 +182,7 @@ def setup(app):
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -980,6 +980,7 @@ def setup(app):
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -107,13 +107,9 @@ class BuildEnvironment(object):
# This can happen for example when the pickle is from a
# different version of Sphinx.
raise IOError(exc)
if env.version != ENV_VERSION:
raise IOError('build environment version not current')
if app:
env.app = app
env.config.values = app.config.values
if env.srcdir != app.srcdir:
raise IOError('source directory has changed')
return env
@classmethod
@@ -187,7 +183,7 @@ class BuildEnvironment(object):
self._warnfunc = None # type: Callable
# this is to invalidate old pickles
self.version = ENV_VERSION
self.version = app.registry.get_envversion(app)
# All "docnames" here are /-separated and relative and exclude
# the source suffix.
@@ -304,6 +300,19 @@ class BuildEnvironment(object):
"""Like :meth:`warn`, but with source information taken from *node*."""
self._warnfunc(msg, '%s:%s' % get_source_line(node), **kwargs)
def need_refresh(self, app):
# type: (Sphinx) -> Tuple[bool, unicode]
"""Check refresh environment is needed.
If needed, this method returns the reason for refresh.
"""
if self.version != app.registry.get_envversion(app):
return True, 'build environment version not current'
elif self.srcdir != app.srcdir:
return True, 'source directory has changed'
else:
return False, None
def clear_doc(self, docname):
# type: (unicode) -> None
"""Remove all traces of a source file in the inventory."""

View File

@@ -18,6 +18,7 @@
from __future__ import print_function
import argparse
import glob
import os
import sys
from os import path
@@ -194,7 +195,17 @@ def shall_skip(module, opts):
# skip it if there is nothing (or just \n or \r\n) in the file
if path.exists(module) and path.getsize(module) <= 2:
return True
skip = True
if os.path.basename(module) == '__init__.py':
pattern = path.join(path.dirname(module), '*.py')
# We only want to skip packages if they do not contain any
# .py files other than __init__.py.
other_modules = list(glob.glob(pattern))
other_modules.remove(module)
skip = not other_modules
if skip:
return True
# skip if it has a "private" name and this is selected
filename = path.basename(module)

View File

@@ -87,7 +87,7 @@ class _MockImporter(object):
self.names = names
self.mocked_modules = [] # type: List[str]
# enable hook by adding itself to meta_path
sys.meta_path = sys.meta_path + [self]
sys.meta_path.insert(0, self)
def disable(self):
# remove `self` from `sys.meta_path` to disable import hook

View File

@@ -347,7 +347,11 @@ def setup(app):
app.add_config_value('intersphinx_timeout', None, False)
app.connect('missing-reference', missing_reference)
app.connect('builder-inited', load_mappings)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
return {
'version': sphinx.__display_version__,
'env_version': 1,
'parallel_read_safe': True
}
def debug(argv):

View File

@@ -258,4 +258,8 @@ def setup(app):
app.connect('doctree-resolved', process_todo_nodes)
app.connect('env-purge-doc', purge_todos)
app.connect('env-merge-info', merge_info)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
return {
'version': sphinx.__display_version__,
'env_version': 1,
'parallel_read_safe': True
}

View File

@@ -241,4 +241,8 @@ def setup(app):
app.connect('missing-reference', missing_reference)
# app.add_config_value('viewcode_include_modules', [], 'env')
# app.add_config_value('viewcode_exclude_modules', [], 'env')
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
return {
'version': sphinx.__display_version__,
'env_version': 1,
'parallel_read_safe': True
}

View File

@@ -344,8 +344,6 @@ class SphinxComponentRegistry(object):
if metadata is None:
metadata = {}
if extname == 'rst2pdf.pdfbuilder':
metadata['parallel_read_safe'] = True
elif not isinstance(metadata, dict):
logger.warning(__('extension %r returned an unsupported object from '
'its setup() function; it should return None or a '
@@ -354,6 +352,14 @@ class SphinxComponentRegistry(object):
app.extensions[extname] = Extension(extname, mod, **metadata)
app._setting_up_extension.pop()
def get_envversion(self, app):
# type: (Sphinx) -> Dict[unicode, unicode]
from sphinx.environment import ENV_VERSION
envversion = {ext.name: ext.metadata['env_version'] for ext in app.extensions.values()
if ext.metadata.get('env_version')}
envversion['sphinx'] = ENV_VERSION
return envversion
def merge_source_suffix(app):
# type: (Sphinx) -> None

View File

@@ -212,10 +212,15 @@ class CitationReferences(SphinxTransform):
def apply(self):
# type: () -> None
# mark citation labels as not smartquoted
for citnode in self.document.traverse(nodes.citation):
citnode[0]['support_smartquotes'] = False
for citnode in self.document.traverse(nodes.citation_reference):
cittext = citnode.astext()
refnode = addnodes.pending_xref(cittext, refdomain='std', reftype='citation',
reftarget=cittext, refwarn=True,
support_smartquotes=False,
ids=citnode["ids"])
refnode.source = citnode.source or citnode.parent.source
refnode.line = citnode.line or citnode.parent.line

View File

@@ -152,34 +152,45 @@ def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact
# date_format mappings: ustrftime() to bable.dates.format_datetime()
date_format_mappings = {
'%a': 'EEE', # Weekday as locales abbreviated name.
'%A': 'EEEE', # Weekday as locales full name.
'%b': 'MMM', # Month as locales abbreviated name.
'%B': 'MMMM', # Month as locales full name.
'%c': 'medium', # Locales appropriate date and time representation.
'%d': 'dd', # Day of the month as a zero-padded decimal number.
'%H': 'HH', # Hour (24-hour clock) as a decimal number [00,23].
'%I': 'hh', # Hour (12-hour clock) as a decimal number [01,12].
'%j': 'DDD', # Day of the year as a zero-padded decimal number.
'%m': 'MM', # Month as a zero-padded decimal number.
'%M': 'mm', # Minute as a decimal number [00,59].
'%p': 'a', # Locales equivalent of either AM or PM.
'%S': 'ss', # Second as a decimal number.
'%U': 'WW', # Week number of the year (Sunday as the first day of the week)
# as a zero padded decimal number. All days in a new year preceding
# the first Sunday are considered to be in week 0.
'%w': 'e', # Weekday as a decimal number, where 0 is Sunday and 6 is Saturday.
'%W': 'WW', # Week number of the year (Monday as the first day of the week)
# as a decimal number. All days in a new year preceding the first
# Monday are considered to be in week 0.
'%x': 'medium', # Locales appropriate date representation.
'%X': 'medium', # Locales appropriate time representation.
'%y': 'YY', # Year without century as a zero-padded decimal number.
'%Y': 'YYYY', # Year with century as a decimal number.
'%Z': 'zzzz', # Time zone name (no characters if no time zone exists).
'%%': '%',
'%a': 'EEE', # Weekday as locales abbreviated name.
'%A': 'EEEE', # Weekday as locales full name.
'%b': 'MMM', # Month as locales abbreviated name.
'%B': 'MMMM', # Month as locales full name.
'%c': 'medium', # Locales appropriate date and time representation.
'%-d': 'd', # Day of the month as a decimal number.
'%d': 'dd', # Day of the month as a zero-padded decimal number.
'%-H': 'H', # Hour (24-hour clock) as a decimal number [0,23].
'%H': 'HH', # Hour (24-hour clock) as a zero-padded decimal number [00,23].
'%-I': 'h', # Hour (12-hour clock) as a decimal number [1,12].
'%I': 'hh', # Hour (12-hour clock) as a zero-padded decimal number [01,12].
'%-j': 'D', # Day of the year as a decimal number.
'%j': 'DDD', # Day of the year as a zero-padded decimal number.
'%-m': 'M', # Month as a decimal number.
'%m': 'MM', # Month as a zero-padded decimal number.
'%-M': 'm', # Minute as a decimal number [0,59].
'%M': 'mm', # Minute as a zero-padded decimal number [00,59].
'%p': 'a', # Locales equivalent of either AM or PM.
'%-S': 's', # Second as a decimal number.
'%S': 'ss', # Second as a zero-padded decimal number.
'%U': 'WW', # Week number of the year (Sunday as the first day of the week)
# as a zero padded decimal number. All days in a new year preceding
# the first Sunday are considered to be in week 0.
'%w': 'e', # Weekday as a decimal number, where 0 is Sunday and 6 is Saturday.
'%-W': 'W', # Week number of the year (Monday as the first day of the week)
# as a decimal number. All days in a new year preceding the first
# Monday are considered to be in week 0.
'%W': 'WW', # Week number of the year (Monday as the first day of the week)
# as a zero-padded decimal number.
'%x': 'medium', # Locales appropriate date representation.
'%X': 'medium', # Locales appropriate time representation.
'%y': 'YY', # Year without century as a zero-padded decimal number.
'%Y': 'YYYY', # Year with century as a decimal number.
'%Z': 'zzzz', # Time zone name (no characters if no time zone exists).
'%%': '%',
}
date_format_re = re.compile('(%s)' % '|'.join(date_format_mappings))
def babel_format_date(date, format, locale, formatter=babel.dates.format_date):
# type: (datetime, unicode, unicode, Callable) -> unicode
@@ -214,7 +225,7 @@ def format_date(format, date=None, language=None):
date = datetime.now()
result = []
tokens = re.split('(%.)', format)
tokens = date_format_re.split(format)
for token in tokens:
if token in date_format_mappings:
babel_format = date_format_mappings.get(token, '')

View File

@@ -431,6 +431,55 @@ class Signature(object):
Displaying complex types from ``typing`` relies on its private API.
"""
if sys.version_info >= (3, 7): # py37+
return self.format_annotation_new(annotation)
else:
return self.format_annotation_old(annotation)
def format_annotation_new(self, annotation):
# type: (Any) -> str
"""format_annotation() for py37+"""
module = getattr(annotation, '__module__', None)
if isinstance(annotation, string_types):
return annotation # type: ignore
elif isinstance(annotation, typing.TypeVar): # type: ignore
return annotation.__name__
elif not annotation:
return repr(annotation)
elif module == 'builtins':
return annotation.__qualname__
elif annotation is Ellipsis:
return '...'
if module == 'typing':
if getattr(annotation, '_name', None):
qualname = annotation._name
elif getattr(annotation, '__qualname__', None):
qualname = annotation.__qualname__
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':
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)
else:
args = ', '.join(self.format_annotation(a) for a in annotation.__args__)
return '%s[%s]' % (qualname, args)
return qualname
def format_annotation_old(self, annotation):
# type: (Any) -> str
"""format_annotation() for py36 or below"""
if isinstance(annotation, string_types):
return annotation # type: ignore
if isinstance(annotation, typing.TypeVar): # type: ignore

View File

@@ -354,6 +354,8 @@ def is_smartquotable(node):
"""Check the node is smart-quotable or not."""
if isinstance(node.parent, NON_SMARTQUOTABLE_PARENT_NODES):
return False
elif node.parent.get('support_smartquotes', None) is False:
return False
elif getattr(node, 'support_smartquotes', None) is False:
return False
else:

View File

@@ -23,13 +23,15 @@ if False:
# For type annotation
from typing import Generator # NOQA
symbols_re = re.compile(r'([!-/:-@\[-`{-~])')
symbols_re = re.compile(r'([!--/:-@\[-`{-~])') # symbols without dot(0x2e)
logger = logging.getLogger(__name__)
def escape(text):
# type: (unicode) -> unicode
return symbols_re.sub(r'\\\1', text) # type: ignore
text = symbols_re.sub(r'\\\1', text) # type: ignore
text = re.sub(r'^\.', r'\.', text) # escape a dot at top
return text
@contextmanager

View File

@@ -0,0 +1 @@
"Module f"

View File

@@ -67,6 +67,7 @@ def test_pep_0420_enabled(make_app, apidoc):
outdir = apidoc.outdir
assert (outdir / 'conf.py').isfile()
assert (outdir / 'a.b.c.rst').isfile()
assert (outdir / 'a.b.e.rst').isfile()
assert (outdir / 'a.b.x.rst').isfile()
with open(outdir / 'a.b.c.rst') as f:
@@ -74,6 +75,10 @@ def test_pep_0420_enabled(make_app, apidoc):
assert "automodule:: a.b.c.d\n" in rst
assert "automodule:: a.b.c\n" in rst
with open(outdir / 'a.b.e.rst') as f:
rst = f.read()
assert "automodule:: a.b.e.f\n" in rst
with open(outdir / 'a.b.x.rst') as f:
rst = f.read()
assert "automodule:: a.b.x.y\n" in rst
@@ -86,12 +91,67 @@ def test_pep_0420_enabled(make_app, apidoc):
builddir = outdir / '_build' / 'text'
assert (builddir / 'a.b.c.txt').isfile()
assert (builddir / 'a.b.e.txt').isfile()
assert (builddir / 'a.b.x.txt').isfile()
with open(builddir / 'a.b.c.txt') as f:
txt = f.read()
assert "a.b.c package\n" in txt
with open(builddir / 'a.b.e.txt') as f:
txt = f.read()
assert "a.b.e.f module\n" in txt
with open(builddir / 'a.b.x.txt') as f:
txt = f.read()
assert "a.b.x namespace\n" in txt
@pytest.mark.apidoc(
coderoot='test-apidoc-pep420/a',
options=["--implicit-namespaces", "--separate"],
)
def test_pep_0420_enabled_separate(make_app, apidoc):
outdir = apidoc.outdir
assert (outdir / 'conf.py').isfile()
assert (outdir / 'a.b.c.rst').isfile()
assert (outdir / 'a.b.e.rst').isfile()
assert (outdir / 'a.b.e.f.rst').isfile()
assert (outdir / 'a.b.x.rst').isfile()
assert (outdir / 'a.b.x.y.rst').isfile()
with open(outdir / 'a.b.c.rst') as f:
rst = f.read()
assert ".. toctree::\n\n a.b.c.d\n" in rst
with open(outdir / 'a.b.e.rst') as f:
rst = f.read()
assert ".. toctree::\n\n a.b.e.f\n" in rst
with open(outdir / 'a.b.x.rst') as f:
rst = f.read()
assert ".. toctree::\n\n a.b.x.y\n" in rst
app = make_app('text', srcdir=outdir)
app.build()
print(app._status.getvalue())
print(app._warning.getvalue())
builddir = outdir / '_build' / 'text'
assert (builddir / 'a.b.c.txt').isfile()
assert (builddir / 'a.b.e.txt').isfile()
assert (builddir / 'a.b.e.f.txt').isfile()
assert (builddir / 'a.b.x.txt').isfile()
assert (builddir / 'a.b.x.y.txt').isfile()
with open(builddir / 'a.b.c.txt') as f:
txt = f.read()
assert "a.b.c package\n" in txt
with open(builddir / 'a.b.e.f.txt') as f:
txt = f.read()
assert "a.b.e.f module\n" in txt
with open(builddir / 'a.b.x.txt') as f:
txt = f.read()
assert "a.b.x namespace\n" in txt

View File

@@ -176,6 +176,8 @@ def test_format_date():
datet = datetime.datetime(2016, 2, 7, 5, 11, 17, 0)
assert i18n.format_date(format, date=datet) == 'February 07, 2016, 05:11:17 05 AM'
format = '%B %-d, %Y, %-H:%-M:%-S %-I %p'
assert i18n.format_date(format, date=datet) == 'February 7, 2016, 5:11:17 5 AM'
format = '%x'
assert i18n.format_date(format, date=datet) == 'Feb 7, 2016'
format = '%X'

View File

@@ -215,12 +215,7 @@ def test_Signature_annotations():
# TypeVars and generic types with TypeVars
sig = inspect.Signature(f2).format_args()
if sys.version_info < (3, 7):
sig == ('(x: typing.List[T], y: typing.List[T_co], z: T) -> '
'typing.List[T_contra]')
else:
sig == ('(x: typing.List[~T], y: typing.List[+T_co], z: T) -> '
'typing.List[-T_contra]')
assert sig == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]'
# Union types
sig = inspect.Signature(f3).format_args()

View File

@@ -14,3 +14,5 @@ from sphinx.util.rst import escape
def test_escape():
assert escape(':ref:`id`') == r'\:ref\:\`id\`'
assert escape('footnote [#]_') == r'footnote \[\#\]\_'
assert escape('sphinx.application') == r'sphinx.application'
assert escape('.. toctree::') == r'\.. toctree\:\:'