Merge branch 'master' into drop_websupport

This commit is contained in:
Takeshi KOMIYA 2017-04-23 19:18:01 +09:00
commit c50321d3ca
51 changed files with 1187 additions and 309 deletions

View File

@ -43,6 +43,7 @@ addons:
- texlive-xetex
- lmodern
- latex-xcolor
- imagemagick
install:
- pip install -U pip setuptools
- pip install docutils==$DOCUTILS
@ -51,4 +52,4 @@ install:
script:
- flake8
- if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then make style-check type-check test-async; fi
- if [[ $TRAVIS_PYTHON_VERSION != '3.6' ]]; then make test; fi
- if [[ $TRAVIS_PYTHON_VERSION != '3.6' ]]; then SKIP_LATEX_BUILD=1 make test; fi

View File

@ -12,6 +12,7 @@ Other co-maintainers:
* Robert Lehmann <@lehmannro>
* Roland Meister <@rolmei>
* Takeshi Komiya <@tk0miya>
* Jean-François Burnol <@jfbu>
* Yoshiki Shibukawa <@shibu_jp>
Other contributors, listed alphabetically, are:
@ -21,7 +22,6 @@ Other contributors, listed alphabetically, are:
* Jakob Lykke Andersen -- Rewritten C++ domain
* Henrique Bastos -- SVG support for graphviz extension
* Daniel Bültmann -- todo extension
* Jean-François Burnol -- LaTeX improvements
* Marco Buttu -- doctest extension (pyversion option)
* Etienne Desautels -- apidoc module
* Michael Droettboom -- inheritance_diagram extension

View File

@ -45,6 +45,7 @@ Incompatible changes
* ``Builder.env`` is not filled at instantiation
* #3594: LaTeX: single raw directive has been considered as block level element
* #3639: If ``html_experimental_html5_writer`` is available, epub builder use it by default.
* ``Sphinx.add_source_parser()`` raises an error if duplicated
Features removed
----------------
@ -117,6 +118,8 @@ Features added
* #3641: Epub theme supports HTML structures that are generated by HTML5 writer.
* #3644 autodoc uses inspect instead of checking types. Thanks to
Jeroen Demeyer.
* Add a new extension; ``sphinx.ext.imgconverter``. It converts images in the
document to appropriate format for builders
Bugs fixed
----------
@ -131,6 +134,8 @@ Bugs fixed
* C++, properly look up ``any`` references.
* #3624: sphinx.ext.intersphinx couldn't load inventories compressed with gzip
* #3551: PDF information dictionary is lacking author and title data
* #3351: intersphinx does not refers context like ``py:module``, ``py:class``
and so on.
* Fail to load template file if the parent template is archived
Deprecated
@ -146,6 +151,8 @@ Deprecated
instead (as Sphinx does since 1.5.)
* ``Sphinx.status_iterator()`` and ``Sphinx.old_status_iterator()`` is now
deprecated. Please use ``sphinx.util:status_iterator()`` intead.
* ``Sphinx._directive_helper()`` is deprecated. Please use
``sphinx.util.docutils.directive_helper()`` instead.
* ``BuildEnvironment.set_warnfunc()`` is now deprecated
* Following methods of ``BuildEnvironment`` is now deprecated.
@ -165,6 +172,8 @@ Deprecated
* #3254: ``sphinx.websupport`` is now separated into independent package;
``sphinxcontrib-websupport``. ``sphinx.websupport`` will be removed in
Sphinx-2.0.
* #3628: ``sphinx_themes`` entry_point is deprecated. Please use
``sphinx.html_themes`` instead.
Release 1.5.6 (in development)
==============================

View File

@ -15,6 +15,7 @@ These extensions are built in and can be activated by respective entries in the
githubpages
graphviz
ifconfig
imgconverter
inheritance
intersphinx
linkcode

30
doc/ext/imgconverter.rst Normal file
View File

@ -0,0 +1,30 @@
.. highlight:: rest
:mod:`sphinx.ext.imgconverter` -- Convert images to appropriate format for builders
===================================================================================
.. module:: sphinx.ext.imgconverter
:synopsis: Convert images to appropriate format for builders
.. versionadded:: 1.6
This extension converts images in your document to appropriate format for builders.
For example, it allows you to use SVG images with LaTeX builder.
As a result, you don't mind what image format the builder supports.
Internally, this extension uses Imagemagick_ to convert images.
.. _Imagemagick: https://www.imagemagick.org/script/index.php
Configuration
-------------
.. confval:: image_converter
A path to :command:`convert` command. By default, the imgconverter uses
the command from search paths.
.. confval:: image_converter_args
Additional command-line arguments to give to :command:`convert`, as a list.
The default is an empty list ``[]``.

View File

@ -370,6 +370,13 @@ package.
.. versionadded:: 1.4
.. method:: Sphinx.add_html_theme(name, theme_path)
Register a HTML Theme. The *name* is a name of theme, and *path* is a
full path to the theme (refs: :ref:`distribute-your-theme`).
.. versionadded:: 1.6
.. method:: Sphinx.add_env_collector(collector)
Register an environment collector class (refs: :ref:`collector-api`)

View File

@ -46,34 +46,14 @@ file :file:`blue.zip`, you can put it right in the directory containing
html_theme = "blue"
html_theme_path = ["."]
The third form provides your theme path dynamically to Sphinx if the
``setuptools`` package is installed. You can provide an entry point section
called ``sphinx_themes`` in your setup.py file and write a ``get_path`` function
that has to return the directory with themes in it::
The third form is a python package. If a theme you want to use is distributed
as a python package, you can use it after installing::
# 'setup.py'
# installing theme package
$ pip install sphinxjp.themes.dotted
setup(
...
entry_points = {
'sphinx_themes': [
'path = your_package:get_path',
]
},
...
)
# 'your_package.py'
from os import path
package_dir = path.abspath(path.dirname(__file__))
template_path = path.join(package_dir, 'themes')
def get_path():
return template_path
.. versionadded:: 1.2
'sphinx_themes' entry_points feature.
# use it in your conf.py
html_theme = "dotted"
.. _builtin-themes:
@ -310,6 +290,48 @@ Python :mod:`ConfigParser` module) and has the following structure:
and are accessible from all templates as ``theme_<name>``.
.. _distribute-your-theme:
Distribute your theme as a python package
-----------------------------------------
As a way to distribute your theme, you can use python package. Python package
brings to users easy setting up ways.
To distribute your theme as a python package, please define an entry point
called ``sphinx.html_themes`` in your setup.py file, and write a ``setup()``
function to register your themes using ``add_html_theme()`` API in it::
# 'setup.py'
setup(
...
entry_points = {
'sphinx.html_themes': [
'name_of_theme = your_package',
]
},
...
)
# 'your_package.py'
from os import path
def setup(app):
app.add_html_theme('name_of_theme', path.abspath(path.dirname(__file__)))
If your theme package contains two or more themes, please call ``add_html_theme()``
twice or more.
.. versionadded:: 1.2
'sphinx_themes' entry_points feature.
.. deprecated:: 1.6
``sphinx_themes`` entry_points has been deprecated.
.. versionadded:: 1.6
``sphinx.html_themes`` entry_points feature.
Templating
~~~~~~~~~~

View File

