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.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/sphinx/domains/std.py

1138 lines
46 KiB

"""
sphinx.domains.std
~~~~~~~~~~~~~~~~~~
The standard domain.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import unicodedata
import warnings
from copy import copy
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
from docutils import nodes
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import docname_join, logging, ws_re
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import clean_astext, make_id, make_refnode
from sphinx.util.typing import RoleFunction
if False:
# For type annotation
from typing import Type # for python3.5.1
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
logger = logging.getLogger(__name__)
# RE for option descriptions
option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
# RE for grammar tokens
token_re = re.compile(r'`(\w+)`', re.U)
class GenericObject(ObjectDescription):
"""
A generic x-ref directive registered with Sphinx.add_object_type().
"""
indextemplate = ''
parse_node = None # type: Callable[[GenericObject, BuildEnvironment, str, desc_signature], str] # NOQA
def handle_signature(self, sig: str, signode: desc_signature) -> str:
if self.parse_node:
name = self.parse_node(self.env, sig, signode)
else:
signode.clear()
signode += addnodes.desc_name(sig, sig)
# normalize whitespace like XRefRole does
name = ws_re.sub(' ', sig)
return name
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)
if self.indextemplate:
colon = self.indextemplate.find(':')
if colon != -1:
indextype = self.indextemplate[:colon].strip()
indexentry = self.indextemplate[colon + 1:].strip() % (name,)
else:
indextype = 'single'
indexentry = self.indextemplate % (name,)
self.indexnode['entries'].append((indextype, indexentry, node_id, '', None))
std = cast(StandardDomain, self.env.get_domain('std'))
std.note_object(self.objtype, name, node_id, location=signode)
def make_old_id(self, name: str) -> str:
"""Generate old styled node_id for generic objects.
.. note:: Old Styled node_id was used until Sphinx-3.0.
This will be removed in Sphinx-5.0.
"""
return self.objtype + '-' + name
class EnvVar(GenericObject):
indextemplate = _('environment variable; %s')
class EnvVarXRefRole(XRefRole):
"""
Cross-referencing role for environment variables (adds an index entry).
"""
def result_nodes(self, document: nodes.document, env: "BuildEnvironment", node: Element,
is_ref: bool) -> Tuple[List[Node], List[system_message]]:
if not is_ref:
return [node], []
varname = node['reftarget']
tgtid = 'index-%s' % env.new_serialno('index')
indexnode = addnodes.index()
indexnode['entries'] = [
('single', varname, tgtid, '', None),
('single', _('environment variable; %s') % varname, tgtid, '', None)
]
targetnode = nodes.target('', '', ids=[tgtid])
document.note_explicit_target(targetnode)
return [indexnode, targetnode, node], []
class Target(SphinxDirective):
"""
Generic target for user-defined cross-reference types.
"""
indextemplate = ''
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self) -> List[Node]:
# normalize whitespace in fullname like XRefRole does
fullname = ws_re.sub(' ', self.arguments[0].strip())
node_id = make_id(self.env, self.state.document, self.name, fullname)
node = nodes.target('', '', ids=[node_id])
self.set_source_info(node)
# 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(fullname)
if old_node_id not in self.state.document.ids and old_node_id not in node['ids']:
node['ids'].append(old_node_id)
self.state.document.note_explicit_target(node)
ret = [node] # type: List[Node]
if self.indextemplate:
indexentry = self.indextemplate % (fullname,)
indextype = 'single'
colon = indexentry.find(':')
if colon != -1:
indextype = indexentry[:colon].strip()
indexentry = indexentry[colon + 1:].strip()
inode = addnodes.index(entries=[(indextype, indexentry, node_id, '', None)])
ret.insert(0, inode)
name = self.name
if ':' in self.name:
_, name = self.name.split(':', 1)
std = cast(StandardDomain, self.env.get_domain('std'))
std.note_object(name, fullname, node_id, location=node)
return ret
def make_old_id(self, name: str) -> str:
"""Generate old styled node_id for targets.
.. note:: Old Styled node_id was used until Sphinx-3.0.
This will be removed in Sphinx-5.0.
"""
return self.name + '-' + name
class Cmdoption(ObjectDescription):
"""
Description of a command-line option (.. option).
"""
def handle_signature(self, sig: str, signode: desc_signature) -> str:
"""Transform an option description into RST nodes."""
count = 0
firstname = ''
for potential_option in sig.split(', '):
potential_option = potential_option.strip()
m = option_desc_re.match(potential_option)
if not m:
logger.warning(__('Malformed option description %r, should '
'look like "opt", "-opt args", "--opt args", '
'"/opt args" or "+opt args"'), potential_option,
location=signode)
continue
optname, args = m.groups()
if optname.endswith('[') and args.endswith(']'):
# optional value surrounded by brackets (ex. foo[=bar])
optname = optname[:-1]
args = '[' + args
if count:
signode += addnodes.desc_addname(', ', ', ')
signode += addnodes.desc_name(optname, optname)
signode += addnodes.desc_addname(args, args)
if not count:
firstname = optname
signode['allnames'] = [optname]
else:
signode['allnames'].append(optname)
count += 1
if not firstname:
raise ValueError
return firstname
def add_target_and_index(self, firstname: str, sig: str, signode: desc_signature) -> None:
currprogram = self.env.ref_context.get('std:program')
for optname in signode.get('allnames', []):
prefixes = ['cmdoption']
if currprogram:
prefixes.append(currprogram)
if not optname.startswith(('-', '/')):
prefixes.append('arg')
prefix = '-'.join(prefixes)
node_id = make_id(self.env, self.state.document, prefix, optname)
signode['ids'].append(node_id)
old_node_id = self.make_old_id(prefix, optname)
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(StandardDomain, self.env.get_domain('std'))
for optname in signode.get('allnames', []):
domain.add_program_option(currprogram, optname,
self.env.docname, signode['ids'][0])
# create an index entry
if currprogram:
descr = _('%s command line option') % currprogram
else:
descr = _('command line option')
for option in sig.split(', '):
entry = '; '.join([descr, option])
self.indexnode['entries'].append(('pair', entry, signode['ids'][0], '', None))
def make_old_id(self, prefix: str, optname: str) -> str:
"""Generate old styled node_id for cmdoption.
.. note:: Old Styled node_id was used until Sphinx-3.0.
This will be removed in Sphinx-5.0.
"""
return nodes.make_id(prefix + '-' + optname)
class Program(SphinxDirective):
"""
Directive to name the program for which options are documented.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self) -> List[Node]:
program = ws_re.sub('-', self.arguments[0].strip())
if program == 'None':
self.env.ref_context.pop('std:program', None)
else:
self.env.ref_context['std:program'] = program
return []
class OptionXRefRole(XRefRole):
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
title: str, target: str) -> Tuple[str, str]:
refnode['std:program'] = env.ref_context.get('std:program')
return title, target
def split_term_classifiers(line: str) -> List[Optional[str]]:
# split line into a term and classifiers. if no classifier, None is used..
parts = re.split(' +: +', line) + [None]
return parts
def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index_key: str,
source: str, lineno: int, node_id: str = None,
document: nodes.document = None) -> nodes.term:
# get a text-only representation of the term and register it
# as a cross-reference target
term = nodes.term('', '', *textnodes)
term.source = source
term.line = lineno
termtext = term.astext()
if node_id:
# node_id is given from outside (mainly i18n module), use it forcedly
term['ids'].append(node_id)
elif document:
node_id = make_id(env, document, 'term', termtext)
term['ids'].append(node_id)
document.note_explicit_target(term)
else:
warnings.warn('make_glossary_term() expects document is passed as an argument.',
RemovedInSphinx40Warning, stacklevel=2)
gloss_entries = env.temp_data.setdefault('gloss_entries', set())
node_id = nodes.make_id('term-' + termtext)
if node_id == 'term':
# "term" is not good for node_id. Generate it by sequence number instead.
node_id = 'term-%d' % env.new_serialno('glossary')
while node_id in gloss_entries:
node_id = 'term-%d' % env.new_serialno('glossary')
gloss_entries.add(node_id)
term['ids'].append(node_id)
std = cast(StandardDomain, env.get_domain('std'))
std.note_object('term', termtext, node_id, location=term)
# add an index entry too
indexnode = addnodes.index()
indexnode['entries'] = [('single', termtext, node_id, 'main', index_key)]
indexnode.source, indexnode.line = term.source, term.line
term.append(indexnode)
return term
class Glossary(SphinxDirective):
"""
Directive to create a glossary with cross-reference targets for :term:
roles.
"""
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'sorted': directives.flag,
}
def run(self) -> List[Node]:
node = addnodes.glossary()
node.document = self.state.document
# This directive implements a custom format of the reST definition list
# that allows multiple lines of terms before the definition. This is
# easy to parse since we know that the contents of the glossary *must
# be* a definition list.
# first, collect single entries
entries = [] # type: List[Tuple[List[Tuple[str, str, int]], StringList]]
in_definition = True
in_comment = False
was_empty = True
messages = [] # type: List[Node]
for line, (source, lineno) in zip(self.content, self.content.items):
# empty line -> add to last definition
if not line:
if in_definition and entries:
entries[-1][1].append('', source, lineno)
was_empty = True
continue
# unindented line -> a term
if line and not line[0].isspace():
# enable comments
if line.startswith('.. '):
in_comment = True
continue
else:
in_comment = False
# first term of definition
if in_definition:
if not was_empty:
messages.append(self.state.reporter.warning(
_('glossary term must be preceded by empty line'),
source=source, line=lineno))
entries.append(([(line, source, lineno)], StringList()))
in_definition = False
# second term and following
else:
if was_empty:
messages.append(self.state.reporter.warning(
_('glossary terms must not be separated by empty lines'),
source=source, line=lineno))
if entries:
entries[-1][0].append((line, source, lineno))
else:
messages.append(self.state.reporter.warning(
_('glossary seems to be misformatted, check indentation'),
source=source, line=lineno))
elif in_comment:
pass
else:
if not in_definition:
# first line of definition, determines indentation
in_definition = True
indent_len = len(line) - len(line.lstrip())
if entries:
entries[-1][1].append(line[indent_len:], source, lineno)
else:
messages.append(self.state.reporter.warning(
_('glossary seems to be misformatted, check indentation'),
source=source, line=lineno))
was_empty = False
# now, parse all the entries into a big definition list
items = []
for terms, definition in entries:
termtexts = [] # type: List[str]
termnodes = [] # type: List[Node]
system_messages = [] # type: List[Node]
for line, source, lineno in terms:
parts = split_term_classifiers(line)
# parse the term with inline markup
# classifiers (parts[1:]) will not be shown on doctree
textnodes, sysmsg = self.state.inline_text(parts[0], lineno)
# use first classifier as a index key
term = make_glossary_term(self.env, textnodes, parts[1], source, lineno,
document=self.state.document)
term.rawsource = line
system_messages.extend(sysmsg)
termtexts.append(term.astext())
termnodes.append(term)
termnodes.extend(system_messages)
defnode = nodes.definition()
if definition:
self.state.nested_parse(definition, definition.items[0][1],
defnode)
termnodes.append(defnode)
items.append((termtexts,
nodes.definition_list_item('', *termnodes)))
if 'sorted' in self.options:
items.sort(key=lambda x:
unicodedata.normalize('NFD', x[0][0].lower()))
dlist = nodes.definition_list()
dlist['classes'].append('glossary')
dlist.extend(item[1] for item in items)
node += dlist
return messages + [node]
def token_xrefs(text: str, productionGroup: str = '') -> List[Node]:
if len(productionGroup) != 0:
productionGroup += ':'
retnodes = [] # type: List[Node]
pos = 0
for m in token_re.finditer(text):
if m.start() > pos:
txt = text[pos:m.start()]
retnodes.append(nodes.Text(txt, txt))
refnode = pending_xref(m.group(1), reftype='token', refdomain='std',
reftarget=productionGroup + m.group(1))
refnode += nodes.literal(m.group(1), m.group(1), classes=['xref'])
retnodes.append(refnode)
pos = m.end()
if pos < len(text):
retnodes.append(nodes.Text(text[pos:], text[pos:]))
return retnodes
class ProductionList(SphinxDirective):
"""
Directive to list grammar productions.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self) -> List[Node]:
domain = cast(StandardDomain, self.env.get_domain('std'))
node = addnodes.productionlist() # type: Element
self.set_source_info(node)
# The backslash handling is from ObjectDescription.get_signatures
nl_escape_re = re.compile(r'\\\n')
lines = nl_escape_re.sub('', self.arguments[0]).split('\n')
productionGroup = ""
i = 0
for rule in lines:
if i == 0 and ':' not in rule:
productionGroup = rule.strip()
continue
i += 1
try:
name, tokens = rule.split(':', 1)
except ValueError:
break
subnode = addnodes.production(rule)
name = name.strip()
subnode['tokenname'] = name
if subnode['tokenname']:
prefix = 'grammar-token-%s' % productionGroup
node_id = make_id(self.env, self.state.document, prefix, name)
subnode['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 subnode['ids']):
subnode['ids'].append(old_node_id)
self.state.document.note_implicit_target(subnode, subnode)
if len(productionGroup) != 0:
objName = "%s:%s" % (productionGroup, name)
else:
objName = name
domain.note_object('token', objName, node_id, location=node)
subnode.extend(token_xrefs(tokens, productionGroup))
node.append(subnode)
return [node]
def make_old_id(self, token: str) -> str:
"""Generate old styled node_id for tokens.
.. note:: Old Styled node_id was used until Sphinx-3.0.
This will be removed in Sphinx-5.0.
"""
return nodes.make_id('grammar-token-' + token)
class TokenXRefRole(XRefRole):
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
title: str, target: str) -> Tuple[str, str]:
target = target.lstrip('~') # a title-specific thing
if not self.has_explicit_title and title[0] == '~':
if ':' in title:
_, title = title.split(':')
else:
title = title[1:]
return title, target
class StandardDomain(Domain):
"""
Domain for all objects that don't fit into another domain or are added
via the application interface.
"""
name = 'std'
label = 'Default'
object_types = {
'term': ObjType(_('glossary term'), 'term', searchprio=-1),
'token': ObjType(_('grammar token'), 'token', searchprio=-1),
'label': ObjType(_('reference label'), 'ref', 'keyword',
searchprio=-1),
'envvar': ObjType(_('environment variable'), 'envvar'),
'cmdoption': ObjType(_('program option'), 'option'),
'doc': ObjType(_('document'), 'doc', searchprio=-1)
} # type: Dict[str, ObjType]
directives = {
'program': Program,
'cmdoption': Cmdoption, # old name for backwards compatibility
'option': Cmdoption,
'envvar': EnvVar,
'glossary': Glossary,
'productionlist': ProductionList,
} # type: Dict[str, Type[Directive]]
roles = {
'option': OptionXRefRole(warn_dangling=True),
'envvar': EnvVarXRefRole(),
# links to tokens in grammar productions
'token': TokenXRefRole(),
# links to terms in glossary
'term': XRefRole(innernodeclass=nodes.inline,
warn_dangling=True),
# links to headings or arbitrary labels
'ref': XRefRole(lowercase=True, innernodeclass=nodes.inline,
warn_dangling=True),
# links to labels of numbered figures, tables and code-blocks
'numref': XRefRole(lowercase=True,
warn_dangling=True),
# links to labels, without a different title
'keyword': XRefRole(warn_dangling=True),
# links to documents
'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline),
} # type: Dict[str, Union[RoleFunction, XRefRole]]
initial_data = {
'progoptions': {}, # (program, name) -> docname, labelid
'objects': {}, # (type, name) -> docname, labelid
'labels': { # labelname -> docname, labelid, sectionname
'genindex': ('genindex', '', _('Index')),
'modindex': ('py-modindex', '', _('Module Index')),
'search': ('search', '', _('Search Page')),
},
'anonlabels': { # labelname -> docname, labelid
'genindex': ('genindex', ''),
'modindex': ('py-modindex', ''),
'search': ('search', ''),
},
}
dangling_warnings = {
'term': 'term not in glossary: %(target)s',
'numref': 'undefined label: %(target)s',
'keyword': 'unknown keyword: %(target)s',
'doc': 'unknown document: %(target)s',
'option': 'unknown option: %(target)s',
}
enumerable_nodes = { # node_class -> (figtype, title_getter)
nodes.figure: ('figure', None),
nodes.table: ('table', None),
nodes.container: ('code-block', None),
} # type: Dict[Type[Node], Tuple[str, Callable]]
def __init__(self, env: "BuildEnvironment") -> None:
super().__init__(env)
# set up enumerable nodes
self.enumerable_nodes = copy(self.enumerable_nodes) # create a copy for this instance
for node, settings in env.app.registry.enumerable_nodes.items():
self.enumerable_nodes[node] = settings
def note_hyperlink_target(self, name: str, docname: str, node_id: str,
title: str = '') -> None:
"""Add a hyperlink target for cross reference.
.. warning::
This is only for internal use. Please don't use this from your extension.
``document.note_explicit_target()`` or ``note_implicit_target()`` are recommended to
add a hyperlink target to the document.
This only adds a hyperlink target to the StandardDomain. And this does not add a
node_id to node. Therefore, it is very fragile to calling this without
understanding hyperlink target framework in both docutils and Sphinx.
.. versionadded:: 3.0
"""
if name in self.anonlabels and self.anonlabels[name] != (docname, node_id):
logger.warning(__('duplicate label %s, other instance in %s'),
name, self.env.doc2path(self.anonlabels[name][0]))
self.anonlabels[name] = (docname, node_id)
if title:
self.labels[name] = (docname, node_id, title)
@property
def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid
def note_object(self, objtype: str, name: str, labelid: str, location: Any = None
) -> None:
"""Note a generic object for cross reference.
.. versionadded:: 3.0
"""
if (objtype, name) in self.objects:
docname = self.objects[objtype, name][0]
logger.warning(__('duplicate %s description of %s, other instance in %s'),
objtype, name, docname, location=location)
self.objects[objtype, name] = (self.env.docname, labelid)
def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None:
warnings.warn('StandardDomain.add_object() is deprecated.',
RemovedInSphinx50Warning, stacklevel=2)
self.objects[objtype, name] = (docname, labelid)
@property
def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]:
return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid
@property
def labels(self) -> Dict[str, Tuple[str, str, str]]:
return self.data.setdefault('labels', {}) # labelname -> docname, labelid, sectionname
@property
def anonlabels(self) -> Dict[str, Tuple[str, str]]:
return self.data.setdefault('anonlabels', {}) # labelname -> docname, labelid
def clear_doc(self, docname: str) -> None:
key = None # type: Any
for key, (fn, _l) in list(self.progoptions.items()):
if fn == docname:
del self.progoptions[key]
for key, (fn, _l) in list(self.objects.items()):
if fn == docname:
del self.objects[key]
for key, (fn, _l, _l) in list(self.labels.items()):
if fn == docname:
del self.labels[key]
for key, (fn, _l) in list(self.anonlabels.items()):
if fn == docname:
del self.anonlabels[key]
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX duplicates?
for key, data in otherdata['progoptions'].items():
if data[0] in docnames:
self.progoptions[key] = data
for key, data in otherdata['objects'].items():
if data[0] in docnames:
self.objects[key] = data
for key, data in otherdata['labels'].items():
if data[0] in docnames:
self.labels[key] = data
for key, data in otherdata['anonlabels'].items():
if data[0] in docnames:
self.anonlabels[key] = data
def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
for name, explicit in document.nametypes.items():
if not explicit:
continue
labelid = document.nameids[name]
if labelid is None:
continue
node = document.ids[labelid]
if isinstance(node, nodes.target) and 'refid' in node:
# indirect hyperlink targets
node = document.ids.get(node['refid'])
labelid = node['names'][0]
if (node.tagname == 'footnote' or
'refuri' in node or
node.tagname.startswith('desc_')):
# ignore footnote labels, labels automatically generated from a
# link and object descriptions
continue
if name in self.labels:
logger.warning(__('duplicate label %s, other instance in %s'),
name, env.doc2path(self.labels[name][0]),
location=node)
self.anonlabels[name] = docname, labelid
if node.tagname in ('section', 'rubric'):
title = cast(nodes.title, node[0])
sectname = clean_astext(title)
elif self.is_enumerable_node(node):
sectname = self.get_numfig_title(node)
if not sectname:
continue
else:
toctree = next(iter(node.traverse(addnodes.toctree)), None)
if toctree and toctree.get('caption'):
sectname = toctree.get('caption')
else:
# anonymous-only labels
continue
self.labels[name] = docname, labelid, sectname
def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None:
self.progoptions[program, name] = (docname, labelid)
def build_reference_node(self, fromdocname: str, builder: "Builder", docname: str,
labelid: str, sectname: str, rolename: str, **options: Any
) -> Element:
nodeclass = options.pop('nodeclass', nodes.reference)
newnode = nodeclass('', '', internal=True, **options)
innernode = nodes.inline(sectname, sectname)
if innernode.get('classes') is not None:
innernode['classes'].append('std')
innernode['classes'].append('std-' + rolename)
if docname == fromdocname:
newnode['refid'] = labelid
else:
# set more info in contnode; in case the
# get_relative_uri call raises NoUri,
# the builder will then have to resolve these
contnode = pending_xref('')
contnode['refdocname'] = docname
contnode['refsectname'] = sectname
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname)
if labelid:
newnode['refuri'] += '#' + labelid
newnode.append(innernode)
return newnode
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element) -> Element:
if typ == 'ref':
resolver = self._resolve_ref_xref
elif typ == 'numref':
resolver = self._resolve_numref_xref
elif typ == 'keyword':
resolver = self._resolve_keyword_xref
elif typ == 'doc':
resolver = self._resolve_doc_xref
elif typ == 'option':
resolver = self._resolve_option_xref
elif typ == 'citation':
warnings.warn('pending_xref(domain=std, type=citation) is deprecated: %r' % node,
RemovedInSphinx40Warning, stacklevel=2)
domain = env.get_domain('citation')
return domain.resolve_xref(env, fromdocname, builder, typ, target, node, contnode)
elif typ == 'term':
resolver = self._resolve_term_xref
else:
resolver = self._resolve_obj_xref
return resolver(env, fromdocname, builder, typ, target, node, contnode)
def _resolve_ref_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str, node: pending_xref,
contnode: Element) -> Element:
if node['refexplicit']:
# reference to anonymous label; the reference uses
# the supplied link caption
docname, labelid = self.anonlabels.get(target, ('', ''))
sectname = node.astext()
else:
# reference to named label; the final node will
# contain the section name after the label
docname, labelid, sectname = self.labels.get(target, ('', '', ''))
if not docname:
return None
return self.build_reference_node(fromdocname, builder,
docname, labelid, sectname, 'ref')
def _resolve_numref_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
if target in self.labels:
docname, labelid, figname = self.labels.get(target, ('', '', ''))
else:
docname, labelid = self.anonlabels.get(target, ('', ''))
figname = None
if not docname:
return None
target_node = env.get_doctree(docname).ids.get(labelid)
figtype = self.get_enumerable_node_type(target_node)
if figtype is None:
return None
if figtype != 'section' and env.config.numfig is False:
logger.warning(__('numfig is disabled. :numref: is ignored.'), location=node)
return contnode
try:
fignumber = self.get_fignumber(env, builder, figtype, docname, target_node)
if fignumber is None:
return contnode
except ValueError:
logger.warning(__("Failed to create a cross reference. Any number is not "
"assigned: %s"),
labelid, location=node)
return contnode
try:
if node['refexplicit']:
title = contnode.astext()
else:
title = env.config.numfig_format.get(figtype, '')
if figname is None and '{name}' in title:
logger.warning(__('the link has no caption: %s'), title, location=node)
return contnode
else:
fignum = '.'.join(map(str, fignumber))
if '{name}' in title or 'number' in title:
# new style format (cf. "Fig.{number}")
if figname:
newtitle = title.format(name=figname, number=fignum)
else:
newtitle = title.format(number=fignum)
else:
# old style format (cf. "Fig.%s")
newtitle = title % fignum
except KeyError as exc:
logger.warning(__('invalid numfig_format: %s (%r)'), title, exc, location=node)
return contnode
except TypeError:
logger.warning(__('invalid numfig_format: %s'), title, location=node)
return contnode
return self.build_reference_node(fromdocname, builder,
docname, labelid, newtitle, 'numref',
nodeclass=addnodes.number_reference,
title=title)
def _resolve_keyword_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
# keywords are oddballs: they are referenced by named labels
docname, labelid, _ = self.labels.get(target, ('', '', ''))
if not docname:
return None
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def _resolve_doc_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
# directly reference to document by source name; can be absolute or relative
refdoc = node.get('refdoc', fromdocname)
docname = docname_join(refdoc, node['reftarget'])
if docname not in env.all_docs:
return None
else:
if node['refexplicit']:
# reference with explicit title
caption = node.astext()
else:
caption = clean_astext(env.titles[docname])
innernode = nodes.inline(caption, caption, classes=['doc'])
return make_refnode(builder, fromdocname, docname, None, innernode)
def _resolve_option_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
progname = node.get('std:program')
target = target.strip()
docname, labelid = self.progoptions.get((progname, target), ('', ''))
if not docname:
commands = []
while ws_re.search(target):
subcommand, target = ws_re.split(target, 1)
commands.append(subcommand)
progname = "-".join(commands)
docname, labelid = self.progoptions.get((progname, target), ('', ''))
if docname:
break
else:
return None
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def _resolve_term_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
result = self._resolve_obj_xref(env, fromdocname, builder, typ,
target, node, contnode)
if result:
return result
else:
for objtype, term in self.objects:
if objtype == 'term' and term.lower() == target.lower():
docname, labelid = self.objects[objtype, term]
logger.warning(__('term %s not found in case sensitive match.'
'made a reference to %s instead.'),
target, term, location=node, type='ref', subtype='term')
break
else:
docname, labelid = '', ''
if not docname:
return None
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
node: pending_xref, contnode: Element) -> Element:
objtypes = self.objtypes_for_role(typ) or []
for objtype in objtypes:
if (objtype, target) in self.objects:
docname, labelid = self.objects[objtype, target]
break
else:
docname, labelid = '', ''
if not docname:
return None
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
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]]
ltarget = target.lower() # :ref: lowercases its target automatically
for role in ('ref', 'option'): # do not try "keyword"
res = self.resolve_xref(env, fromdocname, builder, role,
ltarget if role == 'ref' else target,
node, contnode)
if res:
results.append(('std:' + role, res))
# all others
for objtype in self.object_types:
key = (objtype, target)
if objtype == 'term':
key = (objtype, ltarget)
if key in self.objects:
docname, labelid = self.objects[key]
results.append(('std:' + self.role_for_objtype(objtype),
make_refnode(builder, fromdocname, docname,
labelid, contnode)))
return results
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
# handle the special 'doc' reference here
for doc in self.env.all_docs:
yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1)
for (prog, option), info in self.progoptions.items():
if prog:
fullname = ".".join([prog, option])
yield (fullname, fullname, 'cmdoption', info[0], info[1], 1)
else:
yield (option, option, 'cmdoption', info[0], info[1], 1)
for (type, name), info in self.objects.items():
yield (name, name, type, info[0], info[1],
self.object_types[type].attrs['searchprio'])
for name, (docname, labelid, sectionname) in self.labels.items():
yield (name, sectionname, 'label', docname, labelid, -1)
# add anonymous-only labels as well
non_anon_labels = set(self.labels)
for name, (docname, labelid) in self.anonlabels.items():
if name not in non_anon_labels:
yield (name, name, 'label', docname, labelid, -1)
def get_type_name(self, type: ObjType, primary: bool = False) -> str:
# never prepend "Default"
return type.lname
def is_enumerable_node(self, node: Node) -> bool:
return node.__class__ in self.enumerable_nodes
def get_numfig_title(self, node: Node) -> str:
"""Get the title of enumerable nodes to refer them using its title"""
if self.is_enumerable_node(node):
elem = cast(Element, node)
_, title_getter = self.enumerable_nodes.get(elem.__class__, (None, None))
if title_getter:
return title_getter(elem)
else:
for subnode in elem:
if isinstance(subnode, (nodes.caption, nodes.title)):
return clean_astext(subnode)
return None
def get_enumerable_node_type(self, node: Node) -> str:
"""Get type of enumerable nodes."""
def has_child(node: Element, cls: "Type") -> bool:
return any(isinstance(child, cls) for child in node)
if isinstance(node, nodes.section):
return 'section'
elif (isinstance(node, nodes.container) and
'literal_block' in node and
has_child(node, nodes.literal_block)):
# given node is a code-block having caption
return 'code-block'
else:
figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return figtype
def get_fignumber(self, env: "BuildEnvironment", builder: "Builder",
figtype: str, docname: str, target_node: Element) -> Tuple[int, ...]:
if figtype == 'section':
if builder.name == 'latex':
return tuple()
elif docname not in env.toc_secnumbers:
raise ValueError # no number assigned
else:
anchorname = '#' + target_node['ids'][0]
if anchorname not in env.toc_secnumbers[docname]:
# try first heading which has no anchor
return env.toc_secnumbers[docname].get('')
else:
return env.toc_secnumbers[docname].get(anchorname)
else:
try:
figure_id = target_node['ids'][0]
return env.toc_fignumbers[docname][figtype][figure_id]
except (KeyError, IndexError) as exc:
# target_node is found, but fignumber is not assigned.
# Maybe it is defined in orphaned document.
raise ValueError from exc
def get_full_qualified_name(self, node: Element) -> str:
if node.get('reftype') == 'option':
progname = node.get('std:program')
command = ws_re.split(node.get('reftarget'))
if progname:
command.insert(0, progname)
option = command.pop()
if command:
return '.'.join(['-'.join(command), option])
else:
return None
else:
return None
def note_citations(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
warnings.warn('StandardDomain.note_citations() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
def note_citation_refs(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
warnings.warn('StandardDomain.note_citation_refs() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
def note_labels(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA
warnings.warn('StandardDomain.note_labels() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref) -> bool:
if (domain and domain.name != 'std') or node['reftype'] != 'ref':
return None
else:
target = node['reftarget']
if target not in domain.anonlabels: # type: ignore
msg = __('undefined label: %s')
else:
msg = __('Failed to create a cross reference. A title or caption not found: %s')
logger.warning(msg % target, location=node, type='ref', subtype=node['reftype'])
return True
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_domain(StandardDomain)
app.connect('warn-missing-reference', warn_missing_reference)
return {
'version': 'builtin',
'env_version': 1,
'parallel_read_safe': True,
'parallel_write_safe': True,
}