summaryrefslogtreecommitdiff
path: root/sphinx/registry.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/registry.py')
-rw-r--r--sphinx/registry.py319
1 files changed, 242 insertions, 77 deletions
diff --git a/sphinx/registry.py b/sphinx/registry.py
index cdae77224..e11f0654d 100644
--- a/sphinx/registry.py
+++ b/sphinx/registry.py
@@ -11,10 +11,15 @@
from __future__ import print_function
import traceback
+import warnings
+from inspect import isclass
+from types import MethodType
+from docutils.parsers.rst import Directive
from pkg_resources import iter_entry_points
-from six import iteritems, itervalues, string_types
+from six import iteritems, itervalues
+from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.domains import ObjType
from sphinx.domains.std import GenericObject, Target
from sphinx.errors import ExtensionError, SphinxError, VersionRequirementError
@@ -22,14 +27,12 @@ from sphinx.extension import Extension
from sphinx.locale import __
from sphinx.parsers import Parser as SphinxParser
from sphinx.roles import XRefRole
-from sphinx.util import import_object
from sphinx.util import logging
-from sphinx.util.console import bold # type: ignore
from sphinx.util.docutils import directive_helper
if False:
# For type annotation
- from typing import Any, Callable, Dict, Iterator, List, Type, Union # NOQA
+ from typing import Any, Callable, Dict, Iterator, List, Tuple, Type, Union # NOQA
from docutils import nodes # NOQA
from docutils.io import Input # NOQA
from docutils.parsers import Parser # NOQA
@@ -39,7 +42,7 @@ if False:
from sphinx.domains import Domain, Index # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.ext.autodoc import Documenter # NOQA
- from sphinx.util.typing import RoleFunction # NOQA
+ from sphinx.util.typing import RoleFunction, TitleGetter # NOQA
logger = logging.getLogger(__name__)
@@ -52,26 +55,73 @@ EXTENSION_BLACKLIST = {
class SphinxComponentRegistry(object):
def __init__(self):
+ # type: () -> None
+ #: special attrgetter for autodoc; class object -> attrgetter
self.autodoc_attrgettrs = {} # type: Dict[Type, Callable[[Any, unicode, Any], Any]]
+
+ #: builders; a dict of builder name -> bulider class
self.builders = {} # type: Dict[unicode, Type[Builder]]
+
+ #: autodoc documenters; a dict of documenter name -> documenter class
self.documenters = {} # type: Dict[unicode, Type[Documenter]]
+
+ #: css_files; a list of tuple of filename and attributes
+ self.css_files = [] # type: List[Tuple[unicode, Dict[unicode, unicode]]]
+
+ #: domains; a dict of domain name -> domain class
self.domains = {} # type: Dict[unicode, Type[Domain]]
+
+ #: additional directives for domains
+ #: a dict of domain name -> dict of directive name -> directive
self.domain_directives = {} # type: Dict[unicode, Dict[unicode, Any]]
+
+ #: additional indices for domains
+ #: a dict of domain name -> list of index class
self.domain_indices = {} # type: Dict[unicode, List[Type[Index]]]
+
+ #: additional object types for domains
+ #: a dict of domain name -> dict of objtype name -> objtype
self.domain_object_types = {} # type: Dict[unicode, Dict[unicode, ObjType]]
+
+ #: additional roles for domains
+ #: a dict of domain name -> dict of role name -> role impl.
self.domain_roles = {} # type: Dict[unicode, Dict[unicode, Union[RoleFunction, XRefRole]]] # NOQA
+
+ #: additional enumerable nodes
+ #: a dict of node class -> tuple of figtype and title_getter function
+ self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, TitleGetter]]
+
+ #: LaTeX packages; list of package names and its options
+ self.latex_packages = [] # type: List[Tuple[unicode, unicode]]
+
+ #: post transforms; list of transforms
self.post_transforms = [] # type: List[Type[Transform]]
- self.source_parsers = {} # type: Dict[unicode, Parser]
+
+ #: source paresrs; file type -> parser class
+ self.source_parsers = {} # type: Dict[unicode, Type[Parser]]
+
+ #: source inputs; file type -> input class
self.source_inputs = {} # type: Dict[unicode, Input]
+
+ #: source suffix: suffix -> file type
+ self.source_suffix = {} # type: Dict[unicode, unicode]
+
+ #: custom translators; builder name -> translator class
self.translators = {} # type: Dict[unicode, nodes.NodeVisitor]
+
+ #: custom handlers for translators
+ #: a dict of builder name -> dict of node name -> visitor and departure functions
+ self.translation_handlers = {} # type: Dict[unicode, Dict[unicode, Tuple[Callable, Callable]]] # NOQA
+
+ #: additional transforms; list of transforms
self.transforms = [] # type: List[Type[Transform]]
- def add_builder(self, builder):
- # type: (Type[Builder]) -> None
+ def add_builder(self, builder, override=False):
+ # type: (Type[Builder], bool) -> 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.builders:
+ if builder.name in self.builders and not override:
raise ExtensionError(__('Builder %r already exists (in module %s)') %
(builder.name, self.builders[builder.name].__module__))
self.builders[builder.name] = builder
@@ -98,10 +148,10 @@ class SphinxComponentRegistry(object):
return self.builders[name](app)
- def add_domain(self, domain):
- # type: (Type[Domain]) -> None
+ def add_domain(self, domain, override=False):
+ # type: (Type[Domain], bool) -> None
logger.debug('[app] adding domain: %r', domain)
- if domain.name in self.domains:
+ if domain.name in self.domains and not override:
raise ExtensionError(__('domain %s already registered') % domain.name)
self.domains[domain.name] = domain
@@ -125,44 +175,54 @@ class SphinxComponentRegistry(object):
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
-
- 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
+ warnings.warn('registry.override_domain() is deprecated. '
+ 'Use app.add_domain(domain, override=True) instead.',
+ RemovedInSphinx30Warning)
+ self.add_domain(domain, override=True)
+
+ def add_directive_to_domain(self, domain, name, obj, has_content=None, argument_spec=None,
+ override=False, **option_spec):
+ # type: (unicode, unicode, Any, bool, Any, bool, Any) -> None
logger.debug('[app] adding directive to domain: %r',
(domain, name, obj, has_content, argument_spec, option_spec))
if domain not in self.domains:
raise ExtensionError(__('domain %s not yet registered') % domain)
+
directives = self.domain_directives.setdefault(domain, {})
- directives[name] = directive_helper(obj, has_content, argument_spec, **option_spec)
+ if name in directives and not override:
+ raise ExtensionError(__('The %r directive is already registered to %d domain') %
+ (name, domain))
+ if not isclass(obj) or not issubclass(obj, Directive):
+ directives[name] = directive_helper(obj, has_content, argument_spec, **option_spec)
+ else:
+ directives[name] = obj
- def add_role_to_domain(self, domain, name, role):
- # type: (unicode, unicode, Union[RoleFunction, XRefRole]) -> None
+ def add_role_to_domain(self, domain, name, role, override=False):
+ # type: (unicode, unicode, Union[RoleFunction, XRefRole], bool) -> 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)
roles = self.domain_roles.setdefault(domain, {})
+ if name in roles and not override:
+ raise ExtensionError(__('The %r role is already registered to %d domain') %
+ (name, domain))
roles[name] = role
- def add_index_to_domain(self, domain, index):
- # type: (unicode, Type[Index]) -> None
+ def add_index_to_domain(self, domain, index, override=False):
+ # type: (unicode, Type[Index], bool) -> 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)
indices = self.domain_indices.setdefault(domain, [])
+ if index in indices and not override:
+ raise ExtensionError(__('The %r index is already registered to %d domain') %
+ (index.name, 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
+ doc_field_types=[], override=False):
+ # type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List, bool) -> None
logger.debug('[app] adding object type: %r',
(directivename, rolename, indextemplate, parse_node,
ref_nodeclass, objname, doc_field_types))
@@ -178,11 +238,14 @@ class SphinxComponentRegistry(object):
self.add_role_to_domain('std', rolename, XRefRole(innernodeclass=ref_nodeclass))
object_types = self.domain_object_types.setdefault('std', {})
+ if directivename in object_types and not override:
+ raise ExtensionError(__('The %r object_type is already registered') %
+ directivename)
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
+ ref_nodeclass=None, objname='', override=False):
+ # type: (unicode, unicode, unicode, nodes.Node, unicode, bool) -> None
logger.debug('[app] adding crossref type: %r',
(directivename, rolename, indextemplate, ref_nodeclass, objname))
@@ -195,30 +258,62 @@ class SphinxComponentRegistry(object):
self.add_role_to_domain('std', rolename, XRefRole(innernodeclass=ref_nodeclass))
object_types = self.domain_object_types.setdefault('std', {})
+ if directivename in object_types and not override:
+ raise ExtensionError(__('The %r crossref_type is already registered') %
+ directivename)
object_types[directivename] = ObjType(objname or directivename, rolename)
- def add_source_parser(self, suffix, parser):
- # type: (unicode, Type[Parser]) -> None
- logger.debug('[app] adding search source_parser: %r, %r', suffix, parser)
- if suffix in self.source_parsers:
+ def add_source_suffix(self, suffix, filetype, override=False):
+ # type: (unicode, unicode, bool) -> None
+ logger.debug('[app] adding source_suffix: %r, %r', suffix, filetype)
+ if suffix in self.source_suffix and not override:
raise ExtensionError(__('source_parser for %r is already registered') % suffix)
- self.source_parsers[suffix] = parser
-
- def get_source_parser(self, filename):
- # type: (unicode) -> Type[Parser]
- for suffix, parser_class in iteritems(self.source_parsers):
- if filename.endswith(suffix):
- break
else:
- # use special parser for unknown file-extension '*' (if exists)
- parser_class = self.source_parsers.get('*')
-
- if parser_class is None:
- raise SphinxError(__('source_parser for %s not registered') % filename)
+ self.source_suffix[suffix] = filetype
+
+ def add_source_parser(self, *args, **kwargs):
+ # type: (Any, bool) -> None
+ logger.debug('[app] adding search source_parser: %r', args)
+ if len(args) == 1:
+ # new sytle arguments: (source_parser)
+ suffix = None # type: unicode
+ parser = args[0] # type: Type[Parser]
else:
- if isinstance(parser_class, string_types):
- parser_class = import_object(parser_class, 'source parser') # type: ignore
- return parser_class
+ # old style arguments: (suffix, source_parser)
+ warnings.warn('app.add_source_parser() does not support suffix argument. '
+ 'Use app.add_source_suffix() instead.',
+ RemovedInSphinx30Warning)
+ suffix = args[0]
+ parser = args[1]
+
+ if suffix:
+ self.add_source_suffix(suffix, suffix)
+
+ if len(parser.supported) == 0:
+ warnings.warn('Old source_parser has been detected. Please fill Parser.supported '
+ 'attribute: %s' % parser.__name__,
+ RemovedInSphinx30Warning)
+
+ # create a map from filetype to parser
+ for filetype in parser.supported:
+ if filetype in self.source_parsers and not kwargs.get('override'):
+ raise ExtensionError(__('source_parser for %r is already registered') %
+ filetype)
+ else:
+ self.source_parsers[filetype] = parser
+
+ # also maps suffix to parser
+ #
+ # This rescues old styled parsers which does not have ``supported`` filetypes.
+ if suffix:
+ self.source_parsers[suffix] = parser
+
+ def get_source_parser(self, filetype):
+ # type: (unicode) -> Type[Parser]
+ try:
+ return self.source_parsers[filetype]
+ except KeyError:
+ raise SphinxError(__('Source parser for %s not registered') % filetype)
def get_source_parsers(self):
# type: () -> Dict[unicode, Parser]
@@ -232,44 +327,67 @@ class SphinxComponentRegistry(object):
parser.set_application(app)
return parser
- def add_source_input(self, input_class):
- # type: (Type[Input]) -> None
+ def add_source_input(self, input_class, override=False):
+ # type: (Type[Input], bool) -> None
for filetype in input_class.supported:
- if filetype in self.source_inputs:
+ if filetype in self.source_inputs and not override:
raise ExtensionError(__('source_input for %r is already registered') %
filetype)
self.source_inputs[filetype] = input_class
- def get_source_input(self, filename):
+ def get_source_input(self, filetype):
# type: (unicode) -> Type[Input]
- parser = self.get_source_parser(filename)
- for filetype in parser.supported:
- if filetype in self.source_inputs:
- input_class = self.source_inputs[filetype]
- break
- else:
- # use special source_input for unknown file-type '*' (if exists)
- input_class = self.source_inputs.get('*')
-
- if input_class is None:
- raise SphinxError(__('source_input for %s not registered') % filename)
- else:
- return input_class
-
- def add_translator(self, name, translator):
- # type: (unicode, Type[nodes.NodeVisitor]) -> None
- logger.info(bold(__('Change of translator for the %s builder.') % name))
+ try:
+ return self.source_inputs[filetype]
+ except KeyError:
+ try:
+ # use special source_input for unknown filetype
+ return self.source_inputs['*']
+ except KeyError:
+ raise SphinxError(__('source_input for %s not registered') % filetype)
+
+ def add_translator(self, name, translator, override=False):
+ # type: (unicode, Type[nodes.NodeVisitor], bool) -> None
+ logger.debug('[app] Change of translator for the %s builder.' % name)
+ if name in self.translators and not override:
+ raise ExtensionError(__('Translatoro for %r already exists') % name)
self.translators[name] = translator
+ def add_translation_handlers(self, node, **kwargs):
+ # type: (nodes.Node, Any) -> None
+ logger.debug('[app] adding translation_handlers: %r, %r', node, kwargs)
+ for builder_name, handlers in iteritems(kwargs):
+ translation_handlers = self.translation_handlers.setdefault(builder_name, {})
+ try:
+ visit, depart = handlers # unpack once for assertion
+ translation_handlers[node.__name__] = (visit, depart)
+ except ValueError:
+ raise ExtensionError(__('kwargs for add_node() must be a (visit, depart) '
+ 'function tuple: %r=%r') % builder_name, handlers)
+
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
+ def create_translator(self, builder, *args):
+ # type: (Builder, Any) -> nodes.NodeVisitor
translator_class = self.get_translator_class(builder)
- return translator_class(builder, document)
+ assert translator_class, "translator not found for %s" % builder.name
+ translator = translator_class(*args)
+
+ # transplant handlers for custom nodes to translator instance
+ handlers = self.translation_handlers.get(builder.name, None)
+ if handlers is None:
+ # retry with builder.format
+ handlers = self.translation_handlers.get(builder.format, {})
+
+ for name, (visit, depart) in iteritems(handlers):
+ setattr(translator, 'visit_' + name, MethodType(visit, translator))
+ if depart:
+ setattr(translator, 'depart_' + name, MethodType(depart, translator))
+
+ return translator
def add_transform(self, transform):
# type: (Type[Transform]) -> None
@@ -297,6 +415,21 @@ class SphinxComponentRegistry(object):
# type: (Type, Callable[[Any, unicode, Any], Any]) -> None
self.autodoc_attrgettrs[typ] = attrgetter
+ def add_css_files(self, filename, **attributes):
+ self.css_files.append((filename, attributes))
+
+ def add_latex_package(self, name, options):
+ # type: (unicode, unicode) -> None
+ logger.debug('[app] adding latex package: %r', name)
+ self.latex_packages.append((name, options))
+
+ def add_enumerable_node(self, node, figtype, title_getter=None, override=False):
+ # type: (nodes.Node, unicode, TitleGetter, bool) -> None
+ logger.debug('[app] adding enumerable node: (%r, %r, %r)', node, figtype, title_getter)
+ if node in self.enumerable_nodes and not override:
+ raise ExtensionError(__('enumerable_node %r already registered') % node)
+ self.enumerable_nodes[node] = (figtype, title_getter)
+
def load_extension(self, app, extname):
# type: (Sphinx, unicode) -> None
"""Load a Sphinx extension."""
@@ -334,8 +467,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 '
@@ -344,3 +475,37 @@ 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
+ """Merge source_suffix which specified by user and added by extensions."""
+ for suffix, filetype in iteritems(app.registry.source_suffix):
+ if suffix not in app.config.source_suffix:
+ app.config.source_suffix[suffix] = filetype
+ elif app.config.source_suffix[suffix] is None:
+ # filetype is not specified (default filetype).
+ # So it overrides default filetype by extensions setting.
+ app.config.source_suffix[suffix] = filetype
+
+ # copy config.source_suffix to registry
+ app.registry.source_suffix = app.config.source_suffix
+
+
+def setup(app):
+ # type: (Sphinx) -> Dict[unicode, Any]
+ app.connect('builder-inited', merge_source_suffix)
+
+ return {
+ 'version': 'builtin',
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }