mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'master' into integrate_source_suffix_and_source_parsers
This commit is contained in:
11
CHANGES
11
CHANGES
@@ -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
|
||||
--------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
-----------------------------
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
13
setup.py
13
setup.py
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -330,6 +330,7 @@ def setup(app):
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'env_version': 1,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -6099,6 +6099,7 @@ def setup(app):
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'env_version': 1,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -415,6 +415,7 @@ def setup(app):
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'env_version': 1,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -912,6 +912,7 @@ def setup(app):
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'env_version': 1,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ def setup(app):
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'env_version': 1,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -980,6 +980,7 @@ def setup(app):
|
||||
|
||||
return {
|
||||
'version': 'builtin',
|
||||
'env_version': 1,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 locale’s abbreviated name.
|
||||
'%A': 'EEEE', # Weekday as locale’s full name.
|
||||
'%b': 'MMM', # Month as locale’s abbreviated name.
|
||||
'%B': 'MMMM', # Month as locale’s full name.
|
||||
'%c': 'medium', # Locale’s 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', # Locale’s 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', # Locale’s appropriate date representation.
|
||||
'%X': 'medium', # Locale’s 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 locale’s abbreviated name.
|
||||
'%A': 'EEEE', # Weekday as locale’s full name.
|
||||
'%b': 'MMM', # Month as locale’s abbreviated name.
|
||||
'%B': 'MMMM', # Month as locale’s full name.
|
||||
'%c': 'medium', # Locale’s 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', # Locale’s 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', # Locale’s appropriate date representation.
|
||||
'%X': 'medium', # Locale’s 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, '')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
0
tests/roots/test-apidoc-pep420/a/b/e/__init__.py
Normal file
0
tests/roots/test-apidoc-pep420/a/b/e/__init__.py
Normal file
1
tests/roots/test-apidoc-pep420/a/b/e/f.py
Normal file
1
tests/roots/test-apidoc-pep420/a/b/e/f.py
Normal file
@@ -0,0 +1 @@
|
||||
"Module f"
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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\:\:'
|
||||
|
||||
Reference in New Issue
Block a user