@ -14,7 +14,6 @@ from __future__ import print_function
import os
import sys
import types
import warnings
import posixpath
from os import path
@ -24,24 +23,19 @@ from six import iteritems
from six.moves import cStringIO
from docutils import nodes
from docutils.parsers.rst import convert_directive_function, \
directives, roles
from pkg_resources import iter_entry_points
from docutils.parsers.rst import directives, roles
import sphinx
from sphinx import package_dir, locale
from sphinx.config import Config
from sphinx.errors import SphinxError, ExtensionError, VersionRequirementError, \
ConfigError
from sphinx.domains import ObjType
from sphinx.domains.std import GenericObject, Target, StandardDomain
from sphinx.errors import ConfigError, ExtensionError, VersionRequirementError
from sphinx.deprecation import RemovedInSphinx17Warning, RemovedInSphinx20Warning
from sphinx.environment import BuildEnvironment
from sphinx.events import EventManager
from sphinx.extension import load_extension, verify_required_extensions
from sphinx.extension import verify_required_extensions
from sphinx.io import SphinxStandaloneReader
from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.registry import SphinxComponentRegistry
from sphinx.util import pycompat # noqa: F401
from sphinx.util import import_object
from sphinx.util import logging
@ -49,7 +43,7 @@ from sphinx.util import status_iterator, old_status_iterator, display_chunk
from sphinx.util.tags import Tags
from sphinx.util.osutil import ENOENT
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.util.docutils import is_html5_writer_available
from sphinx.util.docutils import is_html5_writer_available, directive_helper
from sphinx.util.i18n import find_catalog_source_files
if False:
@ -121,12 +115,10 @@ class Sphinx(object):
# type: (unicode, unicode, unicode, unicode, unicode, Dict, IO, IO, bool, bool, List[unicode], int, int) -> None # NOQA
self.verbosity = verbosity
self.extensions = {} # type: Dict[unicode, Extension]
self._additional_source_parsers = {} # type: Dict[unicode, Parser]
self._setting_up_extension = ['?'] # type: List[unicode]
self.domains = {} # type: Dict[unicode, Type[Domain]]
self.builderclasses = {} # type: Dict[unicode, Type[Builder]]
self.builder = None # type: Builder
self.env = None # type: BuildEnvironment
self.registry = SphinxComponentRegistry()
self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, Callable]] # NOQA
self.post_transforms = [] # type: List[Transform]
self.html_themes = {} # type: Dict[unicode, unicode]
@ -154,7 +146,6 @@ class Sphinx(object):
logging.setup(self, self._status, self._warning)
self.events = EventManager()
self._translators = {} # type: Dict[unicode, nodes.GenericNodeVisitor]
# keep last few messages for traceback
# This will be filled by sphinx.util.logging.LastMessagesWriter
@ -226,9 +217,9 @@ class Sphinx(object):
verify_required_extensions(self, self.config.needs_extensions)
# check primary_domain if requested
if self.config.primary_domain and self.config.primary_domain not in self.domains:
logger.warning(_('primary_domain %r not found, ignored.'),
self.config.primary_domain)
primary_domain = self.config.primary_domain
if primary_domain and not self.registry.has_domain(primary_domain):
logger.warning(_('primary_domain %r not found, ignored.'), primary_domain)
# create the builder
self.builder = self.create_builder(buildername)
@ -271,28 +262,28 @@ class Sphinx(object):
def _init_source_parsers(self):
# type: () -> None
for suffix, parser in iteritems(self._additional_source_parsers):
for suffix, parser in iteritems(self.config.source_parsers):
self.add_source_parser(suffix, parser)
for suffix, parser in iteritems(self.registry.get_source_parsers()):
if suffix not in self.config.source_suffix:
self.config.source_suffix.append(suffix)
if suffix not in self.config.source_parsers:
self.config.source_parsers[suffix] = parser
def _init_env(self, freshenv):
# type: (bool) -> None
if freshenv:
self.env = BuildEnvironment(self)
self.env.find_files(self.config, self.builder)
for domain in self.domains.keys():
self.env.domains[domain] = self.domains[domain](self.env)
for domain in self.registry.create_domains(self.env):
self.env.domains[domain.name] = domain
else:
try:
logger.info(bold(_('loading pickled environment... ')), nonl=True)
filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
self.env = BuildEnvironment.frompickle(filename, self)
self.env.domains = {}
for domain in self.domains.keys():
for domain in self.registry.create_domains(self.env):
# this can raise if the data version doesn't fit
self.env.domains[domain] = self.domains[domain](self.env)
self.env.domains[domain.name] = domain
logger.info(_('done'))
except Exception as err:
if isinstance(err, IOError) and err.errno == ENOENT:
@ -301,28 +292,17 @@ class Sphinx(object):
logger.info(_('failed: %s'), err)
self._init_env(freshenv=True)
def preload_builder(self, buildername):
def preload_builder(self, name):
# type: (unicode) -> None
if buildername is None:
return
self.registry.preload_builder(self, name)
if buildername not in self.builderclasses:
entry_points = iter_entry_points('sphinx.builders', buildername)
try:
entry_point = next(entry_points)
except StopIteration:
raise SphinxError(_('Builder name %s not registered or available'
' through entry point') % buildername)
load_extension(self, entry_point.module_name)
def create_builder(self, buildername):
def create_builder(self, name):
# type: (unicode) -> Builder
if buildername is None:
buildername = 'html'
if buildername not in self.builderclasses:
raise SphinxError(_('Builder name %s not registered') % buildername)
if name is None:
logger.info(_('No builder selected, using default: html'))
name = 'html'
return self.builderclasses[buildername](self)
return self.registry.create_builder(self, name)
def _init_builder(self):
# type: () -> None
@ -472,7 +452,7 @@ class Sphinx(object):
# type: (unicode) -> None
"""Import and setup a Sphinx extension module. No-op if called twice."""
logger.debug('[app] setting up extension: %r', extname)
load_extension(self, extname)
self.registry.load_extension(self, extname)
def require_sphinx(self, version):
# type: (unicode) -> None
@ -516,13 +496,7 @@ class Sphinx(object):
def add_builder(self, builder):
# type: (Type[Builder]) -> None
logger.debug('[app] adding builder: %r', builder)
if not hasattr(builder, 'name'):
raise ExtensionError(_('Builder class %s has no "name" attribute')
% builder)
if builder.name in self.builderclasses:
raise ExtensionError(_('Builder %r already exists (in module %s)') %
(builder.name, self.builderclasses[builder.name].__module__))
self.builderclasses[builder.name] = builder
self.registry.add_builder(builder)
def add_config_value(self, name, default, rebuild, types=()):
# type: (unicode, Any, Union[bool, unicode], Any) -> None
@ -540,9 +514,9 @@ class Sphinx(object):
self.events.add(name)
def set_translator(self, name, translator_class):
# type: (unicode, Any) -> None
# type: (unicode, Type[nodes.NodeVisitor]) -> None
logger.info(bold(_('A Translator for the %s builder is changed.') % name))
self._translators[name] = translator_class
self.registry.add_translator(name, translator_class)
def add_node(self, node, **kwds):
# type: (nodes.Node, Any) -> None
@ -560,7 +534,7 @@ class Sphinx(object):
except ValueError:
raise ExtensionError(_('Value for key %r must be a '
'(visit, depart) function tuple') % key)
translator = self._translators.get(key)
translator = self.registry.translators.get(key)
translators = []
if translator is not None:
translators.append(translator)
@ -593,21 +567,15 @@ class Sphinx(object):
self.enumerable_nodes[node] = (figtype, title_getter)
self.add_node(node, **kwds)
def _directive_helper(self, obj, content=None, arguments=None, **options):
# type: (Any, unicode, Any, Any) -> Any
if isinstance(obj, (types.FunctionType, types.MethodType)):
obj.content = content # type: ignore
obj.arguments = arguments or (0, 0, False) # type: ignore
obj.options = options # type: ignore
return convert_directive_function(obj)
else:
if content or arguments or options:
raise ExtensionError(_('when adding directive classes, no '
'additional arguments may be given'))
return obj
def _directive_helper(self, obj, has_content=None, argument_spec=None, **option_spec):
# type: (Any, bool, Tuple[int, int, bool], Any) -> Any
warnings.warn('_directive_helper() is now deprecated. '
'Please use sphinx.util.docutils.directive_helper() instead.',
RemovedInSphinx17Warning)
return directive_helper(obj, has_content, argument_spec, **option_spec)
def add_directive(self, name, obj, content=None, arguments=None, **options):
# type: (unicode, Any, unicode, Any, Any) -> None
# type: (unicode, Any, bool, Tuple[int, int, bool], Any) -> None
logger.debug('[app] adding directive: %r',
(name, obj, content, arguments, options))
if name in directives._directives:
@ -615,8 +583,8 @@ class Sphinx(object):
'already registered, it will be overridden'),
self._setting_up_extension[-1], name,
type='app', subtype='add_directive')
directives.register_directive(
name, self._directive_helper(obj, content, arguments, **options))
directive = directive_helper(obj, content, arguments, **options)
directives.register_directive(name, directive)
def add_role(self, name, role):
# type: (unicode, Any) -> None
@ -644,43 +612,30 @@ class Sphinx(object):
def add_domain(self, domain):
# type: (Type[Domain]) -> None
logger.debug('[app] adding domain: %r', domain)
if domain.name in self.domains:
raise ExtensionError(_('domain %s already registered') % domain.name)
self.domains[domain.name] = domain
self.registry.add_domain(domain)
def override_domain(self, domain):
# type: (Type[Domain]) -> None
logger.debug('[app] overriding domain: %r', domain)
if domain.name not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain.name)
if not issubclass(domain, self.domains[domain.name]):
raise ExtensionError(_('new domain not a subclass of registered %s '
'domain') % domain.name)
self.domains[domain.name] = domain
self.registry.override_domain(domain)
def add_directive_to_domain(self, domain, name, obj,
content=None, arguments=None, **options):
# type: (unicode, unicode, Any, unicode, Any, Any) -> None
has_content=None, argument_spec=None, **option_spec):
# type: (unicode, unicode, Any, bool, Any, Any) -> None
logger.debug('[app] adding directive to domain: %r',
(domain, name, obj, content, arguments, options))
if domain not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain)
self.domains[domain].directives[name] = \
self._directive_helper(obj, content, arguments, **options)
(domain, name, obj, has_content, argument_spec, option_spec))
self.registry.add_directive_to_domain(domain, name, obj,
has_content, argument_spec, **option_spec)
def add_role_to_domain(self, domain, name, role):
# type: (unicode, unicode, Any) -> None
logger.debug('[app] adding role to domain: %r', (domain, name, role))
if domain not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain)
self.domains[domain].roles[name] = role
self.registry.add_role_to_domain(domain, name, role)
def add_index_to_domain(self, domain, index):
# type: (unicode, Type[Index]) -> None
logger.debug('[app] adding index to domain: %r', (domain, index))
if domain not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain)
self.domains[domain].indices.append(index)
self.registry.add_index_to_domain(domain, index)
def add_object_type(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
@ -689,19 +644,18 @@ class Sphinx(object):
logger.debug('[app] adding object type: %r',
(directivename, rolename, indextemplate, parse_node,
ref_nodeclass, objname, doc_field_types))
StandardDomain.object_types[directivename] = \
ObjType(objname or directivename, rolename)
# create a subclass of GenericObject as the new directive
new_directive = type(directivename, (GenericObject, object), # type: ignore
{'indextemplate': indextemplate,
'parse_node': staticmethod(parse_node), # type: ignore
'doc_field_types': doc_field_types})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
self.registry.add_object_type(directivename, rolename, indextemplate, parse_node,
ref_nodeclass, objname, doc_field_types)
# backwards compatible alias
add_description_unit = add_object_type
def add_description_unit(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
doc_field_types=[]):
# type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List) -> None
warnings.warn('app.add_description_unit() is now deprecated. '
'Use app.add_object_type() instead.',
RemovedInSphinx20Warning)
self.add_object_type(directivename, rolename, indextemplate, parse_node,
ref_nodeclass, objname, doc_field_types)
def add_crossref_type(self, directivename, rolename, indextemplate='',
ref_nodeclass=None, objname=''):
@ -709,14 +663,8 @@ class Sphinx(object):
logger.debug('[app] adding crossref type: %r',
(directivename, rolename, indextemplate, ref_nodeclass,
objname))
StandardDomain.object_types[directivename] = \
ObjType(objname or directivename, rolename)
# create a subclass of Target as the new directive
new_directive = type(directivename, (Target, object), # type: ignore
{'indextemplate': indextemplate})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
self.registry.add_crossref_type(directivename, rolename,
indextemplate, ref_nodeclass, objname)
def add_transform(self, transform):
# type: (Type[Transform]) -> None
@ -788,18 +736,18 @@ class Sphinx(object):
def add_source_parser(self, suffix, parser):
# type: (unicode, Parser) -> None
logger.debug('[app] adding search source_parser: %r, %r', suffix, parser)
if suffix in self._additional_source_parsers:
logger.warning(_('while setting up extension %s: source_parser for %r is '
'already registered, it will be overridden'),
self._setting_up_extension[-1], suffix,
type='app', subtype='add_source_parser')
self._additional_source_parsers[suffix] = parser
self.registry.add_source_parser(suffix, parser)
def add_env_collector(self, collector):
# type: (Type[EnvironmentCollector]) -> None
logger.debug('[app] adding environment collector: %r', collector)
collector().enable(self)
def add_html_theme(self, name, theme_path):
# type: (unicode, unicode) -> None
logger.debug('[app] adding HTML theme: %r, %r', name, theme_path)
self.html_themes[name] = theme_path
class TemplateBridge(object):
"""

View File

@ -11,6 +11,7 @@
import os
from os import path
import warnings
try:
import multiprocessing
@ -20,6 +21,7 @@ except ImportError:
from six import itervalues
from docutils import nodes
from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.util import i18n, path_stabilize, logging, status_iterator
from sphinx.util.osutil import SEP, relative_uri
from sphinx.util.i18n import find_catalog
@ -53,6 +55,9 @@ class Builder(object):
name = '' # type: unicode
#: The builder's output format, or '' if no document output is produced.
format = '' # type: unicode
# default translator class for the builder. This will be overrided by
# ``app.set_translator()``.
default_translator_class = None # type: nodes.NodeVisitor
# doctree versioning method
versioning_method = 'none' # type: unicode
versioning_compare = False
@ -101,9 +106,6 @@ class Builder(object):
self.parallel_ok = False
self.finish_tasks = None # type: Any
# load default translator class
self.translator_class = app._translators.get(self.name)
def set_environment(self, env):
# type: (BuildEnvironment) -> None
"""Store BuildEnvironment object."""
@ -111,6 +113,38 @@ class Builder(object):
self.env.set_versioning_method(self.versioning_method,
self.versioning_compare)
def get_translator_class(self, *args):
# type: (Any) -> nodes.NodeVisitor
"""Return a class of translator."""
return self.app.registry.get_translator_class(self)
def create_translator(self, *args):
# type: (Any) -> nodes.NodeVisitor
"""Return an instance of translator.
This method returns an instance of ``default_translator_class`` by default.
Users can replace the translator class with ``app.set_translator()`` API.
"""
translator_class = self.app.registry.get_translator_class(self)
assert translator_class, "translator not found for %s" % self.__class__.__name__
return translator_class(*args)
@property
def translator_class(self):
# type: () -> Callable[[Any], nodes.NodeVisitor]
"""Return a class of translator.
.. deprecated:: 1.6
"""
translator_class = self.app.registry.get_translator_class(self)
if translator_class is None and self.default_translator_class is None:
warnings.warn('builder.translator_class() is now deprecated. '
'Please use builder.create_translator() and '
'builder.default_translator_class instead.',
RemovedInSphinx20Warning)
return None
return self.create_translator
# helper methods
def init(self):
# type: () -> None

View File

@ -158,7 +158,6 @@ class StandaloneHTMLBuilder(Builder):
self.init_templates()
self.init_highlighter()
self.init_translator_class()
if self.config.html_file_suffix is not None:
self.out_suffix = self.config.html_file_suffix
@ -218,23 +217,18 @@ class StandaloneHTMLBuilder(Builder):
self.highlighter = PygmentsBridge('html', style,
self.config.trim_doctest_flags)
def init_translator_class(self):
# type: () -> None
if self.translator_class is None:
use_html5_writer = self.config.html_experimental_html5_writer
if use_html5_writer is None:
use_html5_writer = self.default_html5_translator and html5_ready
if use_html5_writer and html5_ready:
if self.config.html_use_smartypants:
self.translator_class = SmartyPantsHTML5Translator
else:
self.translator_class = HTML5Translator
@property
def default_translator_class(self):
if self.config.html_experimental_html5_writer and html5_ready:
if self.config.html_use_smartypants:
return SmartyPantsHTML5Translator
else:
if self.config.html_use_smartypants:
self.translator_class = SmartyPantsHTMLTranslator
else:
self.translator_class = HTMLTranslator
return HTML5Translator
else:
if self.config.html_use_smartypants:
return SmartyPantsHTMLTranslator
else:
return HTMLTranslator
def get_outdated_docs(self):
# type: () -> Iterator[unicode]
@ -1200,7 +1194,6 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
self.current_docname = None
self.theme = None # no theme necessary
self.templates = None # no template bridge necessary
self.init_translator_class()
self.init_templates()
self.init_highlighter()
self.use_index = self.get_builder_config('use_index', 'html')

View File

@ -31,7 +31,7 @@ from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.fileutil import copy_asset_file
from sphinx.util.osutil import SEP, make_filename
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.writers.latex import LaTeXWriter
from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator
if False:
# For type annotation
@ -51,6 +51,7 @@ class LaTeXBuilder(Builder):
format = 'latex'
supported_image_types = ['application/pdf', 'image/png', 'image/jpeg']
supported_remote_images = False
default_translator_class = LaTeXTranslator
def init(self):
# type: () -> None

View File

@ -23,7 +23,7 @@ from sphinx.util import logging
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import make_filename
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.writers.manpage import ManualPageWriter
from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator
if False:
# For type annotation
@ -40,6 +40,7 @@ class ManualPageBuilder(Builder):
"""
name = 'man'
format = 'man'
default_translator_class = ManualPageTranslator
supported_image_types = [] # type: List[unicode]
def init(self):

View File

@ -27,7 +27,7 @@ from sphinx.util.fileutil import copy_asset_file
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import SEP, make_filename
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.writers.texinfo import TexinfoWriter
from sphinx.writers.texinfo import TexinfoWriter, TexinfoTranslator
if False:
# For type annotation
@ -99,6 +99,7 @@ class TexinfoBuilder(Builder):
format = 'texinfo'
supported_image_types = ['image/png', 'image/jpeg',
'image/gif']
default_translator_class = TexinfoTranslator
def init(self):
# type: () -> None

View File

@ -17,7 +17,7 @@ from docutils.io import StringOutput
from sphinx.builders import Builder
from sphinx.util import logging
from sphinx.util.osutil import ensuredir, os_path
from sphinx.writers.text import TextWriter
from sphinx.writers.text import TextWriter, TextTranslator
if False:
# For type annotation
@ -33,6 +33,7 @@ class TextBuilder(Builder):
format = 'text'
out_suffix = '.txt'
allow_parallel = True
default_translator_class = TextTranslator
current_docname = None # type: unicode

View File

@ -14,6 +14,7 @@ from os import path
from docutils import nodes
from docutils.io import StringOutput
from docutils.writers.docutils_xml import XMLTranslator
from sphinx.builders import Builder
from sphinx.util import logging
@ -38,6 +39,7 @@ class XMLBuilder(Builder):
allow_parallel = True
_writer_class = XMLWriter
default_translator_class = XMLTranslator
def init(self):
# type: () -> None

View File

@ -308,3 +308,8 @@ class Domain(object):
if primary:
return type.lname
return _('%s %s') % (self.label, type.lname)
def get_full_qualified_name(self, node):
# type: (nodes.Node) -> unicode
"""Return full qualified name for given node."""
return None

View File

@ -5032,6 +5032,20 @@ class CPPDomain(Domain):
newestId = symbol.declaration.get_newest_id()
yield (name, name, objectType, docname, newestId, 1)
def get_full_qualified_name(self, node):
# type: (nodes.Node) -> unicode
target = node.get('reftarget', None)
if target is None:
return None
parentKey = node.get("cpp:parent_key", None)
if parentKey is None:
return None
rootSymbol = self.data['root_symbol']
parentSymbol = rootSymbol.direct_lookup(parentKey)
parentName = parentSymbol.get_full_nested_name()
return '::'.join([text_type(parentName), target])
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]

