diff --git a/sphinx/application.py b/sphinx/application.py index ddb14ad18..b6fd7feef 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -33,7 +33,6 @@ from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.environment import BuildEnvironment from sphinx.events import EventManager from sphinx.extension import verify_required_extensions -from sphinx.io import SphinxStandaloneReader from sphinx.locale import __ from sphinx.registry import SphinxComponentRegistry from sphinx.util import pycompat # noqa: F401 @@ -54,7 +53,9 @@ if False: from sphinx.domains import Domain, Index # NOQA from sphinx.environment.collectors import EnvironmentCollector # NOQA from sphinx.extension import Extension # NOQA + from sphinx.roles import XRefRole # NOQA from sphinx.theming import Theme # NOQA + from sphinx.util.typing import RoleFunction # NOQA builtin_extensions = ( 'sphinx.builders.applehelp', @@ -120,7 +121,6 @@ class Sphinx(object): 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] self.srcdir = srcdir @@ -444,7 +444,6 @@ class Sphinx(object): def add_builder(self, builder): # type: (Type[Builder]) -> None - logger.debug('[app] adding builder: %r', builder) self.registry.add_builder(builder) def add_config_value(self, name, default, rebuild, types=()): @@ -464,7 +463,6 @@ class Sphinx(object): def set_translator(self, name, translator_class): # type: (unicode, Type[nodes.NodeVisitor]) -> None - logger.info(bold(__('Change of translator for the %s builder.') % name)) self.registry.add_translator(name, translator_class) def add_node(self, node, **kwds): @@ -553,39 +551,30 @@ class Sphinx(object): def add_domain(self, domain): # type: (Type[Domain]) -> None - logger.debug('[app] adding domain: %r', domain) self.registry.add_domain(domain) def override_domain(self, domain): # type: (Type[Domain]) -> None - logger.debug('[app] overriding domain: %r', domain) self.registry.override_domain(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 - logger.debug('[app] adding directive to domain: %r', - (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)) + # type: (unicode, unicode, Union[RoleFunction, XRefRole]) -> None 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)) self.registry.add_index_to_domain(domain, 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 - logger.debug('[app] adding object type: %r', - (directivename, rolename, indextemplate, parse_node, - ref_nodeclass, objname, doc_field_types)) self.registry.add_object_type(directivename, rolename, indextemplate, parse_node, ref_nodeclass, objname, doc_field_types) @@ -602,21 +591,16 @@ class Sphinx(object): def add_crossref_type(self, directivename, rolename, indextemplate='', ref_nodeclass=None, objname=''): # type: (unicode, unicode, unicode, nodes.Node, unicode) -> None - logger.debug('[app] adding crossref type: %r', - (directivename, rolename, indextemplate, ref_nodeclass, - objname)) self.registry.add_crossref_type(directivename, rolename, indextemplate, ref_nodeclass, objname) def add_transform(self, transform): # type: (Type[Transform]) -> None - logger.debug('[app] adding transform: %r', transform) - SphinxStandaloneReader.transforms.append(transform) + self.registry.add_transform(transform) def add_post_transform(self, transform): # type: (Type[Transform]) -> None - logger.debug('[app] adding post transform: %r', transform) - self.post_transforms.append(transform) + self.registry.add_post_transform(transform) def add_javascript(self, filename): # type: (unicode) -> None @@ -677,7 +661,6 @@ 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) self.registry.add_source_parser(suffix, parser) def add_env_collector(self, collector): diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 6edc3cdb0..1c80d3a42 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -141,7 +141,7 @@ class Domain(object): #: domain label: longer, more descriptive (used in messages) label = '' #: type (usually directive) name -> ObjType instance - object_types = {} # type: Dict[unicode, Any] + object_types = {} # type: Dict[unicode, ObjType] #: directive name -> directive class directives = {} # type: Dict[unicode, Any] #: role name -> role callable @@ -161,6 +161,17 @@ class Domain(object): def __init__(self, env): # type: (BuildEnvironment) -> None self.env = env # type: BuildEnvironment + self._role_cache = {} # type: Dict[unicode, Callable] + self._directive_cache = {} # type: Dict[unicode, Callable] + self._role2type = {} # type: Dict[unicode, List[unicode]] + self._type2role = {} # type: Dict[unicode, unicode] + + # convert class variables to instance one (to enhance through API) + self.object_types = dict(self.object_types) + self.directives = dict(self.directives) + self.roles = dict(self.roles) + self.indices = list(self.indices) + if self.name not in env.domaindata: assert isinstance(self.initial_data, dict) new_data = copy.deepcopy(self.initial_data) @@ -170,10 +181,6 @@ class Domain(object): self.data = env.domaindata[self.name] if self.data['version'] != self.data_version: raise IOError('data of %r domain out of date' % self.label) - self._role_cache = {} # type: Dict[unicode, Callable] - self._directive_cache = {} # type: Dict[unicode, Callable] - self._role2type = {} # type: Dict[unicode, List[unicode]] - self._type2role = {} # type: Dict[unicode, unicode] for name, obj in iteritems(self.object_types): for rolename in obj.roles: self._role2type.setdefault(rolename, []).append(name) @@ -181,6 +188,18 @@ class Domain(object): self.objtypes_for_role = self._role2type.get # type: Callable[[unicode], List[unicode]] # NOQA self.role_for_objtype = self._type2role.get # type: Callable[[unicode], unicode] + def add_object_type(self, name, objtype): + # type: (unicode, ObjType) -> None + """Add an object type.""" + self.object_types[name] = objtype + if objtype.roles: + self._type2role[name] = objtype.roles[0] + else: + self._type2role[name] = '' + + for role in objtype.roles: + self._role2type.setdefault(role, []).append(name) + def role(self, name): # type: (unicode) -> Callable """Return a role adapter function that always gives the registered diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 5ebf9d4d5..b4c40b608 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -878,7 +878,7 @@ class BuildEnvironment(object): transformer = SphinxTransformer(doctree) transformer.set_environment(self) - transformer.add_transforms(self.app.post_transforms) + transformer.add_transforms(self.app.registry.get_post_transforms()) transformer.apply_transforms() finally: self.temp_data = backup diff --git a/sphinx/io.py b/sphinx/io.py index 8a41069db..804932863 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -80,7 +80,13 @@ class SphinxStandaloneReader(SphinxBaseReader): Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline, PreserveTranslatableMessages, FilterSystemMessages, - RefOnlyBulletListTransform, UnreferencedFootnotesDetector] + RefOnlyBulletListTransform, UnreferencedFootnotesDetector + ] # type: List[Transform] + + def __init__(self, app, *args, **kwargs): + # type: (Sphinx, Any, Any) -> None + self.transforms = self.transforms + app.registry.get_transforms() + SphinxBaseReader.__init__(self, *args, **kwargs) # type: ignore class SphinxI18nReader(SphinxBaseReader): @@ -259,7 +265,7 @@ def read_doc(app, env, filename): # type: (Sphinx, BuildEnvironment, unicode) -> nodes.document """Parse a document and convert to doctree.""" input_class = app.registry.get_source_input(filename) - reader = SphinxStandaloneReader() + reader = SphinxStandaloneReader(app) source = input_class(app, env, source=None, source_path=filename, encoding=env.config.source_encoding) parser = app.registry.create_source_parser(app, filename) diff --git a/sphinx/registry.py b/sphinx/registry.py index b627f23af..6ec966a6a 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -24,18 +24,21 @@ from sphinx.parsers import Parser as SphinxParser from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util import import_object +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 # NOQA + from typing import Any, Callable, Dict, Iterator, List, Type, Union # NOQA from docutils import nodes # NOQA from docutils.io import Input # NOQA from docutils.parsers import Parser # NOQA + from docutils.transform import Transform # 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 + from sphinx.util.typing import RoleFunction # NOQA logger = logging.getLogger(__name__) @@ -48,14 +51,21 @@ EXTENSION_BLACKLIST = { 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.source_inputs = {} # type: Dict[unicode, Input] - self.translators = {} # type: Dict[unicode, nodes.NodeVisitor] + self.builders = {} # type: Dict[unicode, Type[Builder]] + self.domains = {} # type: Dict[unicode, Type[Domain]] + self.domain_directives = {} # type: Dict[unicode, Dict[unicode, Any]] + self.domain_indices = {} # type: Dict[unicode, List[Type[Index]]] + self.domain_object_types = {} # type: Dict[unicode, Dict[unicode, ObjType]] + self.domain_roles = {} # type: Dict[unicode, Dict[unicode, Union[RoleFunction, XRefRole]]] # NOQA + self.post_transforms = [] # type: List[Type[Transform]] + self.source_parsers = {} # type: Dict[unicode, Parser] + self.source_inputs = {} # type: Dict[unicode, Input] + self.translators = {} # type: Dict[unicode, nodes.NodeVisitor] + self.transforms = [] # type: List[Type[Transform]] 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.builders: @@ -87,6 +97,7 @@ class SphinxComponentRegistry(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 @@ -98,10 +109,20 @@ class SphinxComponentRegistry(object): def create_domains(self, env): # type: (BuildEnvironment) -> Iterator[Domain] for DomainClass in itervalues(self.domains): - yield DomainClass(env) + domain = DomainClass(env) + + # transplant components added by extensions + domain.directives.update(self.domain_directives.get(domain.name, {})) + domain.roles.update(self.domain_roles.get(domain.name, {})) + domain.indices.extend(self.domain_indices.get(domain.name, [])) + for name, objtype in iteritems(self.domain_object_types.get(domain.name, {})): + domain.add_object_type(name, objtype) + + yield 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]): @@ -112,27 +133,37 @@ class SphinxComponentRegistry(object): 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 + 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) - directive = directive_helper(obj, has_content, argument_spec, **option_spec) - self.domains[domain].directives[name] = directive + directives = self.domain_directives.setdefault(domain, {}) + directives[name] = directive_helper(obj, has_content, argument_spec, **option_spec) def add_role_to_domain(self, domain, name, role): - # type: (unicode, unicode, Any) -> None + # type: (unicode, unicode, Union[RoleFunction, XRefRole]) -> 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 + roles = self.domain_roles.setdefault(domain, {}) + roles[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) + indices = self.domain_indices.setdefault(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 + logger.debug('[app] adding object type: %r', + (directivename, rolename, indextemplate, parse_node, + ref_nodeclass, objname, doc_field_types)) + # create a subclass of GenericObject as the new directive directive = type(directivename, # type: ignore (GenericObject, object), @@ -140,26 +171,32 @@ class SphinxComponentRegistry(object): 'parse_node': staticmethod(parse_node), '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) + self.add_directive_to_domain('std', directivename, directive) + self.add_role_to_domain('std', rolename, XRefRole(innernodeclass=ref_nodeclass)) + + object_types = self.domain_object_types.setdefault('std', {}) + 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 + logger.debug('[app] adding crossref type: %r', + (directivename, rolename, indextemplate, ref_nodeclass, objname)) + # 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) + self.add_directive_to_domain('std', directivename, directive) + self.add_role_to_domain('std', rolename, XRefRole(innernodeclass=ref_nodeclass)) + + object_types = self.domain_object_types.setdefault('std', {}) + 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: raise ExtensionError(__('source_parser for %r is already registered') % suffix) self.source_parsers[suffix] = parser @@ -216,6 +253,7 @@ class SphinxComponentRegistry(object): def add_translator(self, name, translator): # type: (unicode, Type[nodes.NodeVisitor]) -> None + logger.info(bold(__('Change of translator for the %s builder.') % name)) self.translators[name] = translator def get_translator_class(self, builder): @@ -228,6 +266,24 @@ class SphinxComponentRegistry(object): translator_class = self.get_translator_class(builder) return translator_class(builder, document) + def add_transform(self, transform): + # type: (Type[Transform]) -> None + logger.debug('[app] adding transform: %r', transform) + self.transforms.append(transform) + + def get_transforms(self): + # type: () -> List[Type[Transform]] + return self.transforms + + def add_post_transform(self, transform): + # type: (Type[Transform]) -> None + logger.debug('[app] adding post transform: %r', transform) + self.post_transforms.append(transform) + + def get_post_transforms(self): + # type: () -> List[Type[Transform]] + return self.post_transforms + def load_extension(self, app, extname): # type: (Sphinx, unicode) -> None """Load a Sphinx extension."""