diff options
Diffstat (limited to 'sphinx/domains')
-rw-r--r-- | sphinx/domains/__init__.py | 17 | ||||
-rw-r--r-- | sphinx/domains/c.py | 4 | ||||
-rw-r--r-- | sphinx/domains/cpp.py | 489 | ||||
-rw-r--r-- | sphinx/domains/javascript.py | 4 | ||||
-rw-r--r-- | sphinx/domains/python.py | 35 | ||||
-rw-r--r-- | sphinx/domains/rst.py | 4 | ||||
-rw-r--r-- | sphinx/domains/std.py | 287 |
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) |