Source code for sphinx.domains

"""
    sphinx.domains
    ~~~~~~~~~~~~~~

    Support for domains, which are groupings of description directives
    and roles describing e.g. constructs of one programming language.

    :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import copy
from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, Union
from typing import cast

from docutils import nodes
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst.states import Inliner

from sphinx.addnodes import pending_xref
from sphinx.errors import SphinxError
from sphinx.locale import _
from sphinx.roles import XRefRole
from sphinx.util.typing import RoleFunction

if False:
    # For type annotation
    from typing import Type  # for python3.5.1
    from sphinx.builders import Builder
    from sphinx.environment import BuildEnvironment


[docs]class ObjType: """ An ObjType is the description for a type of object that a domain can document. In the object_types attribute of Domain subclasses, object type names are mapped to instances of this class. Constructor arguments: - *lname*: localized name of the type (do not include domain name) - *roles*: all the roles that can refer to an object of this type - *attrs*: object attributes -- currently only "searchprio" is known, which defines the object's priority in the full-text search index, see :meth:`Domain.get_objects()`. """ known_attrs = { 'searchprio': 1, } def __init__(self, lname: str, *roles: Any, **attrs: Any) -> None: self.lname = lname self.roles = roles # type: Tuple self.attrs = self.known_attrs.copy() # type: Dict self.attrs.update(attrs)
IndexEntry = NamedTuple('IndexEntry', [('name', str), ('subtype', int), ('docname', str), ('anchor', str), ('extra', str), ('qualifier', str), ('descr', str)])
[docs]class Index: """ An Index is the description for a domain-specific index. To add an index to a domain, subclass Index, overriding the three name attributes: * `name` is an identifier used for generating file names. It is also used for a hyperlink target for the index. Therefore, users can refer the index page using ``ref`` role and a string which is combined domain name and ``name`` attribute (ex. ``:ref:`py-modindex```). * `localname` is the section title for the index. * `shortname` is a short name for the index, for use in the relation bar in HTML output. Can be empty to disable entries in the relation bar. and providing a :meth:`generate()` method. Then, add the index class to your domain's `indices` list. Extensions can add indices to existing domains using :meth:`~sphinx.application.Sphinx.add_index_to_domain()`. .. versionchanged:: 3.0 Index pages can be referred by domain name and index name via :rst:role:`ref` role. """ name = None # type: str localname = None # type: str shortname = None # type: str def __init__(self, domain: "Domain") -> None: if self.name is None or self.localname is None: raise SphinxError('Index subclass %s has no valid name or localname' % self.__class__.__name__) self.domain = domain
[docs] def generate(self, docnames: Iterable[str] = None ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: """Get entries for the index. If ``docnames`` is given, restrict to entries referring to these docnames. The return value is a tuple of ``(content, collapse)``: ``collapse`` A boolean that determines if sub-entries should start collapsed (for output formats that support collapsing sub-entries). ``content``: A sequence of ``(letter, entries)`` tuples, where ``letter`` is the "heading" for the given ``entries``, usually the starting letter, and ``entries`` is a sequence of single entries. Each entry is a sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``. The items in this sequence have the following meaning: ``name`` The name of the index entry to be displayed. ``subtype`` The sub-entry related type. One of: ``0`` A normal entry. ``1`` An entry with sub-entries. ``2`` A sub-entry. ``docname`` *docname* where the entry is located. ``anchor`` Anchor for the entry within ``docname`` ``extra`` Extra info for the entry. ``qualifier`` Qualifier for the description. ``descr`` Description for the entry. Qualifier and description are not rendered for some output formats such as LaTeX. """ raise NotImplementedError
[docs]class Domain: """ A Domain is meant to be a group of "object" description directives for objects of a similar nature, and corresponding roles to create references to them. Examples would be Python modules, classes, functions etc., elements of a templating language, Sphinx roles and directives, etc. Each domain has a separate storage for information about existing objects and how to reference them in `self.data`, which must be a dictionary. It also must implement several functions that expose the object information in a uniform way to parts of Sphinx that allow the user to reference or search for objects in a domain-agnostic way. About `self.data`: since all object and cross-referencing information is stored on a BuildEnvironment instance, the `domain.data` object is also stored in the `env.domaindata` dict under the key `domain.name`. Before the build process starts, every active domain is instantiated and given the environment object; the `domaindata` dict must then either be nonexistent or a dictionary whose 'version' key is equal to the domain class' :attr:`data_version` attribute. Otherwise, `OSError` is raised and the pickled environment is discarded. """ #: domain name: should be short, but unique name = '' #: domain label: longer, more descriptive (used in messages) label = '' #: type (usually directive) name -> ObjType instance object_types = {} # type: Dict[str, ObjType] #: directive name -> directive class directives = {} # type: Dict[str, Any] #: role name -> role callable roles = {} # type: Dict[str, Union[RoleFunction, XRefRole]] #: a list of Index subclasses indices = [] # type: List[Type[Index]] #: role name -> a warning message if reference is missing dangling_warnings = {} # type: Dict[str, str] #: node_class -> (enum_node_type, title_getter) enumerable_nodes = {} # type: Dict[Type[Node], Tuple[str, Callable]] #: data value for a fresh environment initial_data = {} # type: Dict #: data value data = None # type: Dict #: data version, bump this when the format of `self.data` changes data_version = 0 def __init__(self, env: "BuildEnvironment") -> None: self.env = env # type: BuildEnvironment self._role_cache = {} # type: Dict[str, Callable] self._directive_cache = {} # type: Dict[str, Callable] self._role2type = {} # type: Dict[str, List[str]] self._type2role = {} # type: Dict[str, str] # 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) new_data['version'] = self.data_version self.data = env.domaindata[self.name] = new_data else: self.data = env.domaindata[self.name] if self.data['version'] != self.data_version: raise OSError('data of %r domain out of date' % self.label) for name, obj in self.object_types.items(): for rolename in obj.roles: self._role2type.setdefault(rolename, []).append(name) self._type2role[name] = obj.roles[0] if obj.roles else '' self.objtypes_for_role = self._role2type.get # type: Callable[[str], List[str]] self.role_for_objtype = self._type2role.get # type: Callable[[str], str]
[docs] def setup(self) -> None: """Set up domain object.""" from sphinx.domains.std import StandardDomain # Add special hyperlink target for index pages (ex. py-modindex) std = cast(StandardDomain, self.env.get_domain('std')) for index in self.indices: if index.name and index.localname: docname = "%s-%s" % (self.name, index.name) std.note_hyperlink_target(docname, docname, '', index.localname)
[docs] def add_object_type(self, name: str, objtype: 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)
[docs] def role(self, name: str) -> RoleFunction: """Return a role adapter function that always gives the registered role its full name ('domain:name') as the first argument. """ if name in self._role_cache: return self._role_cache[name] if name not in self.roles: return None fullname = '%s:%s' % (self.name, name) def role_adapter(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner, options: Dict = {}, content: List[str] = [] ) -> Tuple[List[Node], List[system_message]]: return self.roles[name](fullname, rawtext, text, lineno, inliner, options, content) self._role_cache[name] = role_adapter return role_adapter
[docs] def directive(self, name: str) -> Callable: """Return a directive adapter class that always gives the registered directive its full name ('domain:name') as ``self.name``. """ if name in self._directive_cache: return self._directive_cache[name] if name not in self.directives: return None fullname = '%s:%s' % (self.name, name) BaseDirective = self.directives[name] class DirectiveAdapter(BaseDirective): # type: ignore def run(self) -> List[Node]: self.name = fullname return super().run() self._directive_cache[name] = DirectiveAdapter return DirectiveAdapter
# methods that should be overwritten
[docs] def clear_doc(self, docname: str) -> None: """Remove traces of a document in the domain-specific inventories.""" pass
[docs] def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: """Merge in data regarding *docnames* from a different domaindata inventory (coming from a subprocess in parallel builds). """ raise NotImplementedError('merge_domaindata must be implemented in %s ' 'to be able to do parallel builds!' % self.__class__)
[docs] def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: """Process a document after it is read by the environment.""" pass
[docs] def check_consistency(self) -> None: """Do consistency checks (**experimental**).""" pass
[docs] def process_field_xref(self, pnode: pending_xref) -> None: """Process a pending xref created in a doc field. For example, attach information about the current scope. """ pass
[docs] def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element ) -> Element: """Resolve the pending_xref *node* with the given *typ* and *target*. This method should return a new node, to replace the xref node, containing the *contnode* which is the markup content of the cross-reference. If no resolution can be found, None can be returned; the xref node will then given to the :event:`missing-reference` event, and if that yields no resolution, replaced by *contnode*. The method can also raise :exc:`sphinx.environment.NoUri` to suppress the :event:`missing-reference` event being emitted. """ pass
[docs] def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", target: str, node: pending_xref, contnode: Element ) -> List[Tuple[str, Element]]: """Resolve the pending_xref *node* with the given *target*. The reference comes from an "any" or similar role, which means that we don't know the type. Otherwise, the arguments are the same as for :meth:`resolve_xref`. The method must return a list (potentially empty) of tuples ``('domain:role', newnode)``, where ``'domain:role'`` is the name of a role that could have created the same reference, e.g. ``'py:func'``. ``newnode`` is what :meth:`resolve_xref` would return. .. versionadded:: 1.3 """ raise NotImplementedError
[docs] def get_objects(self) -> Iterable[Tuple[str, str, str, str, str, int]]: """Return an iterable of "object descriptions". Object descriptions are tuples with six items: ``name`` Fully qualified name. ``dispname`` Name to display when searching/linking. ``type`` Object type, a key in ``self.object_types``. ``docname`` The document where it is to be found. ``anchor`` The anchor name for the object. ``priority`` How "important" the object is (determines placement in search results). One of: ``1`` Default priority (placed before full-text matches). ``0`` Object is important (placed before default-priority objects). ``2`` Object is unimportant (placed after full-text matches). ``-1`` Object should not show up in search at all. """ return []
[docs] def get_type_name(self, type: ObjType, primary: bool = False) -> str: """Return full name for given ObjType.""" if primary: return type.lname return _('%s %s') % (self.label, type.lname)
[docs] def get_enumerable_node_type(self, node: Node) -> str: """Get type of enumerable nodes (experimental).""" enum_node_type, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return enum_node_type
[docs] def get_full_qualified_name(self, node: Element) -> str: """Return full qualified name for given node.""" return None