diff options
Diffstat (limited to 'sphinx/registry.py')
-rw-r--r-- | sphinx/registry.py | 319 |
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, + } |