View File

@ -398,6 +398,16 @@ class JavaScriptDomain(Domain):
yield refname, refname, type, docname, \
refname.replace('$', '_S_'), 1
def get_full_qualified_name(self, node):
# type: (nodes.Node) -> unicode
modname = node.get('js:module')
prefix = node.get('js:object')
target = node.get('reftarget')
if target is None:
return None
else:
return '.'.join(filter(None, [modname, prefix, target]))
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]

View File

@ -885,6 +885,16 @@ class PythonDomain(Domain):
if type != 'module': # modules are already handled
yield (refname, refname, type, docname, refname, 1)
def get_full_qualified_name(self, node):
# type: (nodes.Node) -> unicode
modname = node.get('py:module')
clsname = node.get('py:class')
target = node.get('reftarget')
if target is None:
return None
else:
return '.'.join(filter(None, [modname, clsname, target]))
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]

View File

@ -849,7 +849,11 @@ class StandardDomain(Domain):
for doc in self.env.all_docs:
yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1)
for (prog, option), info in iteritems(self.data['progoptions']):
yield (option, option, 'cmdoption', info[0], info[1], 1)
if prog:
fullname = ".".join([prog, option])
yield (fullname, fullname, 'cmdoption', info[0], info[1], 1)
else:
yield (option, option, 'cmdoption', info[0], info[1], 1)
for (type, name), info in iteritems(self.data['objects']):
yield (name, name, type, info[0], info[1],
self.object_types[type].attrs['searchprio'])
@ -925,6 +929,15 @@ class StandardDomain(Domain):
# Maybe it is defined in orphaned document.
raise ValueError
def get_full_qualified_name(self, node):
# type: (nodes.Node) -> unicode
progname = node.get('std:program')
target = node.get('reftarget')
if progname is None or target is None:
return None
else:
return '.'.join([progname, target])
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]

View File

@ -691,7 +691,8 @@ class BuildEnvironment(object):
codecs.register_error('sphinx', self.warn_and_replace) # type: ignore
# publish manually
reader = SphinxStandaloneReader(self.app, parsers=self.config.source_parsers)
reader = SphinxStandaloneReader(self.app,
parsers=self.app.registry.get_source_parsers())
pub = Publisher(reader=reader,
writer=SphinxDummyWriter(),
destination_class=NullOutput)

View File

@ -573,7 +573,7 @@ def get_rst_suffix(app):
# type: (Sphinx) -> unicode
def get_supported_format(suffix):
# type: (unicode) -> Tuple[unicode]
parser_class = app.config.source_parsers.get(suffix)
parser_class = app.registry.get_source_parsers().get(suffix)
if parser_class is None:
return ('restructuredtext',)
if isinstance(parser_class, string_types):

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
sphinx.ext.imgconverter
~~~~~~~~~~~~~~~~~~~~~~~
Image converter extension for Sphinx
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import subprocess
from sphinx.errors import ExtensionError
from sphinx.locale import _
from sphinx.transforms.post_transforms.images import ImageConverter
from sphinx.util import logging
from sphinx.util.osutil import ENOENT, EPIPE, EINVAL
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__)
class ImagemagickConverter(ImageConverter):
conversion_rules = [
('image/svg+xml', 'image/png'),
('application/pdf', 'image/png'),
]
def is_available(self):
# type: () -> bool
"""Confirms the converter is available or not."""
try:
args = [self.config.image_converter, '-version']
logger.debug('Invoking %r ...', args)
ret = subprocess.call(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if ret == 0:
return True
else:
return False
except (OSError, IOError):
logger.warning(_('convert command %r cannot be run.'
'check the image_converter setting'),
self.config.image_converter)
return False
def convert(self, _from, _to):
# type: (unicode, unicode) -> bool
"""Converts the image to expected one."""
try:
args = ([self.config.image_converter] +
self.config.image_converter_args +
[_from, _to])
logger.debug('Invoking %r ...', args)
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
except OSError as err:
if err.errno != ENOENT: # No such file or directory
raise
logger.warning(_('convert command %r cannot be run.'
'check the image_converter setting'),
self.config.image_converter)
return False
try:
stdout, stderr = p.communicate()
except (OSError, IOError) as err:
if err.errno not in (EPIPE, EINVAL):
raise
stdout, stderr = p.stdout.read(), p.stderr.read()
p.wait()
if p.returncode != 0:
raise ExtensionError(_('convert exited with error:\n'
'[stderr]\n%s\n[stdout]\n%s') %
(stderr, stdout))
return True
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]
app.add_post_transform(ImagemagickConverter)
app.add_config_value('image_converter', 'convert', 'env')
app.add_config_value('image_converter_args', [], 'env')
return {
'version': 'builtin',
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@ -292,6 +292,10 @@ def missing_reference(app, env, node, contnode):
# until Sphinx-1.6, cmdoptions are stored as std:option
objtypes.append('std:option')
to_try = [(inventories.main_inventory, target)]
if domain:
full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)
if full_qualified_name:
to_try.append((inventories.main_inventory, full_qualified_name))
in_set = None
if ':' in target:
# first part may be the foreign doc set name
@ -299,6 +303,10 @@ def missing_reference(app, env, node, contnode):
if setname in inventories.named_inventory:
in_set = setname
to_try.append((inventories.named_inventory[setname], newtarget))
if domain:
full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)
if full_qualified_name:
to_try.append((inventories.named_inventory[setname], full_qualified_name))
for inventory, target in to_try:
for objtype in objtypes:
if objtype not in inventory or target not in inventory[objtype]:

View File

