summaryrefslogtreecommitdiff
path: root/sphinx/domains
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/domains')
-rw-r--r--sphinx/domains/__init__.py17
-rw-r--r--sphinx/domains/c.py4
-rw-r--r--sphinx/domains/cpp.py489
-rw-r--r--sphinx/domains/javascript.py4
-rw-r--r--sphinx/domains/python.py35
-rw-r--r--sphinx/domains/rst.py4
-rw-r--r--sphinx/domains/std.py287
7 files changed, 700 insertions, 140 deletions
diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py
index bee03fce7..da7e5d9ae 100644
--- a/sphinx/domains/__init__.py
+++ b/sphinx/domains/__init__.py
@@ -275,20 +275,3 @@ class Domain(object):
if primary:
return type.lname
return _('%s %s') % (self.label, type.lname)
-
-
-from sphinx.domains.c import CDomain # noqa
-from sphinx.domains.cpp import CPPDomain # noqa
-from sphinx.domains.std import StandardDomain # noqa
-from sphinx.domains.python import PythonDomain # noqa
-from sphinx.domains.javascript import JavaScriptDomain # noqa
-from sphinx.domains.rst import ReSTDomain # noqa
-
-BUILTIN_DOMAINS = {
- 'std': StandardDomain,
- 'py': PythonDomain,
- 'c': CDomain,
- 'cpp': CPPDomain,
- 'js': JavaScriptDomain,
- 'rst': ReSTDomain,
-}
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 8ba159d32..43e869dbc 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -302,3 +302,7 @@ class CDomain(Domain):
def get_objects(self):
for refname, (docname, type) in list(self.data['objects'].items()):
yield (refname, refname, type, docname, 'c.' + refname, 1)
+
+
+def setup(app):
+ app.add_domain(CDomain)
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 323905256..6c12d6aca 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -46,6 +46,7 @@ from sphinx.util.docfields import Field, GroupedField
Each desc_signature node will have the attribute 'sphinx_cpp_tagname' set to
- 'templateParams', if the line is on the form 'template<...>',
+ - 'templateIntroduction, if the line is on the form 'conceptName{...}'
- 'declarator', if the line contains the name of the declared object.
No other desc_signature nodes should exist (so far).
@@ -54,7 +55,8 @@ from sphinx.util.docfields import Field, GroupedField
----------------------------------------------------------------------------
See http://www.nongnu.org/hcb/ for the grammar,
- or https://github.com/cplusplus/draft/blob/master/source/grammar.tex
+ and https://github.com/cplusplus/draft/blob/master/source/grammar.tex,
+ and https://github.com/cplusplus/concepts-ts
for the newest grammar.
common grammar things:
@@ -201,6 +203,17 @@ from sphinx.util.docfields import Field, GroupedField
We additionally add the possibility for specifying the visibility as the
first thing.
+ concept_object:
+ goal:
+ just a declaration of the name (for now)
+ either a variable concept or function concept
+
+ grammar: only a single template parameter list, and the nested name
+ may not have any template argument lists
+
+ "template" "<" template-parameter-list ">"
+ nested-name-specifier "()"[opt]
+
type_object:
goal:
either a single type (e.g., "MyClass:Something_T" or a typedef-like
@@ -540,6 +553,80 @@ def _verify_description_mode(mode):
raise Exception("Description mode '%s' is invalid." % mode)
+class ASTCPPAttribute(ASTBase):
+ def __init__(self, arg):
+ self.arg = arg
+
+ def __unicode__(self):
+ return "[[" + self.arg + "]]"
+
+ def describe_signature(self, signode):
+ txt = text_type(self)
+ signode.append(nodes.Text(txt, txt))
+
+
+class ASTGnuAttribute(ASTBase):
+ def __init__(self, name, args):
+ self.name = name
+ self.args = args
+
+ def __unicode__(self):
+ res = [self.name]
+ if self.args:
+ res.append('(')
+ res.append(text_type(self.args))
+ res.append(')')
+ return ''.join(res)
+
+
+class ASTGnuAttributeList(ASTBase):
+ def __init__(self, attrs):
+ self.attrs = attrs
+
+ def __unicode__(self):
+ res = ['__attribute__((']
+ first = True
+ for attr in self.attrs:
+ if not first:
+ res.append(', ')
+ first = False
+ res.append(text_type(attr))
+ res.append('))')
+ return ''.join(res)
+
+ def describe_signature(self, signode):
+ txt = text_type(self)
+ signode.append(nodes.Text(txt, txt))
+
+
+class ASTIdAttribute(ASTBase):
+ """For simple attributes defined by the user."""
+
+ def __init__(self, id):
+ self.id = id
+
+ def __unicode__(self):
+ return self.id
+
+ def describe_signature(self, signode):
+ signode.append(nodes.Text(self.id, self.id))
+
+
+class ASTParenAttribute(ASTBase):
+ """For paren attributes defined by the user."""
+
+ def __init__(self, id, arg):
+ self.id = id
+ self.arg = arg
+
+ def __unicode__(self):
+ return self.id + '(' + self.arg + ')'
+
+ def describe_signature(self, signode):
+ txt = text_type(self)
+ signode.append(nodes.Text(txt, txt))
+
+
class ASTIdentifier(ASTBase):
def __init__(self, identifier):
assert identifier is not None
@@ -567,7 +654,8 @@ class ASTIdentifier(ASTBase):
_verify_description_mode(mode)
if mode == 'markType':
targetText = prefix + self.identifier
- pnode = addnodes.pending_xref('', refdomain='cpp', reftype='type',
+ pnode = addnodes.pending_xref('', refdomain='cpp',
+ reftype='typeOrConcept',
reftarget=targetText, modname=None,
classname=None)
key = symbol.get_lookup_key()
@@ -751,6 +839,7 @@ class ASTTemplateParams(ASTBase):
return ''.join(res)
def describe_signature(self, signode, mode, env, symbol):
+ signode.sphinx_cpp_tagname = 'templateParams'
signode += nodes.Text("template<")
first = True
for param in self.params:
@@ -761,6 +850,92 @@ class ASTTemplateParams(ASTBase):
signode += nodes.Text(">")
+class ASTTemplateIntroductionParameter(ASTBase):
+ def __init__(self, identifier, parameterPack):
+ self.identifier = identifier
+ self.parameterPack = parameterPack
+
+ def get_identifier(self):
+ return self.identifier
+
+ def get_id_v2(self, objectType=None, symbol=None):
+ # this is not part of the normal name mangling in C++
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id_v2(prefixed=None)
+ else:
+ if self.parameterPack:
+ return 'Dp'
+ else:
+ return '0' # we need to put something
+
+ def get_id_v2_as_arg(self):
+ # used for the implicit requires clause
+ res = self.identifier.get_id_v2()
+ if self.parameterPack:
+ return u'sp' + res
+ else:
+ return res
+
+ def __unicode__(self):
+ res = []
+ if self.parameterPack:
+ res.append('...')
+ res.append(text_type(self.identifier))
+ return ''.join(res)
+
+ def describe_signature(self, signode, mode, env, symbol):
+ if self.parameterPack:
+ signode += nodes.Text('...')
+ self.identifier.describe_signature(signode, mode, env, '', symbol)
+
+
+class ASTTemplateIntroduction(ASTBase):
+ def __init__(self, concept, params):
+ assert len(params) > 0
+ self.concept = concept
+ self.params = params
+
+ # id_v1 does not exist
+
+ def get_id_v2(self):
+ # first do the same as a normal template parameter list
+ res = []
+ res.append("I")
+ for param in self.params:
+ res.append(param.get_id_v2())
+ res.append("E")
+ # let's use X expr E, which is otherwise for constant template args
+ res.append("X")
+ res.append(self.concept.get_id_v2())
+ res.append("I")
+ for param in self.params:
+ res.append(param.get_id_v2_as_arg())
+ res.append("E")
+ res.append("E")
+ return ''.join(res)
+
+ def __unicode__(self):
+ res = []
+ res.append(text_type(self.concept))
+ res.append('{')
+ res.append(', '.join(text_type(param) for param in self.params))
+ res.append('} ')
+ return ''.join(res)
+
+ def describe_signature(self, signode, mode, env, symbol):
+ signode.sphinx_cpp_tagname = 'templateIntroduction'
+ self.concept.describe_signature(signode, 'markType', env, symbol)
+ signode += nodes.Text('{')
+ first = True
+ for param in self.params:
+ if not first:
+ signode += nodes.Text(', ')
+ first = False
+ param.describe_signature(signode, mode, env, symbol)
+ signode += nodes.Text('}')
+
+
class ASTTemplateDeclarationPrefix(ASTBase):
def __init__(self, templates):
assert templates is not None
@@ -785,8 +960,7 @@ class ASTTemplateDeclarationPrefix(ASTBase):
def describe_signature(self, signode, mode, env, symbol):
_verify_description_mode(mode)
for t in self.templates:
- templateNode = addnodes.desc_signature()
- templateNode.sphinx_cpp_tagname = 'templateParams'
+ templateNode = addnodes.desc_signature_line()
t.describe_signature(templateNode, 'lastIsName', env, symbol)
signode += templateNode
@@ -1256,7 +1430,7 @@ class ASTParametersQualifiers(ASTBase):
class ASTDeclSpecsSimple(ASTBase):
def __init__(self, storage, threadLocal, inline, virtual, explicit,
- constexpr, volatile, const, friend):
+ constexpr, volatile, const, friend, attrs):
self.storage = storage
self.threadLocal = threadLocal
self.inline = inline
@@ -1266,6 +1440,7 @@ class ASTDeclSpecsSimple(ASTBase):
self.volatile = volatile
self.const = const
self.friend = friend
+ self.attrs = attrs
def mergeWith(self, other):
if not other:
@@ -1278,10 +1453,12 @@ class ASTDeclSpecsSimple(ASTBase):
self.constexpr or other.constexpr,
self.volatile or other.volatile,
self.const or other.const,
- self.friend or other.friend)
+ self.friend or other.friend,
+ self.attrs + other.attrs)
def __unicode__(self):
res = []
+ res.extend(text_type(attr) for attr in self.attrs)
if self.storage:
res.append(self.storage)
if self.threadLocal:
@@ -1307,6 +1484,10 @@ class ASTDeclSpecsSimple(ASTBase):
if len(modifiers) > 0:
modifiers.append(nodes.Text(' '))
modifiers.append(addnodes.desc_annotation(text, text))
+ for attr in self.attrs:
+ if len(modifiers) > 0:
+ modifiers.append(nodes.Text(' '))
+ modifiers.append(attr.describe_signature(modifiers))
if self.storage:
_add(modifiers, self.storage)
if self.threadLocal:
@@ -2025,6 +2206,39 @@ class ASTTypeUsing(ASTBase):
self.type.describe_signature(signode, 'markType', env, symbol=symbol)
+class ASTConcept(ASTBase):
+ def __init__(self, nestedName, isFunction, initializer):
+ self.nestedName = nestedName
+ self.isFunction = isFunction # otherwise it's a variable concept
+ self.initializer = initializer
+
+ @property
+ def name(self):
+ return self.nestedName
+
+ def get_id_v1(self, objectType=None, symbol=None):
+ raise NoOldIdError()
+
+ def get_id_v2(self, objectType, symbol):
+ return symbol.get_full_nested_name().get_id_v2()
+
+ def __unicode__(self):
+ res = text_type(self.nestedName)
+ if self.isFunction:
+ res += "()"
+ if self.initializer:
+ res += text_type(self.initializer)
+ return res
+
+ def describe_signature(self, signode, mode, env, symbol):
+ signode += nodes.Text(text_type("bool "))
+ self.nestedName.describe_signature(signode, mode, env, symbol)
+ if self.isFunction:
+ signode += nodes.Text("()")
+ if self.initializer:
+ self.initializer.describe_signature(signode, mode)
+
+
class ASTBaseClass(ASTBase):
def __init__(self, name, visibility, virtual, pack):
self.name = name
@@ -2215,17 +2429,19 @@ class ASTDeclaration(ASTBase):
def describe_signature(self, signode, mode, env):
_verify_description_mode(mode)
- # the caller of the domain added a desc_signature node
- # let's pop it so we can add templates before that
- parentNode = signode.parent
- mainDeclNode = signode
+ # The caller of the domain added a desc_signature node.
+ # Always enable multiline:
+ signode['is_multiline'] = True
+ # Put each line in a desc_signature_line node.
+ mainDeclNode = addnodes.desc_signature_line()
mainDeclNode.sphinx_cpp_tagname = 'declarator'
- parentNode.pop()
+ mainDeclNode['add_permalink'] = True
assert self.symbol
if self.templatePrefix:
- self.templatePrefix.describe_signature(parentNode, mode, env,
+ self.templatePrefix.describe_signature(signode, mode, env,
symbol=self.symbol)
+ signode += mainDeclNode
if self.visibility and self.visibility != "public":
mainDeclNode += addnodes.desc_annotation(self.visibility + " ",
self.visibility + " ")
@@ -2233,6 +2449,8 @@ class ASTDeclaration(ASTBase):
prefix = self.declaration.get_type_declaration_prefix()
prefix += ' '
mainDeclNode += addnodes.desc_annotation(prefix, prefix)
+ elif self.objectType == 'concept':
+ mainDeclNode += addnodes.desc_annotation('concept ', 'concept ')
elif self.objectType == 'member':
pass
elif self.objectType == 'function':
@@ -2251,7 +2469,6 @@ class ASTDeclaration(ASTBase):
assert False
self.declaration.describe_signature(mainDeclNode, mode, env,
symbol=self.symbol)
- parentNode += mainDeclNode
class ASTNamespace(ASTBase):
@@ -2709,7 +2926,7 @@ class DefinitionParser(object):
_prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')
- def __init__(self, definition, warnEnv):
+ def __init__(self, definition, warnEnv, config):
self.definition = definition.strip()
self.pos = 0
self.end = len(self.definition)
@@ -2717,6 +2934,7 @@ class DefinitionParser(object):
self._previous_state = (0, None)
self.warnEnv = warnEnv
+ self.config = config
def _make_multi_error(self, errors, header):
if len(errors) == 1:
@@ -2785,6 +3003,12 @@ class DefinitionParser(object):
return True
return False
+ def skip_string_and_ws(self, string):
+ if self.skip_string(string):
+ self.skip_ws()
+ return True
+ return False
+
@property
def eof(self):
return self.pos >= self.end
@@ -2811,6 +3035,85 @@ class DefinitionParser(object):
if not self.eof:
self.fail('Expected end of definition.')
+ def _parse_balanced_token_seq(self, end):
+ # TODO: add handling of string literals and similar
+ brackets = {'(': ')', '[': ']', '{': '}'}
+ startPos = self.pos
+ symbols = []
+ while not self.eof:
+ if len(symbols) == 0 and self.current_char in end:
+ break
+ if self.current_char in brackets.keys():
+ symbols.append(brackets[self.current_char])
+ elif len(symbols) > 0 and self.current_char == symbols[-1]:
+ symbols.pop()
+ elif self.current_char in ")]}":
+ self.fail("Unexpected '%s' in balanced-token-seq." % self.current_char)
+ self.pos += 1
+ if self.eof:
+ self.fail("Could not find end of balanced-token-seq starting at %d."
+ % startPos)
+ return self.definition[startPos:self.pos]
+
+ def _parse_attribute(self):
+ self.skip_ws()
+ # try C++11 style
+ startPos = self.pos
+ if self.skip_string_and_ws('['):
+ if not self.skip_string('['):
+ self.pos = startPos
+ else:
+ # TODO: actually implement the correct grammar
+ arg = self._parse_balanced_token_seq(end=[']'])
+ if not self.skip_string_and_ws(']'):
+ self.fail("Expected ']' in end of attribute.")
+ if not self.skip_string_and_ws(']'):
+ self.fail("Expected ']' in end of attribute after [[...]")
+ return ASTCPPAttribute(arg)
+
+ # try GNU style
+ if self.skip_word_and_ws('__attribute__'):
+ if not self.skip_string_and_ws('('):
+ self.fail("Expected '(' after '__attribute__'.")
+ if not self.skip_string_and_ws('('):
+ self.fail("Expected '(' after '__attribute__('.")
+ attrs = []
+ while 1:
+ if self.match(_identifier_re):
+ name = self.matched_text
+ self.skip_ws()
+ if self.skip_string_and_ws('('):
+ self.fail('Parameterized GNU style attribute not yet supported.')
+ attrs.append(ASTGnuAttribute(name, None))
+ # TODO: parse arguments for the attribute
+ if self.skip_string_and_ws(','):
+ continue
+ elif self.skip_string_and_ws(')'):
+ break
+ else:
+ self.fail("Expected identifier, ')', or ',' in __attribute__.")
+ if not self.skip_string_and_ws(')'):
+ self.fail("Expected ')' after '__attribute__((...)'")
+ return ASTGnuAttributeList(attrs)
+
+ # try the simple id attributes defined by the user
+ for id in self.config.cpp_id_attributes:
+ if self.skip_word_and_ws(id):
+ return ASTIdAttribute(id)
+
+ # try the paren attributes defined by the user
+ for id in self.config.cpp_paren_attributes:
+ if not self.skip_string_and_ws(id):
+ continue
+ if not self.skip_string('('):
+ self.fail("Expected '(' after user-defined paren-attribute.")
+ arg = self._parse_balanced_token_seq(end=[')'])
+ if not self.skip_string(')'):
+ self.fail("Expected ')' to end user-defined paren-attribute.")
+ return ASTParenAttribute(id, arg)
+
+ return None
+
def _parse_expression(self, end):
# Stupidly "parse" an expression.
# 'end' should be a list of characters which ends the expression.
@@ -3092,6 +3395,7 @@ class DefinitionParser(object):
volatile = None
const = None
friend = None
+ attrs = []
while 1: # accept any permutation of a subset of some decl-specs
self.skip_ws()
if not storage:
@@ -3145,9 +3449,14 @@ class DefinitionParser(object):
const = self.skip_word('const')
if const:
continue
+ attr = self._parse_attribute()
+ if attr:
+ attrs.append(attr)
+ continue
break
return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual,
- explicit, constexpr, volatile, const, friend)
+ explicit, constexpr, volatile, const,
+ friend, attrs)
def _parse_decl_specs(self, outer, typed=True):
if outer:
@@ -3430,6 +3739,23 @@ class DefinitionParser(object):
type = self._parse_type(False, None)
return ASTTypeUsing(name, type)
+ def _parse_concept(self):
+ nestedName = self._parse_nested_name()
+ isFunction = False
+
+ self.skip_ws()
+ if self.skip_string('('):
+ isFunction = True
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in function concept declaration.")
+
+ initializer = self._parse_initializer('member')
+ if initializer and isFunction:
+ self.fail("Function concept with initializer.")
+
+ return ASTConcept(nestedName, isFunction, initializer)
+
def _parse_class(self):
name = self._parse_nested_name()
self.skip_ws()
@@ -3548,14 +3874,62 @@ class DefinitionParser(object):
prevErrors.append((e, ""))
raise self._make_multi_error(prevErrors, header)
- def _parse_template_declaration_prefix(self):
- templates = []
+ def _parse_template_introduction(self):
+ pos = self.pos
+ try:
+ concept = self._parse_nested_name()
+ except:
+ self.pos = pos
+ return None
+ self.skip_ws()
+ if not self.skip_string('{'):
+ self.pos = pos
+ return None
+
+ # for sure it must be a template introduction now
+ params = []
while 1:
self.skip_ws()
- if not self.skip_word("template"):
+ parameterPack = self.skip_string('...')
+ self.skip_ws()
+ if not self.match(_identifier_re):
+ self.fail("Expected identifier in template introduction list.")
+ identifier = self.matched_text
+ # make sure there isn't a keyword
+ if identifier in _keywords:
+ self.fail("Expected identifier in template introduction list, "
+ "got keyword: %s" % identifier)
+ identifier = ASTIdentifier(identifier)
+ params.append(ASTTemplateIntroductionParameter(identifier, parameterPack))
+
+ self.skip_ws()
+ if self.skip_string('}'):
break
- params = self._parse_template_parameter_list()
+ elif self.skip_string(','):
+ continue
+ else:
+ self.fail("Error in template introduction list. "
+ 'Expected ",", or "}".')
+ return ASTTemplateIntroduction(concept, params)
+
+ def _parse_template_declaration_prefix(self, objectType):
+ templates = []
+ while 1:
+ self.skip_ws()
+ # the saved position is only used to provide a better error message
+ pos = self.pos
+ if self.skip_word("template"):
+ params = self._parse_template_parameter_list()
+ else:
+ params = self._parse_template_introduction()
+ if not params:
+ break
+ if objectType == 'concept' and len(templates) > 0:
+ self.pos = pos
+ self.fail("More than 1 template parameter list for concept.")
templates.append(params)
+ if len(templates) == 0 and objectType == 'concept':
+ self.fail('Missing template parameter list for concept.')
if len(templates) == 0:
return None
else:
@@ -3594,7 +3968,7 @@ class DefinitionParser(object):
return templatePrefix
def parse_declaration(self, objectType):
- if objectType not in ('type', 'member',
+ if objectType not in ('type', 'concept', 'member',
'function', 'class', 'enum', 'enumerator'):
raise Exception('Internal error, unknown objectType "%s".' % objectType)
visibility = None
@@ -3605,8 +3979,8 @@ class DefinitionParser(object):
if self.match(_visibility_re):
visibility = self.matched_text
- if objectType in ('type', 'member', 'function', 'class'):
- templatePrefix = self._parse_template_declaration_prefix()
+ if objectType in ('type', 'concept', 'member', 'function', 'class'):
+ templatePrefix = self._parse_template_declaration_prefix(objectType)
if objectType == 'type':
prevErrors = []
@@ -3626,6 +4000,8 @@ class DefinitionParser(object):
prevErrors.append((e, "If type alias or template alias"))
header = "Error in type declaration."
raise self._make_multi_error(prevErrors, header)
+ elif objectType == 'concept':
+ declaration = self._parse_concept()
elif objectType == 'member':
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':
@@ -3645,7 +4021,7 @@ class DefinitionParser(object):
templatePrefix, declaration)
def parse_namespace_object(self):
- templatePrefix = self._parse_template_declaration_prefix()
+ templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=False)
@@ -3654,7 +4030,7 @@ class DefinitionParser(object):
return res
def parse_xref_object(self):
- templatePrefix = self._parse_template_declaration_prefix()
+ templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=True)
@@ -3746,7 +4122,12 @@ class CPPObject(ObjectDescription):
'report as bug (id=%s).' % (text_type(ast), newestId))
name = text_type(ast.symbol.get_full_nested_name()).lstrip(':')
- indexText = self.get_index_text(name)
+ strippedName = name
+ for prefix in self.env.config.cpp_index_common_prefix:
+ if name.startswith(prefix):
+ strippedName = strippedName[len(prefix):]
+ break
+ indexText = self.get_index_text(strippedName)
self.indexnode['entries'].append(('single', indexText, newestId, '', None))
if newestId not in self.state.document.ids:
@@ -3767,7 +4148,7 @@ class CPPObject(ObjectDescription):
continue
if id not in self.state.document.ids:
signode['ids'].append(id)
- signode['first'] = (not self.names) # hmm, what is this abound?
+ signode['first'] = (not self.names) # hmm, what is this about?
self.state.document.note_explicit_target(signode)
def parse_definition(self, parser):
@@ -3782,7 +4163,7 @@ class CPPObject(ObjectDescription):
self.env.ref_context['cpp:parent_symbol'] = root
parentSymbol = self.env.ref_context['cpp:parent_symbol']
- parser = DefinitionParser(sig, self)
+ parser = DefinitionParser(sig, self, self.env.config)
try:
ast = self.parse_definition(parser)
parser.assert_end()
@@ -3830,6 +4211,17 @@ class CPPTypeObject(CPPObject):
ast.describe_signature(signode, 'lastIsName', self.env)
+class CPPConceptObject(CPPObject):
+ def get_index_text(self, name):
+ return _('%s (C++ concept)') % name
+
+ def parse_definition(self, parser):
+ return parser.parse_declaration("concept")
+
+ def describe_signature(self, signode, ast):
+ ast.describe_signature(signode, 'lastIsName', self.env)
+
+
class CPPMemberObject(CPPObject):
def get_index_text(self, name):
return _('%s (C++ member)') % name
@@ -3917,7 +4309,7 @@ class CPPNamespaceObject(Directive):
symbol = rootSymbol
stack = []
else:
- parser = DefinitionParser(self.arguments[0], self)
+ parser = DefinitionParser(self.arguments[0], self, env.config)
try:
ast = parser.parse_namespace_object()
parser.assert_end()
@@ -3946,7 +4338,7 @@ class CPPNamespacePushObject(Directive):
env = self.state.document.settings.env
if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
return
- parser = DefinitionParser(self.arguments[0], self)
+ parser = DefinitionParser(self.arguments[0], self, env.config)
try:
ast = parser.parse_namespace_object()
parser.assert_end()
@@ -4026,6 +4418,7 @@ class CPPDomain(Domain):
'function': ObjType(l_('function'), 'func'),
'member': ObjType(l_('member'), 'member'),
'type': ObjType(l_('type'), 'type'),
+ 'concept': ObjType(l_('concept'), 'concept'),
'enum': ObjType(l_('enum'), 'enum'),
'enumerator': ObjType(l_('enumerator'), 'enumerator')
}
@@ -4036,6 +4429,7 @@ class CPPDomain(Domain):
'member': CPPMemberObject,
'var': CPPMemberObject,
'type': CPPTypeObject,
+ 'concept': CPPConceptObject,
'enum': CPPEnumObject,
'enum-struct': CPPEnumObject,
'enum-class': CPPEnumObject,
@@ -4051,6 +4445,7 @@ class CPPDomain(Domain):
'member': CPPXRefRole(),
'var': CPPXRefRole(),
'type': CPPXRefRole(),
+ 'concept': CPPXRefRole(),
'enum': CPPXRefRole(),
'enumerator': CPPXRefRole()
}
@@ -4093,7 +4488,7 @@ class CPPDomain(Domain):
if emitWarnings:
env.warn_node(msg, node)
warner = Warner()
- parser = DefinitionParser(target, warner)
+ parser = DefinitionParser(target, warner, env.config)
try:
ast = parser.parse_xref_object()
parser.skip_ws()
@@ -4124,6 +4519,33 @@ class CPPDomain(Domain):
if s is None or s.declaration is None:
return None, None
+ if typ.startswith('cpp:'):
+ typ = typ[4:]
+ if typ == 'func':
+ typ = 'function'
+ declTyp = s.declaration.objectType
+
+ def checkType():
+ if typ == 'any':
+ return True
+ if declTyp == 'templateParam':
+ return True
+ if typ == 'var' or typ == 'member':
+ return declTyp in ['var', 'member']
+ if typ in ['enum', 'enumerator', 'function', 'class', 'concept']:
+ return declTyp == typ
+ validForType = ['enum', 'class', 'function', 'type']
+ if typ == 'typeOrConcept':
+ return declTyp == 'concept' or declTyp in validForType
+ if typ == 'type':
+ return declTyp in validForType
+ print("Type is %s" % typ)
+ assert False
+ if not checkType():
+ warner.warn("cpp:%s targets a %s (%s)."
+ % (typ, s.declaration.objectType,
+ s.get_full_nested_name()))
+
declaration = s.declaration
fullNestedName = s.get_full_nested_name()
name = text_type(fullNestedName).lstrip(':')
@@ -4163,3 +4585,10 @@ class CPPDomain(Domain):
docname = symbol.docname
newestId = symbol.declaration.get_newest_id()
yield (name, name, objectType, docname, newestId, 1)
+
+
+def setup(app):
+ app.add_domain(CPPDomain)
+ app.add_config_value("cpp_index_common_prefix", [], 'env')
+ app.add_config_value("cpp_id_attributes", [], 'env')
+ app.add_config_value("cpp_paren_attributes", [], 'env')
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index b5f64022a..ade6e4224 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -234,3 +234,7 @@ class JavaScriptDomain(Domain):
for refname, (docname, type) in list(self.data['objects'].items()):
yield refname, refname, type, docname, \
refname.replace('$', '_S_'), 1
+
+
+def setup(app):
+ app.add_domain(JavaScriptDomain)
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 1639d8288..d37e55fa3 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -101,11 +101,36 @@ class PyXrefMixin(object):
break
return result
+ def make_xrefs(self, rolename, domain, target, innernode=nodes.emphasis,
+ contnode=None):
+ delims = '(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)'
+ delims_re = re.compile(delims)
+ sub_targets = re.split(delims, target)
+
+ split_contnode = bool(contnode and contnode.astext() == target)
+
+ results = []
+ for sub_target in sub_targets:
+ if split_contnode:
+ contnode = nodes.Text(sub_target)
+
+ if delims_re.match(sub_target):
+ results.append(contnode or innernode(sub_target, sub_target))
+ else:
+ results.append(self.make_xref(rolename, domain, sub_target,
+ innernode, contnode))
+
+ return results
+
class PyField(PyXrefMixin, Field):
pass
+class PyGroupedField(PyXrefMixin, GroupedField):
+ pass
+
+
class PyTypedField(PyXrefMixin, TypedField):
pass
@@ -130,9 +155,9 @@ class PyObject(ObjectDescription):
names=('var', 'ivar', 'cvar'),
typerolename='obj', typenames=('vartype',),
can_collapse=True),
- GroupedField('exceptions', label=l_('Raises'), rolename='exc',
- names=('raises', 'raise', 'exception', 'except'),
- can_collapse=True),
+ PyGroupedField('exceptions', label=l_('Raises'), rolename='exc',
+ names=('raises', 'raise', 'exception', 'except'),
+ can_collapse=True),
Field('returnvalue', label=l_('Returns'), has_arg=False,
names=('returns', 'return')),
PyField('returntype', label=l_('Return type'), has_arg=False,
@@ -771,3 +796,7 @@ class PythonDomain(Domain):
for refname, (docname, type) in iteritems(self.data['objects']):
if type != 'module': # modules are already handled
yield (refname, refname, type, docname, refname, 1)
+
+
+def setup(app):
+ app.add_domain(PythonDomain)
diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py
index b11c9450b..526ae18a7 100644
--- a/sphinx/domains/rst.py
+++ b/sphinx/domains/rst.py
@@ -156,3 +156,7 @@ class ReSTDomain(Domain):
def get_objects(self):
for (typ, name), docname in iteritems(self.data['objects']):
yield name, name, typ, docname, typ + '-' + name, 1
+
+
+def setup(app):
+ app.add_domain(ReSTDomain)
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 997d95ba3..b7f2597d4 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -14,7 +14,6 @@ import unicodedata
from six import iteritems
from docutils import nodes
-from docutils.nodes import fully_normalize_name
from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
@@ -29,7 +28,7 @@ from sphinx.util.compat import Directive
# RE for option descriptions
-option_desc_re = re.compile(r'((?:/|--|-|\+)?[-?@#_a-zA-Z0-9]+)(=?\s*.*)')
+option_desc_re = re.compile(r'((?:/|--|-|\+)?[-\.?@#_a-zA-Z0-9]+)(=?\s*.*)')
# RE for grammar tokens
token_re = re.compile('`(\w+)`', re.U)
@@ -175,16 +174,18 @@ class Cmdoption(ObjectDescription):
if currprogram:
targetname = '-' + currprogram + targetname
targetname = 'cmdoption' + targetname
- signode['ids'].append(targetname)
- self.state.document.note_explicit_target(signode)
+ signode['names'].append(targetname)
+
+ self.state.document.note_explicit_target(signode)
+ for optname in signode.get('allnames', []):
self.env.domaindata['std']['progoptions'][currprogram, optname] = \
- self.env.docname, targetname
+ self.env.docname, signode['ids'][0]
# create only one index entry for the whole option
if optname == firstname:
self.indexnode['entries'].append(
('pair', _('%scommand line option; %s') %
((currprogram and currprogram + ' ' or ''), sig),
- targetname, '', None))
+ signode['ids'][0], '', None))
class Program(Directive):
@@ -467,6 +468,7 @@ class StandardDomain(Domain):
initial_data = {
'progoptions': {}, # (program, name) -> docname, labelid
'objects': {}, # (type, name) -> docname, labelid
+ 'citations': {}, # name -> docname, labelid
'labels': { # labelname -> docname, labelid, sectionname
'genindex': ('genindex', '', l_('Index')),
'modindex': ('py-modindex', '', l_('Module Index')),
@@ -486,6 +488,7 @@ class StandardDomain(Domain):
'numref': 'undefined label: %(target)s',
'keyword': 'unknown keyword: %(target)s',
'option': 'unknown option: %(target)s',
+ 'citation': 'citation not found: %(target)s',
}
enumerable_nodes = { # node_class -> (figtype, title_getter)
@@ -501,6 +504,9 @@ class StandardDomain(Domain):
for key, (fn, _l) in list(self.data['objects'].items()):
if fn == docname:
del self.data['objects'][key]
+ for key, (fn, _l) in list(self.data['citations'].items()):
+ if fn == docname:
+ del self.data['citations'][key]
for key, (fn, _l, _l) in list(self.data['labels'].items()):
if fn == docname:
del self.data['labels'][key]
@@ -516,6 +522,9 @@ class StandardDomain(Domain):
for key, data in otherdata['objects'].items():
if data[0] in docnames:
self.data['objects'][key] = data
+ for key, data in otherdata['citations'].items():
+ if data[0] in docnames:
+ self.data['citations'][key] = data
for key, data in otherdata['labels'].items():
if data[0] in docnames:
self.data['labels'][key] = data
@@ -524,6 +533,19 @@ class StandardDomain(Domain):
self.data['anonlabels'][key] = data
def process_doc(self, env, docname, document):
+ self.note_citations(env, docname, document)
+ self.note_labels(env, docname, document)
+
+ def note_citations(self, env, docname, document):
+ for node in document.traverse(nodes.citation):
+ label = node[0].astext()
+ if label in self.data['citations']:
+ path = env.doc2path(self.data['citations'][0])
+ env.warn_node('duplicate citation %s, other instance in %s' %
+ (label, path), node)
+ self.data['citations'][label] = (docname, node['ids'][0])
+
+ def note_labels(self, env, docname, document):
labels, anonlabels = self.data['labels'], self.data['anonlabels']
for name, explicit in iteritems(document.nametypes):
if not explicit:
@@ -585,106 +607,163 @@ class StandardDomain(Domain):
newnode.append(innernode)
return newnode
- def resolve_xref(self, env, fromdocname, builder,
- typ, target, node, contnode):
+ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
if typ == 'ref':
- if node['refexplicit']:
- # reference to anonymous label; the reference uses
- # the supplied link caption
- docname, labelid = self.data['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.data['labels'].get(target,
- ('', '', ''))
- if not docname:
- return None
-
- return self.build_reference_node(fromdocname, builder,
- docname, labelid, sectname, 'ref')
+ resolver = self._resolve_ref_xref
elif typ == 'numref':
+ resolver = self._resolve_numref_xref
+ elif typ == 'keyword':
+ resolver = self._resolve_keyword_xref
+ elif typ == 'option':
+ resolver = self._resolve_option_xref
+ elif typ == 'citation':
+ resolver = self._resolve_citation_xref
+ else:
+ resolver = self._resolve_obj_xref
+
+ return resolver(env, fromdocname, builder, typ, target, node, contnode)
+
+ def _resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ if node['refexplicit']:
+ # reference to anonymous label; the reference uses
+ # the supplied link caption
docname, labelid = self.data['anonlabels'].get(target, ('', ''))
- if not docname:
- return None
+ sectname = node.astext()
+ else:
+ # reference to named label; the final node will
+ # contain the section name after the label
+ docname, labelid, sectname = self.data['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, fromdocname, builder, typ, target, node, contnode):
+ if target in self.data['labels']:
+ docname, labelid, figname = self.data['labels'].get(target, ('', '', ''))
+ else:
+ docname, labelid = self.data['anonlabels'].get(target, ('', ''))
+ figname = None
- if env.config.numfig is False:
- env.warn(fromdocname, 'numfig is disabled. :numref: is ignored.',
- lineno=node.line)
- return contnode
+ if not docname:
+ return None
- target_node = env.get_doctree(docname).ids.get(labelid)
- figtype = self.get_figtype(target_node)
- if figtype is None:
- return None
+ if env.config.numfig is False:
+ env.warn_node('numfig is disabled. :numref: is ignored.', node)
+ return contnode
- try:
- figure_id = target_node['ids'][0]
- fignumber = env.toc_fignumbers[docname][figtype][figure_id]
- except (KeyError, IndexError):
- # target_node is found, but fignumber is not assigned.
- # Maybe it is defined in orphaned document.
- env.warn(fromdocname, "no number is assigned for %s: %s" % (figtype, labelid),
- lineno=node.line)
+ target_node = env.get_doctree(docname).ids.get(labelid)
+ figtype = self.get_figtype(target_node)
+ if figtype is None:
+ return None
+
+ try:
+ fignumber = self.get_fignumber(env, builder, figtype, docname, target_node)
+ if fignumber is None:
return contnode
+ except ValueError:
+ env.warn_node("no number is assigned for %s: %s" % (figtype, labelid), node)
+ return contnode
- title = contnode.astext()
- if target == fully_normalize_name(title):
+ try:
+ if node['refexplicit']:
+ title = contnode.astext()
+ else:
title = env.config.numfig_format.get(figtype, '')
- try:
- newtitle = title % '.'.join(map(str, fignumber))
- except TypeError:
- env.warn(fromdocname, 'invalid numfig_format: %s' % title,
- lineno=node.line)
- return None
-
- return self.build_reference_node(fromdocname, builder,
- docname, labelid, newtitle, 'numref',
- nodeclass=addnodes.number_reference,
- title=title)
- elif typ == 'keyword':
- # keywords are oddballs: they are referenced by named labels
- docname, labelid, _ = self.data['labels'].get(target, ('', '', ''))
- if not docname:
- return None
- return make_refnode(builder, fromdocname, docname,
- labelid, contnode)
- elif typ == 'option':
- progname = node.get('std:program')
- target = target.strip()
- docname, labelid = self.data['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.data['progoptions'].get((progname, target),
- ('', ''))
- if docname:
- break
+ if figname is None and '%{name}' in title:
+ env.warn_node('the link has no caption: %s' % title, 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:
- return None
-
- return make_refnode(builder, fromdocname, docname,
- labelid, contnode)
- else:
- objtypes = self.objtypes_for_role(typ) or []
- for objtype in objtypes:
- if (objtype, target) in self.data['objects']:
- docname, labelid = self.data['objects'][objtype, target]
+ # old style format (cf. "Fig.%s")
+ newtitle = title % fignum
+ except KeyError as exc:
+ env.warn_node('invalid numfig_format: %s (%r)' % (title, exc), node)
+ return contnode
+ except TypeError:
+ env.warn_node('invalid numfig_format: %s' % title, 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, fromdocname, builder, typ, target, node, contnode):
+ # keywords are oddballs: they are referenced by named labels
+ docname, labelid, _ = self.data['labels'].get(target, ('', '', ''))
+ if not docname:
+ return None
+ return make_refnode(builder, fromdocname, docname,
+ labelid, contnode)
+
+ def _resolve_option_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ progname = node.get('std:program')
+ target = target.strip()
+ docname, labelid = self.data['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.data['progoptions'].get((progname, target),
+ ('', ''))
+ if docname:
break
else:
- docname, labelid = '', ''
- if not docname:
return None
+
+ return make_refnode(builder, fromdocname, docname,
+ labelid, contnode)
+
+ def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ from sphinx.environment import NoUri
+
+ docname, labelid = self.data['citations'].get(target, ('', ''))
+ if not docname:
+ if 'ids' in node:
+ # remove ids attribute that annotated at
+ # transforms.CitationReference.apply.
+ del node['ids'][:]
+ return None
+
+ try:
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
+ except NoUri:
+ # remove the ids we added in the CitationReferences
+ # transform since they can't be transfered to
+ # the contnode (if it's a Text node)
+ if not isinstance(contnode, nodes.Element):
+ del node['ids'][:]
+ raise
+
+ def _resolve_obj_xref(self, env, fromdocname, builder, typ, target, node, contnode):
+ objtypes = self.objtypes_for_role(typ) or []
+ for objtype in objtypes:
+ if (objtype, target) in self.data['objects']:
+ docname, labelid = self.data['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, fromdocname, builder, target,
- node, contnode):
+ def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
results = []
ltarget = target.lower() # :ref: lowercases its target automatically
for role in ('ref', 'option'): # do not try "keyword"
@@ -747,7 +826,9 @@ class StandardDomain(Domain):
def has_child(node, cls):
return any(isinstance(child, cls) for child in node)
- if isinstance(node, nodes.container):
+ if isinstance(node, nodes.section):
+ return 'section'
+ elif isinstance(node, nodes.container):
if node.get('literal_block') and has_child(node, nodes.literal_block):
return 'code-block'
else:
@@ -755,3 +836,29 @@ class StandardDomain(Domain):
else:
figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return figtype
+
+ def get_fignumber(self, env, builder, figtype, docname, target_node):
+ 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):
+ # target_node is found, but fignumber is not assigned.
+ # Maybe it is defined in orphaned document.
+ raise ValueError
+
+
+def setup(app):
+ app.add_domain(StandardDomain)