You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
10 KiB
284 lines
10 KiB
"""
|
|
sphinx.domains.rst
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
The reStructuredText domain.
|
|
|
|
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
|
:license: BSD, see LICENSE for details.
|
|
"""
|
|
|
|
import re
|
|
from typing import Any, Dict, Iterator, List, Tuple, cast
|
|
|
|
from docutils.nodes import Element
|
|
from docutils.parsers.rst import directives
|
|
|
|
from sphinx import addnodes
|
|
from sphinx.addnodes import desc_signature, pending_xref
|
|
from sphinx.application import Sphinx
|
|
from sphinx.builders import Builder
|
|
from sphinx.directives import ObjectDescription
|
|
from sphinx.domains import Domain, ObjType
|
|
from sphinx.environment import BuildEnvironment
|
|
from sphinx.locale import _, __
|
|
from sphinx.roles import XRefRole
|
|
from sphinx.util import logging
|
|
from sphinx.util.nodes import make_id, make_refnode
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$')
|
|
|
|
|
|
class ReSTMarkup(ObjectDescription):
|
|
"""
|
|
Description of generic reST markup.
|
|
"""
|
|
|
|
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
|
|
node_id = make_id(self.env, self.state.document, self.objtype, name)
|
|
signode['ids'].append(node_id)
|
|
|
|
# Assign old styled node_id not to break old hyperlinks (if possible)
|
|
# Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
|
|
old_node_id = self.make_old_id(name)
|
|
if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']:
|
|
signode['ids'].append(old_node_id)
|
|
|
|
self.state.document.note_explicit_target(signode)
|
|
|
|
domain = cast(ReSTDomain, self.env.get_domain('rst'))
|
|
domain.note_object(self.objtype, name, node_id, location=signode)
|
|
|
|
indextext = self.get_index_text(self.objtype, name)
|
|
if indextext:
|
|
self.indexnode['entries'].append(('single', indextext, node_id, '', None))
|
|
|
|
def get_index_text(self, objectname: str, name: str) -> str:
|
|
return ''
|
|
|
|
def make_old_id(self, name: str) -> str:
|
|
"""Generate old styled node_id for reST markups.
|
|
|
|
.. note:: Old Styled node_id was used until Sphinx-3.0.
|
|
This will be removed in Sphinx-5.0.
|
|
"""
|
|
return self.objtype + '-' + name
|
|
|
|
|
|
def parse_directive(d: str) -> Tuple[str, str]:
|
|
"""Parse a directive signature.
|
|
|
|
Returns (directive, arguments) string tuple. If no arguments are given,
|
|
returns (directive, '').
|
|
"""
|
|
dir = d.strip()
|
|
if not dir.startswith('.'):
|
|
# Assume it is a directive without syntax
|
|
return (dir, '')
|
|
m = dir_sig_re.match(dir)
|
|
if not m:
|
|
return (dir, '')
|
|
parsed_dir, parsed_args = m.groups()
|
|
if parsed_args.strip():
|
|
return (parsed_dir.strip(), ' ' + parsed_args.strip())
|
|
else:
|
|
return (parsed_dir.strip(), '')
|
|
|
|
|
|
class ReSTDirective(ReSTMarkup):
|
|
"""
|
|
Description of a reST directive.
|
|
"""
|
|
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
|
name, args = parse_directive(sig)
|
|
desc_name = '.. %s::' % name
|
|
signode += addnodes.desc_name(desc_name, desc_name)
|
|
if len(args) > 0:
|
|
signode += addnodes.desc_addname(args, args)
|
|
return name
|
|
|
|
def get_index_text(self, objectname: str, name: str) -> str:
|
|
return _('%s (directive)') % name
|
|
|
|
def before_content(self) -> None:
|
|
if self.names:
|
|
directives = self.env.ref_context.setdefault('rst:directives', [])
|
|
directives.append(self.names[0])
|
|
|
|
def after_content(self) -> None:
|
|
directives = self.env.ref_context.setdefault('rst:directives', [])
|
|
if directives:
|
|
directives.pop()
|
|
|
|
|
|
class ReSTDirectiveOption(ReSTMarkup):
|
|
"""
|
|
Description of an option for reST directive.
|
|
"""
|
|
option_spec = ReSTMarkup.option_spec.copy()
|
|
option_spec.update({
|
|
'type': directives.unchanged,
|
|
})
|
|
|
|
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
|
try:
|
|
name, argument = re.split(r'\s*:\s+', sig.strip(), 1)
|
|
except ValueError:
|
|
name, argument = sig, None
|
|
|
|
signode += addnodes.desc_name(':%s:' % name, ':%s:' % name)
|
|
if argument:
|
|
signode += addnodes.desc_annotation(' ' + argument, ' ' + argument)
|
|
if self.options.get('type'):
|
|
text = ' (%s)' % self.options['type']
|
|
signode += addnodes.desc_annotation(text, text)
|
|
return name
|
|
|
|
def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None:
|
|
domain = cast(ReSTDomain, self.env.get_domain('rst'))
|
|
|
|
directive_name = self.current_directive
|
|
if directive_name:
|
|
prefix = '-'.join([self.objtype, directive_name])
|
|
objname = ':'.join([directive_name, name])
|
|
else:
|
|
prefix = self.objtype
|
|
objname = name
|
|
|
|
node_id = make_id(self.env, self.state.document, prefix, name)
|
|
signode['ids'].append(node_id)
|
|
|
|
# Assign old styled node_id not to break old hyperlinks (if possible)
|
|
# Note: Will be removed in Sphinx-5.0 (RemovedInSphinx50Warning)
|
|
old_node_id = self.make_old_id(name)
|
|
if old_node_id not in self.state.document.ids and old_node_id not in signode['ids']:
|
|
signode['ids'].append(old_node_id)
|
|
|
|
self.state.document.note_explicit_target(signode)
|
|
domain.note_object(self.objtype, objname, node_id, location=signode)
|
|
|
|
if directive_name:
|
|
key = name[0].upper()
|
|
pair = [_('%s (directive)') % directive_name,
|
|
_(':%s: (directive option)') % name]
|
|
self.indexnode['entries'].append(('pair', '; '.join(pair), node_id, '', key))
|
|
else:
|
|
key = name[0].upper()
|
|
text = _(':%s: (directive option)') % name
|
|
self.indexnode['entries'].append(('single', text, node_id, '', key))
|
|
|
|
@property
|
|
def current_directive(self) -> str:
|
|
directives = self.env.ref_context.get('rst:directives')
|
|
if directives:
|
|
return directives[-1]
|
|
else:
|
|
return ''
|
|
|
|
def make_old_id(self, name: str) -> str:
|
|
"""Generate old styled node_id for directive options.
|
|
|
|
.. note:: Old Styled node_id was used until Sphinx-3.0.
|
|
This will be removed in Sphinx-5.0.
|
|
"""
|
|
return '-'.join([self.objtype, self.current_directive, name])
|
|
|
|
|
|
class ReSTRole(ReSTMarkup):
|
|
"""
|
|
Description of a reST role.
|
|
"""
|
|
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
|
signode += addnodes.desc_name(':%s:' % sig, ':%s:' % sig)
|
|
return sig
|
|
|
|
def get_index_text(self, objectname: str, name: str) -> str:
|
|
return _('%s (role)') % name
|
|
|
|
|
|
class ReSTDomain(Domain):
|
|
"""ReStructuredText domain."""
|
|
name = 'rst'
|
|
label = 'reStructuredText'
|
|
|
|
object_types = {
|
|
'directive': ObjType(_('directive'), 'dir'),
|
|
'directive:option': ObjType(_('directive-option'), 'dir'),
|
|
'role': ObjType(_('role'), 'role'),
|
|
}
|
|
directives = {
|
|
'directive': ReSTDirective,
|
|
'directive:option': ReSTDirectiveOption,
|
|
'role': ReSTRole,
|
|
}
|
|
roles = {
|
|
'dir': XRefRole(),
|
|
'role': XRefRole(),
|
|
}
|
|
initial_data = {
|
|
'objects': {}, # fullname -> docname, objtype
|
|
} # type: Dict[str, Dict[Tuple[str, str], str]]
|
|
|
|
@property
|
|
def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
|
|
return self.data.setdefault('objects', {}) # (objtype, fullname) -> (docname, node_id)
|
|
|
|
def note_object(self, objtype: str, name: str, node_id: str, location: Any = None) -> None:
|
|
if (objtype, name) in self.objects:
|
|
docname, node_id = self.objects[objtype, name]
|
|
logger.warning(__('duplicate description of %s %s, other instance in %s') %
|
|
(objtype, name, docname), location=location)
|
|
|
|
self.objects[objtype, name] = (self.env.docname, node_id)
|
|
|
|
def clear_doc(self, docname: str) -> None:
|
|
for (typ, name), (doc, node_id) in list(self.objects.items()):
|
|
if doc == docname:
|
|
del self.objects[typ, name]
|
|
|
|
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
|
|
# XXX check duplicates
|
|
for (typ, name), (doc, node_id) in otherdata['objects'].items():
|
|
if doc in docnames:
|
|
self.objects[typ, name] = (doc, node_id)
|
|
|
|
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
|
|
typ: str, target: str, node: pending_xref, contnode: Element
|
|
) -> Element:
|
|
objtypes = self.objtypes_for_role(typ)
|
|
for objtype in objtypes:
|
|
todocname, node_id = self.objects.get((objtype, target), (None, None))
|
|
if todocname:
|
|
return make_refnode(builder, fromdocname, todocname, node_id,
|
|
contnode, target + ' ' + objtype)
|
|
return None
|
|
|
|
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
|
|
target: str, node: pending_xref, contnode: Element
|
|
) -> List[Tuple[str, Element]]:
|
|
results = [] # type: List[Tuple[str, Element]]
|
|
for objtype in self.object_types:
|
|
todocname, node_id = self.objects.get((objtype, target), (None, None))
|
|
if todocname:
|
|
results.append(('rst:' + self.role_for_objtype(objtype),
|
|
make_refnode(builder, fromdocname, todocname, node_id,
|
|
contnode, target + ' ' + objtype)))
|
|
return results
|
|
|
|
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
|
|
for (typ, name), (docname, node_id) in self.data['objects'].items():
|
|
yield name, name, typ, docname, node_id, 1
|
|
|
|
|
|
def setup(app: Sphinx) -> Dict[str, Any]:
|
|
app.add_domain(ReSTDomain)
|
|
|
|
return {
|
|
'version': 'builtin',
|
|
'env_version': 2,
|
|
'parallel_read_safe': True,
|
|
'parallel_write_safe': True,
|
|
}
|