@ -9,30 +9,20 @@
:license: BSD, see LICENSE for details.
"""
import traceback
from six import iteritems
from sphinx.errors import ExtensionError, VersionRequirementError
from sphinx.errors import VersionRequirementError
from sphinx.locale import _
from sphinx.util import logging
if False:
# For type annotation
from typing import Any, Dict # NOQA
from typing import Dict # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__)
# list of deprecated extensions. Keys are extension name.
# Values are Sphinx version that merge the extension.
EXTENSION_BLACKLIST = {
"sphinxjp.themecore": "1.2"
} # type: Dict[unicode, unicode]
class Extension(object):
def __init__(self, name, module, **kwargs):
self.name = name
@ -51,54 +41,6 @@ class Extension(object):
self.parallel_write_safe = kwargs.pop('parallel_read_safe', True)
def load_extension(app, extname):
# type: (Sphinx, unicode) -> None
"""Load a Sphinx extension."""
if extname in app.extensions: # alread loaded
return
if extname in EXTENSION_BLACKLIST:
logger.warning(_('the extension %r was already merged with Sphinx since '
'version %s; this extension is ignored.'),
extname, EXTENSION_BLACKLIST[extname])
return
# update loading context
app._setting_up_extension.append(extname)
try:
mod = __import__(extname, None, None, ['setup'])
except ImportError as err:
logger.verbose(_('Original exception:\n') + traceback.format_exc())
raise ExtensionError(_('Could not import extension %s') % extname, err)
if not hasattr(mod, 'setup'):
logger.warning(_('extension %r has no setup() function; is it really '
'a Sphinx extension module?'), extname)
metadata = {} # type: Dict[unicode, Any]
else:
try:
metadata = mod.setup(app)
except VersionRequirementError as err:
# add the extension name to the version required
raise VersionRequirementError(
_('The %s extension used by this project needs at least '
'Sphinx v%s; it therefore cannot be built with this '
'version.') % (extname, err)
)
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 '
'metadata dictionary'), extname)
app.extensions[extname] = Extension(extname, mod, **metadata)
app._setting_up_extension.pop()
def verify_required_extensions(app, requirements):
# type: (Sphinx, Dict[unicode, unicode]) -> None
"""Verify the required Sphinx extensions are loaded."""

View File

@ -11,7 +11,7 @@
from docutils.io import FileInput
from docutils.readers import standalone
from docutils.writers import UnfilteredWriter
from six import string_types, text_type
from six import string_types, text_type, iteritems
from typing import Any, Union # NOQA
from sphinx.transforms import (
@ -158,9 +158,8 @@ class SphinxFileInput(FileInput):
# type: () -> unicode
def get_parser_type(source_path):
# type: (unicode) -> Tuple[unicode]
for suffix in self.env.config.source_parsers:
for suffix, parser_class in iteritems(self.app.registry.get_source_parsers()):
if source_path.endswith(suffix):
parser_class = self.env.config.source_parsers[suffix]
if isinstance(parser_class, string_types):
parser_class = import_object(parser_class, 'source parser') # type: ignore # NOQA
return parser_class.supported

226
sphinx/registry.py Normal file
View File

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
"""
sphinx.registry
~~~~~~~~~~~~~~~
Sphinx component registry.
:copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from __future__ import print_function
import traceback
from pkg_resources import iter_entry_points
from six import itervalues
from sphinx.errors import ExtensionError, SphinxError, VersionRequirementError
from sphinx.extension import Extension
from sphinx.domains import ObjType
from sphinx.domains.std import GenericObject, Target
from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docutils import directive_helper
if False:
# For type annotation
from typing import Any, Callable, Dict, Iterator, List, Type # NOQA
from docutils import nodes # NOQA
from docutils.parsers import Parser # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.domains import Domain, Index # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__)
# list of deprecated extensions. Keys are extension name.
# Values are Sphinx version that merge the extension.
EXTENSION_BLACKLIST = {
"sphinxjp.themecore": "1.2"
} # type: Dict[unicode, unicode]
class SphinxComponentRegistry(object):
def __init__(self):
self.builders = {} # type: Dict[unicode, Type[Builder]]
self.domains = {} # type: Dict[unicode, Type[Domain]]
self.source_parsers = {} # type: Dict[unicode, Parser]
self.translators = {} # type: Dict[unicode, nodes.NodeVisitor]
def add_builder(self, builder):
# type: (Type[Builder]) -> None
if not hasattr(builder, 'name'):
raise ExtensionError(_('Builder class %s has no "name" attribute') % builder)
if builder.name in self.builders:
raise ExtensionError(_('Builder %r already exists (in module %s)') %
(builder.name, self.builders[builder.name].__module__))
self.builders[builder.name] = builder
def preload_builder(self, app, name):
# type: (Sphinx, unicode) -> None
if name is None:
return
if name not in self.builders:
entry_points = iter_entry_points('sphinx.builders', name)
try:
entry_point = next(entry_points)
except StopIteration:
raise SphinxError(_('Builder name %s not registered or available'
' through entry point') % name)
self.load_extension(app, entry_point.module_name)
def create_builder(self, app, name):
# type: (Sphinx, unicode) -> Builder
if name not in self.builders:
raise SphinxError(_('Builder name %s not registered') % name)
return self.builders[name](app)
def add_domain(self, domain):
# type: (Type[Domain]) -> None
if domain.name in self.domains:
raise ExtensionError(_('domain %s already registered') % domain.name)
self.domains[domain.name] = domain
def has_domain(self, domain):
# type: (unicode) -> bool
return domain in self.domains
def create_domains(self, env):
# type: (BuildEnvironment) -> Iterator[Domain]
for DomainClass in itervalues(self.domains):
yield DomainClass(env)
def override_domain(self, domain):
# type: (Type[Domain]) -> None
if domain.name not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain.name)
if not issubclass(domain, self.domains[domain.name]):
raise ExtensionError(_('new domain not a subclass of registered %s '
'domain') % domain.name)
self.domains[domain.name] = domain
def add_directive_to_domain(self, domain, name, obj,
has_content=None, argument_spec=None, **option_spec):
# type: (unicode, unicode, Any, bool, Any, Any) -> None
if domain not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain)
directive = directive_helper(obj, has_content, argument_spec, **option_spec)
self.domains[domain].directives[name] = directive
def add_role_to_domain(self, domain, name, role):
# type: (unicode, unicode, Any) -> None
if domain not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain)
self.domains[domain].roles[name] = role
def add_index_to_domain(self, domain, index):
# type: (unicode, Type[Index]) -> None
if domain not in self.domains:
raise ExtensionError(_('domain %s not yet registered') % domain)
self.domains[domain].indices.append(index)
def add_object_type(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
doc_field_types=[]):
# type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List) -> None
# create a subclass of GenericObject as the new directive
directive = type(directivename, # type: ignore
(GenericObject, object),
{'indextemplate': indextemplate,
'parse_node': staticmethod(parse_node), # type: ignore
'doc_field_types': doc_field_types})
stddomain = self.domains['std']
stddomain.directives[directivename] = directive
stddomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
stddomain.object_types[directivename] = ObjType(objname or directivename, rolename)
def add_crossref_type(self, directivename, rolename, indextemplate='',
ref_nodeclass=None, objname=''):
# type: (unicode, unicode, unicode, nodes.Node, unicode) -> None
# create a subclass of Target as the new directive
directive = type(directivename, # type: ignore
(Target, object),
{'indextemplate': indextemplate})
stddomain = self.domains['std']
stddomain.directives[directivename] = directive
stddomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
stddomain.object_types[directivename] = ObjType(objname or directivename, rolename)
def add_source_parser(self, suffix, parser):
# type: (unicode, Parser) -> None
if suffix in self.source_parsers:
raise ExtensionError(_('source_parser for %r is already registered') % suffix)
self.source_parsers[suffix] = parser
def get_source_parsers(self):
# type: () -> Dict[unicode, Parser]
return self.source_parsers
def add_translator(self, name, translator):
# type: (unicode, Type[nodes.NodeVisitor]) -> None
self.translators[name] = translator
def get_translator_class(self, builder):
# type: (Builder) -> Type[nodes.NodeVisitor]
return self.translators.get(builder.name,
builder.default_translator_class)
def create_translator(self, builder, document):
# type: (Builder, nodes.Node) -> nodes.NodeVisitor
translator_class = self.get_translator_class(builder)
return translator_class(builder, document)
def load_extension(self, app, extname):
# type: (Sphinx, unicode) -> None
"""Load a Sphinx extension."""
if extname in app.extensions: # alread loaded
return
if extname in EXTENSION_BLACKLIST:
logger.warning(_('the extension %r was already merged with Sphinx since '
'version %s; this extension is ignored.'),
extname, EXTENSION_BLACKLIST[extname])
return
# update loading context
app._setting_up_extension.append(extname)
try:
mod = __import__(extname, None, None, ['setup'])
except ImportError as err:
logger.verbose(_('Original exception:\n') + traceback.format_exc())
raise ExtensionError(_('Could not import extension %s') % extname, err)
if not hasattr(mod, 'setup'):
logger.warning(_('extension %r has no setup() function; is it really '
'a Sphinx extension module?'), extname)
metadata = {} # type: Dict[unicode, Any]
else:
try:
metadata = mod.setup(app)
except VersionRequirementError as err:
# add the extension name to the version required
raise VersionRequirementError(
_('The %s extension used by this project needs at least '
'Sphinx v%s; it therefore cannot be built with this '
'version.') % (extname, err)
)
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 '
'metadata dictionary'), extname)
app.extensions[extname] = Extension(extname, mod, **metadata)
app._setting_up_extension.pop()

View File

@ -12,6 +12,7 @@
import os
import shutil
import tempfile
import warnings
from os import path
from zipfile import ZipFile
@ -20,6 +21,7 @@ from six import string_types, iteritems
from six.moves import configparser
from sphinx import package_dir
from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.errors import ThemeError
from sphinx.locale import _
from sphinx.util import logging
@ -77,6 +79,8 @@ class Theme(object):
try:
inherit = self.config.get('theme', 'inherit')
except configparser.NoSectionError:
raise ThemeError(_('theme %r doesn\'t have "theme" setting') % name)
except configparser.NoOptionError:
raise ThemeError(_('theme %r doesn\'t have "inherit" setting') % name)
@ -161,7 +165,7 @@ class HTMLThemeFactory(object):
def __init__(self, app):
# type: (Sphinx) -> None
self.confdir = app.confdir
self.app = app
self.themes = app.html_themes
self.load_builtin_themes()
if getattr(app.config, 'html_theme_path', None):
@ -178,7 +182,7 @@ class HTMLThemeFactory(object):
# type: (unicode) -> None
"""Load additional themes placed at specified directories."""
for theme_path in theme_paths:
abs_theme_path = path.abspath(path.join(self.confdir, theme_path))
abs_theme_path = path.abspath(path.join(self.app.confdir, theme_path))
themes = self.find_themes(abs_theme_path)
for name, theme in iteritems(themes):
self.themes[name] = theme
@ -215,6 +219,16 @@ class HTMLThemeFactory(object):
Sphinx refers to ``sphinx_themes`` entry_points.
"""
# look up for new styled entry_points at first
entry_points = pkg_resources.iter_entry_points('sphinx.html_themes', name)
try:
entry_point = next(entry_points)
self.app.registry.load_extension(self.app, entry_point.module_name)
return
except StopIteration:
pass
# look up for old styled entry_points
for entry_point in pkg_resources.iter_entry_points('sphinx_themes'):
target = entry_point.load()
if callable(target):
@ -228,6 +242,9 @@ class HTMLThemeFactory(object):
themes = self.find_themes(themedir)
for entry, theme in iteritems(themes):
if name == entry:
warnings.warn('``sphinx_themes`` entry point is now deprecated. '
'Please use ``sphinx.html_themes`` instead.',
RemovedInSphinx20Warning)
self.themes[name] = theme
def find_themes(self, theme_path):

View File

@ -52,7 +52,7 @@ def publish_msgstr(app, source, source_path, source_line, config, settings):
from sphinx.io import SphinxI18nReader
reader = SphinxI18nReader(
app=app,
parsers=config.source_parsers,
parsers=app.registry.get_source_parsers(),
parser_name='restructuredtext', # default parser
)
reader.set_lineno_for_reporter(source_line)

View File

@ -24,7 +24,7 @@ from sphinx.util.osutil import ensuredir
if False:
# For type annotation
from typing import Any, Dict # NOQA
from typing import Any, Dict, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
@ -136,6 +136,107 @@ class DataURIExtractor(BaseImageConverter):
self.app.env.images.add_file(self.env.docname, path)
def get_filename_for(filename, mimetype):
# type: (unicode, unicode) -> unicode
basename = os.path.basename(filename)
return os.path.splitext(basename)[0] + get_image_extension(mimetype)
class ImageConverter(BaseImageConverter):
"""A base class images converter.
The concrete image converters should derive this class and
overrides the following methods and attributes:
* default_priority (if needed)
* conversion_rules
* is_available()
* convert()
"""
default_priority = 200
#: A conversion rules between two mimetypes which this converters supports
conversion_rules = [] # type: List[Tuple[unicode, unicode]]
def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
self.available = None # type: bool
# the converter is available or not.
# Will be checked at first conversion
BaseImageConverter.__init__(self, *args, **kwargs) # type: ignore
def match(self, node):
# type: (nodes.Node) -> bool
if self.available is None:
self.available = self.is_available()
if not self.available:
return False
elif set(node['candidates']) & set(self.app.builder.supported_image_types):
# builder supports the image; no need to convert
return False
else:
rule = self.get_conversion_rule(node)
if rule:
return True
else:
return False
def get_conversion_rule(self, node):
# type: (nodes.Node) -> Tuple[unicode, unicode]
for candidate in self.guess_mimetypes(node):
for supported in self.app.builder.supported_image_types:
rule = (candidate, supported)
if rule in self.conversion_rules:
return rule
return None
def is_available(self):
# type: () -> bool
"""Confirms the converter is available or not."""
raise NotImplemented
def guess_mimetypes(self, node):
# type: (nodes.Node) -> List[unicode]
if '?' in node['candidates']:
return []
elif '*' in node['candidates']:
from sphinx.util.images import guess_mimetype
return [guess_mimetype(node['uri'])]
else:
return node['candidates'].keys()
def handle(self, node):
# type: (nodes.Node) -> None
_from, _to = self.get_conversion_rule(node)
if _from in node['candidates']:
srcpath = node['candidates'][_from]
else:
srcpath = node['candidates']['*']
filename = get_filename_for(srcpath, _to)
ensuredir(self.imagedir)
destpath = os.path.join(self.imagedir, filename)
abs_srcpath = os.path.join(self.app.srcdir, srcpath)
if self.convert(abs_srcpath, destpath):
if '*' in node['candidates']:
node['candidates']['*'] = destpath
else:
node['candidates'][_to] = destpath
node['uri'] = destpath
self.env.original_image_uri[destpath] = srcpath
self.env.images.add_file(self.env.docname, destpath)
def convert(self, _from, _to):
# type: (unicode, unicode) -> bool
"""Converts the image to expected one."""
raise NotImplemented
def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]
app.add_post_transform(ImageDownloader)

View File

@ -11,13 +11,16 @@
from __future__ import absolute_import
import re
import types
from copy import copy
from contextlib import contextmanager
import docutils
from docutils.utils import Reporter
from docutils.parsers.rst import directives, roles
from docutils.parsers.rst import directives, roles, convert_directive_function
from sphinx.errors import ExtensionError
from sphinx.locale import _
from sphinx.util import logging
logger = logging.getLogger(__name__)
@ -154,3 +157,17 @@ class LoggingReporter(Reporter):
def is_html5_writer_available():
# type: () -> bool
return __version_info__ > (0, 13, 0)
def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
# type: (Any, bool, Tuple[int, int, bool], Any) -> Any
if isinstance(obj, (types.FunctionType, types.MethodType)):
obj.content = has_content # type: ignore
obj.arguments = argument_spec or (0, 0, False) # type: ignore
obj.options = option_spec # type: ignore
return convert_directive_function(obj)
else:
if has_content or argument_spec or option_spec:
raise ExtensionError(_('when adding directive classes, no '
'additional arguments may be given'))
return obj

View File

@ -52,8 +52,8 @@ class HTMLWriter(Writer):
def translate(self):
# type: () -> None
# sadly, this is mostly copied from parent class
self.visitor = visitor = self.builder.translator_class(self.builder,
self.document)
self.visitor = visitor = self.builder.create_translator(self.builder,
self.document)
self.document.walkabout(visitor)
self.output = visitor.astext()
for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',

View File

@ -159,14 +159,12 @@ class LaTeXWriter(writers.Writer):
# type: (Builder) -> None
writers.Writer.__init__(self)
self.builder = builder
self.translator_class = (
self.builder.translator_class or LaTeXTranslator)
def translate(self):
# type: () -> None
transform = ShowUrlsTransform(self.document)
transform.apply()
visitor = self.translator_class(self.document, self.builder)
visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor)
self.output = visitor.astext()

View File

@ -35,14 +35,12 @@ class ManualPageWriter(Writer):
# type: (Builder) -> None
Writer.__init__(self)
self.builder = builder
self.translator_class = (
self.builder.translator_class or ManualPageTranslator)
def translate(self):
# type: () -> None
transform = NestedInlineTransform(self.document)
transform.apply()
visitor = self.translator_class(self.builder, self.document)
visitor = self.builder.create_translator(self.builder, self.document)
self.visitor = visitor
self.document.walkabout(visitor)
self.output = visitor.astext()

View File

@ -133,13 +133,10 @@ class TexinfoWriter(writers.Writer):
# type: (TexinfoBuilder) -> None
writers.Writer.__init__(self)
self.builder = builder
self.translator_class = (
self.builder.translator_class or TexinfoTranslator)
def translate(self):
# type: () -> None
self.visitor = visitor = self.translator_class(
self.document, self.builder)
self.visitor = visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor)
visitor.finish()
for attr in self.visitor_attributes:

View File

@ -159,11 +159,10 @@ class TextWriter(writers.Writer):
# type: (TextBuilder) -> None
writers.Writer.__init__(self)
self.builder = builder
self.translator_class = self.builder.translator_class or TextTranslator
def translate(self):
# type: () -> None
visitor = self.translator_class(self.document, self.builder)
visitor = self.builder.create_translator(self.document, self.builder)
self.document.walkabout(visitor)
self.output = visitor.body

View File

@ -24,8 +24,7 @@ class XMLWriter(BaseXMLWriter):
# type: (Builder) -> None
BaseXMLWriter.__init__(self)
self.builder = builder
if self.builder.translator_class:
self.translator_class = self.builder.translator_class
self.translator_class = self.builder.get_translator_class()
def translate(self, *args, **kwargs):
# type: (Any, Any) -> None

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
master_doc = 'index'
extensions = ['sphinx.ext.imgconverter']

View File

@ -0,0 +1,4 @@
test-ext-imgconverter
=====================
.. image:: svgimg.svg

View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="60"
width="60"
_SVGFile__filename="oldscale/apps/warning.svg"
version="1.0"
y="0"
x="0"
id="svg1"
sodipodi:version="0.32"
inkscape:version="0.41"
sodipodi:docname="exclamation.svg"
sodipodi:docbase="/home/danny/work/icons/primary/scalable/actions">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0000000"
inkscape:pageshadow="2"
inkscape:zoom="7.5136000"
inkscape:cx="42.825186"
inkscape:cy="24.316071"
inkscape:window-width="1020"
inkscape:window-height="691"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:current-layer="svg1" />
<defs
id="defs3">
<linearGradient
id="linearGradient1160">
<stop
style="stop-color: #000000;stop-opacity: 1.0;"
id="stop1161"
offset="0" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
id="stop1162"
offset="1" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient1160"
id="linearGradient1163" />
</defs>
<metadata
id="metadata12">
<RDF
id="RDF13">
<Work
about=""
id="Work14">
<title
id="title15">Part of the Flat Icon Collection (Thu Aug 26 14:31:40 2004)</title>
<description
id="description17" />
<subject
id="subject18">
<Bag
id="Bag19">
<li
id="li20" />
</Bag>
</subject>
<publisher
id="publisher21">
<Agent
about=""
id="Agent22">
<title
id="title23" />
</Agent>
</publisher>
<creator
id="creator24">
<Agent
about=""
id="Agent25">
<title
id="title26">Danny Allen</title>
</Agent>
</creator>
<rights
id="rights28">
<Agent
about=""
id="Agent29">
<title
id="title30">Danny Allen</title>
</Agent>
</rights>
<date
id="date32" />
<format
id="format33">image/svg+xml</format>
<type
id="type35"
resource="http://purl.org/dc/dcmitype/StillImage" />
<license
id="license36"
resource="http://creativecommons.org/licenses/LGPL/2.1/">
<date
id="date37" />
</license>
<language
id="language38">en</language>
</Work>
</RDF>
<rdf:RDF
id="RDF40">
<cc:Work
rdf:about=""
id="Work41">
<dc:format
id="format42">image/svg+xml</dc:format>
<dc:type
id="type44"
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g2099">
<path
style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 55.311891,51.920745 L 4.6880989,51.920744 L 29.999995,8.0792542 L 55.311891,51.920745 z "
id="path1724" />
<path
style="color:#000000;fill:#ffe940;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:3.1250010;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 55.311891,51.920745 L 4.6880989,51.920744 L 29.999995,8.0792542 L 55.311891,51.920745 z "
id="path1722" />
<path
style="font-size:12.000000;font-weight:900;fill:none;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:8.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
d="M 34.944960,10.779626 L 34.944960,33.186510 C 34.944960,34.752415 34.501979,36.081368 33.616007,37.173380 C 32.750636,38.265402 31.545298,38.811408 29.999995,38.811408 C 28.475302,38.811408 27.269965,38.265402 26.383993,37.173380 C 25.498020,36.060767 25.055030,34.731804 25.055030,33.186510 L 25.055030,10.779626 C 25.055030,9.1931155 25.498020,7.8641562 26.383993,6.7927462 C 27.269965,5.7007332 28.475302,5.1547262 29.999995,5.1547262 C 31.009593,5.1547262 31.885265,5.4019740 32.627010,5.8964706 C 33.389356,6.3909681 33.966274,7.0709005 34.357752,7.9362696 C 34.749221,8.7810349 34.944960,9.7288200 34.944960,10.779626 z "
id="path1099" />
<path
style="font-size:12.000000;font-weight:900;fill:#e71c02;fill-opacity:1.0000000;stroke:none;stroke-width:3.1249981;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0000000"
d="M 29.999995,3.5986440 C 28.102272,3.5986440 26.318514,4.3848272 25.156245,5.8173940 C 24.028906,7.1806889 23.499995,8.9087770 23.499995,10.786144 L 23.499995,33.192394 C 23.499995,35.036302 24.050685,36.772771 25.156245,38.161144 C 26.318514,39.593721 28.102273,40.379893 29.999995,40.379894 C 31.913354,40.379894 33.697195,39.576736 34.843745,38.129894 C 35.959941,36.754118 36.499995,35.052976 36.499995,33.192394 L 36.499995,10.786144 C 36.499995,9.5413010 36.276626,8.3551469 35.781245,7.2861440 C 35.278844,6.1755772 34.477762,5.2531440 33.468745,4.5986440 C 32.454761,3.9226545 31.264694,3.5986439 29.999995,3.5986440 z "
id="path835"
sodipodi:nodetypes="cccccccccccc" />
<path
style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:5.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 36.506243,49.901522 C 36.506243,53.492972 33.591442,56.407773 29.999991,56.407773 C 26.408541,56.407773 23.493739,53.492972 23.493739,49.901522 C 23.493739,46.310071 26.408541,43.395270 29.999991,43.395270 C 33.591442,43.395270 36.506243,46.310071 36.506243,49.901522 z "
id="path1727" />
<path
style="color:#000000;fill:#e71c02;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:3.1250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 36.506243,49.901522 C 36.506243,53.492972 33.591442,56.407773 29.999991,56.407773 C 26.408541,56.407773 23.493739,53.492972 23.493739,49.901522 C 23.493739,46.310071 26.408541,43.395270 29.999991,43.395270 C 33.591442,43.395270 36.506243,46.310071 36.506243,49.901522 z "
id="path1725" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
extensions = ['sphinx.ext.intersphinx']
master_doc = 'index'

View File

@ -0,0 +1,6 @@
test-ext-intersphinx-cppdomain
==============================
.. cpp:namespace:: foo
:cpp:class:`Bar`

View File

@ -26,7 +26,7 @@ def teardown_module():
@pytest.mark.sphinx('html')
def test_html_translator(app, status, warning):
# no set_translator()
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'SmartyPantsHTMLTranslator'
@ -35,7 +35,7 @@ def test_html_translator(app, status, warning):
'html_use_smartypants': False})
def test_html_with_smartypants(app, status, warning):
# no set_translator(), html_use_smartypants=False
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'HTMLTranslator'
@ -43,69 +43,69 @@ def test_html_with_smartypants(app, status, warning):
@pytest.mark.sphinx('html', testroot='api-set-translator')
def test_html_with_set_translator_for_html_(app, status, warning):
# use set_translator()
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfHTMLTranslator'
@pytest.mark.sphinx('singlehtml', testroot='api-set-translator')
def test_singlehtml_set_translator_for_singlehtml(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfSingleHTMLTranslator'
@pytest.mark.sphinx('pickle', testroot='api-set-translator')
def test_pickle_set_translator_for_pickle(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfPickleTranslator'
@pytest.mark.sphinx('json', testroot='api-set-translator')
def test_json_set_translator_for_json(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfJsonTranslator'
@pytest.mark.sphinx('latex', testroot='api-set-translator')
def test_html_with_set_translator_for_latex(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfLaTeXTranslator'
@pytest.mark.sphinx('man', testroot='api-set-translator')
def test_html_with_set_translator_for_man(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfManualPageTranslator'
@pytest.mark.sphinx('texinfo', testroot='api-set-translator')
def test_html_with_set_translator_for_texinfo(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfTexinfoTranslator'
@pytest.mark.sphinx('text', testroot='api-set-translator')
def test_html_with_set_translator_for_text(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfTextTranslator'
@pytest.mark.sphinx('xml', testroot='api-set-translator')
def test_html_with_set_translator_for_xml(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfXMLTranslator'
@pytest.mark.sphinx('pseudoxml', testroot='api-set-translator')
def test_html_with_set_translator_for_pseudoxml(app, status, warning):
translator_class = app.builder.translator_class
translator_class = app.builder.get_translator_class()
assert translator_class
assert translator_class.__name__ == 'ConfPseudoXMLTranslator'

View File

@ -85,13 +85,6 @@ def test_domain_override(app, status, warning):
@pytest.mark.sphinx(testroot='add_source_parser')
def test_add_source_parser(app, status, warning):
assert set(app.config.source_suffix) == set(['.rst', '.md', '.test'])
assert set(app.config.source_parsers.keys()) == set(['.md', '.test'])
assert app.config.source_parsers['.md'].__name__ == 'DummyMarkdownParser'
assert app.config.source_parsers['.test'].__name__ == 'TestSourceParser'
@pytest.mark.sphinx(testroot='add_source_parser-conflicts-with-users-setting')
def test_add_source_parser_conflicts_with_users_setting(app, status, warning):
assert set(app.config.source_suffix) == set(['.rst', '.test'])
assert set(app.config.source_parsers.keys()) == set(['.test'])
assert app.config.source_parsers['.test'].__name__ == 'DummyTestParser'
assert set(app.registry.get_source_parsers().keys()) == set(['.md', '.test'])
assert app.registry.get_source_parsers()['.md'].__name__ == 'DummyMarkdownParser'
assert app.registry.get_source_parsers()['.test'].__name__ == 'TestSourceParser'

View File

@ -87,6 +87,14 @@ def compile_latex_document(app):
app.config.latex_engine, p.returncode)
def skip_if_requested(testfunc):
if 'SKIP_LATEX_BUILD' in os.environ:
msg = 'Skip LaTeX builds because SKIP_LATEX_BUILD is set'
return skip_if(True, msg)(testfunc)
else:
return testfunc
def skip_if_stylefiles_notfound(testfunc):
if kpsetest(*STYLEFILES) is False:
msg = 'not running latex, the required styles do not seem to be installed'
@ -95,6 +103,7 @@ def skip_if_stylefiles_notfound(testfunc):
return testfunc
@skip_if_requested
@skip_if_stylefiles_notfound
@pytest.mark.parametrize(
"engine,docclass",

View File

@ -10,7 +10,11 @@
"""
import pytest
from mock import Mock
from docutils import nodes
from sphinx import addnodes
from sphinx.domains.javascript import JavaScriptDomain
from util import assert_node
@ -133,3 +137,31 @@ def test_domain_js_find_obj(app, status, warning):
( u'module_a.submodule.ModTopLevel.mod_child_2', (u'module', u'method')))
assert (find_obj(u'module_b.submodule', u'ModTopLevel', u'module_a.submodule', u'mod') ==
( u'module_a.submodule', (u'module', u'module')))
def test_get_full_qualified_name():
env = Mock(domaindata={})
domain = JavaScriptDomain(env)
# non-js references
node = nodes.reference()
assert domain.get_full_qualified_name(node) is None
# simple reference
node = nodes.reference(reftarget='func')
assert domain.get_full_qualified_name(node) == 'func'
# with js:module context
kwargs = {'js:module': 'module1'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'module1.func'
# with js:object context
kwargs = {'js:object': 'Class'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'Class.func'
# with both js:module and js:object context
kwargs = {'js:module': 'module1', 'js:object': 'Class'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'module1.Class.func'

View File

@ -10,9 +10,12 @@
"""
import pytest
from mock import Mock
from six import text_type
from docutils import nodes
from sphinx import addnodes
from sphinx.domains.python import py_sig_re, _pseudo_parse_arglist
from sphinx.domains.python import py_sig_re, _pseudo_parse_arglist, PythonDomain
from util import assert_node
@ -28,7 +31,6 @@ def parse(sig):
def test_function_signatures():
rv = parse('func(a=1) -> int object')
assert text_type(rv) == u'a=1'
@ -165,3 +167,31 @@ def test_domain_py_find_obj(app, status, warning):
[(u'NestedParentA.NestedChildA.subchild_1', (u'roles', u'method'))])
assert (find_obj(None, u'NestedParentA.NestedChildA', u'subchild_1', u'meth') ==
[(u'NestedParentA.NestedChildA.subchild_1', (u'roles', u'method'))])
def test_get_full_qualified_name():
env = Mock(domaindata={})
domain = PythonDomain(env)
# non-python references
node = nodes.reference()
assert domain.get_full_qualified_name(node) is None
# simple reference
node = nodes.reference(reftarget='func')
assert domain.get_full_qualified_name(node) == 'func'
# with py:module context
kwargs = {'py:module': 'module1'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'module1.func'
# with py:class context
kwargs = {'py:class': 'Class'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'Class.func'
# with both py:module and py:class context
kwargs = {'py:module': 'module1', 'py:class': 'Class'}
node = nodes.reference(reftarget='func', **kwargs)
assert domain.get_full_qualified_name(node) == 'module1.Class.func'

View File

@ -57,3 +57,21 @@ def test_process_doc_handle_table_title():
assert 'testname' in domain.data['labels']
assert domain.data['labels']['testname'] == (
'testdoc', 'testid', 'title text')
def test_get_full_qualified_name():
env = mock.Mock(domaindata={})
domain = StandardDomain(env)
# normal references
node = nodes.reference()
assert domain.get_full_qualified_name(node) is None
# simple reference to options
node = nodes.reference(reftype='option', reftarget='-l')
assert domain.get_full_qualified_name(node) is None
# options with std:program context
kwargs = {'std:program': 'ls'}
node = nodes.reference(reftype='option', reftarget='-l', **kwargs)
assert domain.get_full_qualified_name(node) == 'ls.-l'

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
"""
test_ext_imgconverter
~~~~~~~~~~~~~~~~~~~~~
Test sphinx.ext.imgconverter extension.
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import pytest
@pytest.mark.sphinx('latex', testroot='ext-imgconverter')
def test_ext_imgconverter(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'Python.tex').text()
assert '\\sphinxincludegraphics{{svgimg}.png}' in content
assert not (app.outdir / 'svgimg.svg').exists()
assert (app.outdir / 'svgimg.png').exists()

View File

@ -26,6 +26,22 @@ from sphinx.ext.intersphinx import (
from test_util_inventory import inventory_v2
def fake_node(domain, type, target, content, **attrs):
contnode = nodes.emphasis(content, content)
node = addnodes.pending_xref('')
node['reftarget'] = target
node['reftype'] = type
node['refdomain'] = domain
node.attributes.update(attrs)
node += contnode
return node, contnode
def reference_check(app, *args, **kwds):
node, contnode = fake_node(*args, **kwds)
return missing_reference(app, app.env, node, contnode)
@mock.patch('sphinx.ext.intersphinx.InventoryFile')
@mock.patch('sphinx.ext.intersphinx._read_from_url')
def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status, warning):
@ -88,46 +104,30 @@ def test_missing_reference(tempdir, app, status, warning):
assert inv['py:module']['module2'] == \
('foo', '2.0', 'https://docs.python.org/foo.html#module-module2', '-')
# create fake nodes and check referencing
def fake_node(domain, type, target, content, **attrs):
contnode = nodes.emphasis(content, content)
node = addnodes.pending_xref('')
node['reftarget'] = target
node['reftype'] = type
node['refdomain'] = domain
node.attributes.update(attrs)
node += contnode
return node, contnode
def reference_check(*args, **kwds):
node, contnode = fake_node(*args, **kwds)
return missing_reference(app, app.env, node, contnode)
# check resolution when a target is found
rn = reference_check('py', 'func', 'module1.func', 'foo')
rn = reference_check(app, 'py', 'func', 'module1.func', 'foo')
assert isinstance(rn, nodes.reference)
assert rn['refuri'] == 'https://docs.python.org/sub/foo.html#module1.func'
assert rn['reftitle'] == '(in foo v2.0)'
assert rn[0].astext() == 'foo'
# create unresolvable nodes and check None return value
assert reference_check('py', 'foo', 'module1.func', 'foo') is None
assert reference_check('py', 'func', 'foo', 'foo') is None
assert reference_check('py', 'func', 'foo', 'foo') is None
assert reference_check(app, 'py', 'foo', 'module1.func', 'foo') is None
assert reference_check(app, 'py', 'func', 'foo', 'foo') is None
assert reference_check(app, 'py', 'func', 'foo', 'foo') is None
# check handling of prefixes
# prefix given, target found: prefix is stripped
rn = reference_check('py', 'mod', 'py3k:module2', 'py3k:module2')
rn = reference_check(app, 'py', 'mod', 'py3k:module2', 'py3k:module2')
assert rn[0].astext() == 'module2'
# prefix given, but not in title: nothing stripped
rn = reference_check('py', 'mod', 'py3k:module2', 'module2')
rn = reference_check(app, 'py', 'mod', 'py3k:module2', 'module2')
assert rn[0].astext() == 'module2'
# prefix given, but explicit: nothing stripped
rn = reference_check('py', 'mod', 'py3k:module2', 'py3k:module2',
rn = reference_check(app, 'py', 'mod', 'py3k:module2', 'py3k:module2',
refexplicit=True)
assert rn[0].astext() == 'py3k:module2'
@ -146,23 +146,116 @@ def test_missing_reference(tempdir, app, status, warning):
assert contnode[0].astext() == 'py3k:unknown'
# check relative paths
rn = reference_check('py', 'mod', 'py3krel:module1', 'foo')
rn = reference_check(app, 'py', 'mod', 'py3krel:module1', 'foo')
assert rn['refuri'] == 'py3k/foo.html#module-module1'
rn = reference_check('py', 'mod', 'py3krelparent:module1', 'foo')
rn = reference_check(app, 'py', 'mod', 'py3krelparent:module1', 'foo')
assert rn['refuri'] == '../../py3k/foo.html#module-module1'
rn = reference_check('py', 'mod', 'py3krel:module1', 'foo', refdoc='sub/dir/test')
rn = reference_check(app, 'py', 'mod', 'py3krel:module1', 'foo', refdoc='sub/dir/test')
assert rn['refuri'] == '../../py3k/foo.html#module-module1'
rn = reference_check('py', 'mod', 'py3krelparent:module1', 'foo', refdoc='sub/dir/test')
rn = reference_check(app, 'py', 'mod', 'py3krelparent:module1', 'foo',
refdoc='sub/dir/test')
assert rn['refuri'] == '../../../../py3k/foo.html#module-module1'
# check refs of standard domain
rn = reference_check('std', 'doc', 'docname', 'docname')
rn = reference_check(app, 'std', 'doc', 'docname', 'docname')
assert rn['refuri'] == 'https://docs.python.org/docname.html'
def test_missing_reference_pydomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly
load_mappings(app)
# no context data
kwargs = {}
node, contnode = fake_node('py', 'func', 'func', 'func()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn is None
# py:module context helps to search objects
kwargs = {'py:module': 'module1'}
node, contnode = fake_node('py', 'func', 'func', 'func()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'func()'
def test_missing_reference_stddomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly
load_mappings(app)
# no context data
kwargs = {}
node, contnode = fake_node('std', 'option', '-l', '-l', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn is None
# std:program context helps to search objects
kwargs = {'std:program': 'ls'}
node, contnode = fake_node('std', 'option', '-l', 'ls -l', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'ls -l'
@pytest.mark.sphinx('html', testroot='ext-intersphinx-cppdomain')
def test_missing_reference_cppdomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly
load_mappings(app)
app.build()
html = (app.outdir / 'index.html').text()
assert ('<a class="reference external"'
' href="https://docs.python.org/index.html#cpp_foo_bar"'
' title="(in foo v2.0)"><code class="xref cpp cpp-class docutils literal">'
'<span class="pre">Bar</span></code></a>' in html)
def test_missing_reference_jsdomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly
load_mappings(app)
# no context data
kwargs = {}
node, contnode = fake_node('js', 'meth', 'baz', 'baz()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn is None
# js:module and js:object context helps to search objects
kwargs = {'js:module': 'foo', 'js:object': 'bar'}
node, contnode = fake_node('js', 'meth', 'baz', 'baz()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'baz()'
def test_load_mappings_warnings(tempdir, app, status, warning):
"""
load_mappings issues a warning if new-style mapping

View File

@ -34,8 +34,15 @@ module1 py:module 0 foo.html#module-module1 Long Module desc
module2 py:module 0 foo.html#module-$ -
module1.func py:function 1 sub/foo.html#$ -
CFunc c:function 2 cfunc.html#CFunc -
foo::Bar cpp:class 1 index.html#cpp_foo_bar -
foo::Bar::baz cpp:function 1 index.html#cpp_foo_bar_baz -
a term std:term -1 glossary.html#term-a-term -
ls.-l std:cmdoption 1 index.html#cmdoption-ls-l -
docname std:doc -1 docname.html -
foo js:module 1 index.html#foo -
foo.bar js:class 1 index.html#foo.bar -
foo.bar.baz js:method 1 index.html#foo.bar.baz -
foo.bar.qux js:data 1 index.html#foo.bar.qux -
a term including:colon std:term -1 glossary.html#term-a-term-including-colon -
'''.encode('utf-8'))