summaryrefslogtreecommitdiff
path: root/sphinx
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-04-13 23:36:49 +0900
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2020-04-13 23:36:49 +0900
commit1aca70cf8095dd87f943fe69bee97befd9be9f55 (patch)
tree7bf402c7f58c67705ae58074ebcbda7878d17acf /sphinx
parentb08a00af739ffbf5e957c0649ac4c4c8d4f866ed (diff)
parentb2a601a33dfb96c2a2257e0f9307a73377351650 (diff)
downloadsphinx-git-1aca70cf8095dd87f943fe69bee97befd9be9f55.tar.gz
Merge branch '3.x'
Diffstat (limited to 'sphinx')
-rw-r--r--sphinx/builders/latex/theming.py28
-rw-r--r--sphinx/domains/c.py174
-rw-r--r--sphinx/domains/cpp.py263
-rw-r--r--sphinx/domains/python.py24
-rw-r--r--sphinx/domains/std.py4
-rw-r--r--sphinx/environment/adapters/toctree.py2
-rw-r--r--sphinx/ext/autodoc/__init__.py7
-rw-r--r--sphinx/ext/autosummary/generate.py11
-rw-r--r--sphinx/ext/autosummary/templates/autosummary/class.rst4
-rw-r--r--sphinx/ext/autosummary/templates/autosummary/module.rst6
-rw-r--r--sphinx/pycode/ast.py5
-rw-r--r--sphinx/themes/agogo/layout.html10
-rw-r--r--sphinx/themes/basic/domainindex.html2
-rw-r--r--sphinx/themes/basic/globaltoc.html2
-rw-r--r--sphinx/themes/basic/layout.html16
-rw-r--r--sphinx/themes/basic/localtoc.html2
-rw-r--r--sphinx/themes/basic/opensearch.xml2
-rw-r--r--sphinx/themes/haiku/layout.html6
-rw-r--r--sphinx/themes/pyramid/layout.html4
-rw-r--r--sphinx/themes/scrolls/layout.html2
-rw-r--r--sphinx/util/cfamily.py181
-rw-r--r--sphinx/util/inspect.py13
-rw-r--r--sphinx/util/logging.py2
23 files changed, 471 insertions, 299 deletions
diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py
index 56f2735f0..d638639aa 100644
--- a/sphinx/builders/latex/theming.py
+++ b/sphinx/builders/latex/theming.py
@@ -66,19 +66,29 @@ class BuiltInTheme(Theme):
class UserTheme(Theme):
"""A user defined LaTeX theme."""
+ REQUIRED_CONFIG_KEYS = ['docclass', 'wrapperclass']
+ OPTIONAL_CONFIG_KEYS = ['toplevel_sectioning']
+
def __init__(self, name: str, filename: str) -> None:
- self.name = name
+ super().__init__(name)
self.config = configparser.RawConfigParser()
self.config.read(path.join(filename))
- try:
- self.docclass = self.config.get('theme', 'docclass')
- self.wrapperclass = self.config.get('theme', 'wrapperclass')
- self.toplevel_sectioning = self.config.get('theme', 'toplevel_sectioning')
- except configparser.NoSectionError:
- raise ThemeError(__('%r doesn\'t have "theme" setting') % filename)
- except configparser.NoOptionError as exc:
- raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0]))
+ for key in self.REQUIRED_CONFIG_KEYS:
+ try:
+ value = self.config.get('theme', key)
+ setattr(self, key, value)
+ except configparser.NoSectionError:
+ raise ThemeError(__('%r doesn\'t have "theme" setting') % filename)
+ except configparser.NoOptionError as exc:
+ raise ThemeError(__('%r doesn\'t have "%s" setting') % (filename, exc.args[0]))
+
+ for key in self.OPTIONAL_CONFIG_KEYS:
+ try:
+ value = self.config.get('theme', key)
+ setattr(self, key, value)
+ except configparser.NoOptionError:
+ pass
class ThemeFactory:
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index cf815bd04..c658572f5 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -35,6 +35,7 @@ from sphinx.util.cfamily import (
char_literal_re
)
from sphinx.util.docfields import Field, TypedField
+from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode
logger = logging.getLogger(__name__)
@@ -1990,6 +1991,14 @@ class DefinitionParser(BaseParser):
def language(self) -> str:
return 'C'
+ @property
+ def id_attributes(self):
+ return self.config.c_id_attributes
+
+ @property
+ def paren_attributes(self):
+ return self.config.c_paren_attributes
+
def _parse_string(self) -> str:
if self.current_char != '"':
return None
@@ -2009,66 +2018,6 @@ class DefinitionParser(BaseParser):
self.pos += 1
return self.definition[startPos:self.pos]
- def _parse_attribute(self) -> Any:
- return None
- # 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_literal(self) -> ASTLiteral:
# -> integer-literal
# | character-literal
@@ -2928,6 +2877,9 @@ class DefinitionParser(BaseParser):
assert False
return ASTDeclaration(objectType, directiveType, declaration)
+ def parse_namespace_object(self) -> ASTNestedName:
+ return self._parse_nested_name()
+
def parse_xref_object(self) -> ASTNestedName:
name = self._parse_nested_name()
# if there are '()' left, just skip them
@@ -3081,7 +3033,7 @@ class CObject(ObjectDescription):
def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
parentSymbol = self.env.temp_data['c:parent_symbol'] # type: Symbol
- parser = DefinitionParser(sig, location=signode)
+ parser = DefinitionParser(sig, location=signode, config=self.env.config)
try:
ast = self.parse_definition(parser)
parser.assert_end()
@@ -3178,6 +3130,95 @@ class CTypeObject(CObject):
object_type = 'type'
+class CNamespaceObject(SphinxDirective):
+ """
+ This directive is just to tell Sphinx that we're documenting stuff in
+ namespace foo.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {} # type: Dict
+
+ def run(self) -> List[Node]:
+ rootSymbol = self.env.domaindata['c']['root_symbol']
+ if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
+ symbol = rootSymbol
+ stack = [] # type: List[Symbol]
+ else:
+ parser = DefinitionParser(self.arguments[0],
+ location=self.get_source_info())
+ try:
+ name = parser.parse_namespace_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=self.get_source_info())
+ name = _make_phony_error_name()
+ symbol = rootSymbol.add_name(name)
+ stack = [symbol]
+ self.env.temp_data['c:parent_symbol'] = symbol
+ self.env.temp_data['c:namespace_stack'] = stack
+ self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class CNamespacePushObject(SphinxDirective):
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {} # type: Dict
+
+ def run(self) -> List[Node]:
+ if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
+ return []
+ parser = DefinitionParser(self.arguments[0],
+ location=self.get_source_info())
+ try:
+ name = parser.parse_namespace_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=self.get_source_info())
+ name = _make_phony_error_name()
+ oldParent = self.env.temp_data.get('c:parent_symbol', None)
+ if not oldParent:
+ oldParent = self.env.domaindata['c']['root_symbol']
+ symbol = oldParent.add_name(name)
+ stack = self.env.temp_data.get('c:namespace_stack', [])
+ stack.append(symbol)
+ self.env.temp_data['c:parent_symbol'] = symbol
+ self.env.temp_data['c:namespace_stack'] = stack
+ self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class CNamespacePopObject(SphinxDirective):
+ has_content = False
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {} # type: Dict
+
+ def run(self) -> List[Node]:
+ stack = self.env.temp_data.get('c:namespace_stack', None)
+ if not stack or len(stack) == 0:
+ logger.warning("C namespace pop on empty stack. Defaulting to gobal scope.",
+ location=self.get_source_info())
+ stack = []
+ else:
+ stack.pop()
+ if len(stack) > 0:
+ symbol = stack[-1]
+ else:
+ symbol = self.env.domaindata['c']['root_symbol']
+ self.env.temp_data['c:parent_symbol'] = symbol
+ self.env.temp_data['c:namespace_stack'] = stack
+ self.env.ref_context['cp:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
class CXRefRole(XRefRole):
def process_link(self, env: BuildEnvironment, refnode: Element,
has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
@@ -3214,7 +3255,8 @@ class CExprRole(SphinxRole):
def run(self) -> Tuple[List[Node], List[system_message]]:
text = self.text.replace('\n', ' ')
- parser = DefinitionParser(text, location=self.get_source_info())
+ parser = DefinitionParser(text, location=self.get_source_info(),
+ config=self.env.config)
# attempt to mimic XRefRole classes, except that...
classes = ['xref', 'c', self.class_type]
try:
@@ -3256,6 +3298,10 @@ class CDomain(Domain):
'enum': CEnumObject,
'enumerator': CEnumeratorObject,
'type': CTypeObject,
+ # scope control
+ 'namespace': CNamespaceObject,
+ 'namespace-push': CNamespacePushObject,
+ 'namespace-pop': CNamespacePopObject,
}
roles = {
'member': CXRefRole(),
@@ -3344,7 +3390,7 @@ class CDomain(Domain):
def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref,
contnode: Element) -> Tuple[Element, str]:
- parser = DefinitionParser(target, location=node)
+ parser = DefinitionParser(target, location=node, config=env.config)
try:
name = parser.parse_xref_object()
except DefinitionError as e:
@@ -3401,6 +3447,8 @@ class CDomain(Domain):
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(CDomain)
+ app.add_config_value("c_id_attributes", [], 'env')
+ app.add_config_value("c_paren_attributes", [], 'env')
return {
'version': 'builtin',
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 8787bc9ce..e09a56b06 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -21,7 +21,6 @@ from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.application import Sphinx
from sphinx.builders import Builder
-from sphinx.config import Config
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.environment import BuildEnvironment
@@ -32,7 +31,7 @@ from sphinx.transforms import SphinxTransform
from sphinx.transforms.post_transforms import ReferencesResolver
from sphinx.util import logging
from sphinx.util.cfamily import (
- NoOldIdError, ASTBaseBase, verify_description_mode, StringifyTransform,
+ NoOldIdError, ASTBaseBase, ASTAttribute, verify_description_mode, StringifyTransform,
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
hex_literal_re, binary_literal_re, float_literal_re,
@@ -770,89 +769,6 @@ class ASTNestedName(ASTBase):
################################################################################
-# Attributes
-################################################################################
-
-class ASTAttribute(ASTBase):
- def describe_signature(self, signode: TextElement) -> None:
- raise NotImplementedError(repr(self))
-
-
-class ASTCPPAttribute(ASTAttribute):
- def __init__(self, arg: str) -> None:
- self.arg = arg
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return "[[" + self.arg + "]]"
-
- def describe_signature(self, signode: TextElement) -> None:
- txt = str(self)
- signode.append(nodes.Text(txt, txt))
-
-
-class ASTGnuAttribute(ASTBase):
- def __init__(self, name: str, args: Any) -> None:
- self.name = name
- self.args = args
-
- def _stringify(self, transform: StringifyTransform) -> str:
- res = [self.name]
- if self.args:
- res.append('(')
- res.append(transform(self.args))
- res.append(')')
- return ''.join(res)
-
-
-class ASTGnuAttributeList(ASTAttribute):
- def __init__(self, attrs: List[ASTGnuAttribute]) -> None:
- self.attrs = attrs
-
- def _stringify(self, transform: StringifyTransform) -> str:
- res = ['__attribute__((']
- first = True
- for attr in self.attrs:
- if not first:
- res.append(', ')
- first = False
- res.append(transform(attr))
- res.append('))')
- return ''.join(res)
-
- def describe_signature(self, signode: TextElement) -> None:
- txt = str(self)
- signode.append(nodes.Text(txt, txt))
-
-
-class ASTIdAttribute(ASTAttribute):
- """For simple attributes defined by the user."""
-
- def __init__(self, id: str) -> None:
- self.id = id
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return self.id
-
- def describe_signature(self, signode: TextElement) -> None:
- signode.append(nodes.Text(self.id, self.id))
-
-
-class ASTParenAttribute(ASTAttribute):
- """For paren attributes defined by the user."""
-
- def __init__(self, id: str, arg: str) -> None:
- self.id = id
- self.arg = arg
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return self.id + '(' + self.arg + ')'
-
- def describe_signature(self, signode: TextElement) -> None:
- txt = str(self)
- signode.append(nodes.Text(txt, txt))
-
-
-################################################################################
# Expressions
################################################################################
@@ -4300,18 +4216,73 @@ class Symbol:
Symbol.debug_indent += 1
Symbol.debug_print("merge_with:")
assert other is not None
+
+ def unconditionalAdd(self, otherChild):
+ # TODO: hmm, should we prune by docnames?
+ self._children.append(otherChild)
+ otherChild.parent = self
+ otherChild._assert_invariants()
+
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
for otherChild in other._children:
- ourChild = self._find_first_named_symbol(
+ if Symbol.debug_lookup:
+ Symbol.debug_print("otherChild:\n", otherChild.to_string(Symbol.debug_indent))
+ Symbol.debug_indent += 1
+ if otherChild.isRedeclaration:
+ unconditionalAdd(self, otherChild)
+ if Symbol.debug_lookup:
+ Symbol.debug_print("isRedeclaration")
+ Symbol.debug_indent -= 1
+ continue
+ candiateIter = self._find_named_symbols(
identOrOp=otherChild.identOrOp,
templateParams=otherChild.templateParams,
templateArgs=otherChild.templateArgs,
templateShorthand=False, matchSelf=False,
- recurseInAnon=False, correctPrimaryTemplateArgs=False)
+ recurseInAnon=False, correctPrimaryTemplateArgs=False,
+ searchInSiblings=False)
+ candidates = list(candiateIter)
+
+ if Symbol.debug_lookup:
+ Symbol.debug_print("raw candidate symbols:", len(candidates))
+ symbols = [s for s in candidates if not s.isRedeclaration]
+ if Symbol.debug_lookup:
+ Symbol.debug_print("non-duplicate candidate symbols:", len(symbols))
+
+ if len(symbols) == 0:
+ unconditionalAdd(self, otherChild)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ continue
+
+ ourChild = None
+ if otherChild.declaration is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("no declaration in other child")
+ ourChild = symbols[0]
+ else:
+ queryId = otherChild.declaration.get_newest_id()
+ if Symbol.debug_lookup:
+ Symbol.debug_print("queryId: ", queryId)
+ for symbol in symbols:
+ if symbol.declaration is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("empty candidate")
+ # if in the end we have non matching, but have an empty one,
+ # then just continue with that
+ ourChild = symbol
+ continue
+ candId = symbol.declaration.get_newest_id()
+ if Symbol.debug_lookup:
+ Symbol.debug_print("candidate:", candId)
+ if candId == queryId:
+ ourChild = symbol
+ break
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
if ourChild is None:
- # TODO: hmm, should we prune by docnames?
- self._children.append(otherChild)
- otherChild.parent = self
- otherChild._assert_invariants()
+ unconditionalAdd(self, otherChild)
continue
if otherChild.declaration and otherChild.docname in docnames:
if not ourChild.declaration:
@@ -4326,10 +4297,14 @@ class Symbol:
# Both have declarations, and in the same docname.
# This can apparently happen, it should be safe to
# just ignore it, right?
- pass
+ # Hmm, only on duplicate declarations, right?
+ msg = "Internal C++ domain error during symbol merging.\n"
+ msg += "ourChild:\n" + ourChild.to_string(1)
+ msg += "\notherChild:\n" + otherChild.to_string(1)
+ logger.warning(msg, location=otherChild.docname)
ourChild.merge_with(otherChild, docnames, env)
if Symbol.debug_lookup:
- Symbol.debug_indent -= 1
+ Symbol.debug_indent -= 2
def add_name(self, nestedName: ASTNestedName,
templatePrefix: ASTTemplateDeclarationPrefix = None) -> "Symbol":
@@ -4608,16 +4583,18 @@ class DefinitionParser(BaseParser):
_prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')
- def __init__(self, definition: str, *,
- location: Union[nodes.Node, Tuple[str, int]],
- config: "Config") -> None:
- super().__init__(definition, location=location)
- self.config = config
-
@property
def language(self) -> str:
return 'C++'
+ @property
+ def id_attributes(self):
+ return self.config.cpp_id_attributes
+
+ @property
+ def paren_attributes(self):
+ return self.config.cpp_paren_attributes
+
def _parse_string(self) -> str:
if self.current_char != '"':
return None
@@ -4637,85 +4614,6 @@ class DefinitionParser(BaseParser):
self.pos += 1
return self.definition[startPos:self.pos]
- def _parse_balanced_token_seq(self, end: List[str]) -> str:
- # TODO: add handling of string literals and similar
- brackets = {'(': ')', '[': ']', '{': '}'}
- startPos = self.pos
- symbols = [] # type: List[str]
- 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) -> ASTAttribute:
- 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_literal(self) -> ASTLiteral:
# -> integer-literal
# | character-literal
@@ -7116,7 +7014,6 @@ class CPPDomain(Domain):
print("\tother:")
print(otherdata['root_symbol'].dump(1))
print("\tother end")
- print("merge_domaindata end")
self.data['root_symbol'].merge_with(otherdata['root_symbol'],
docnames, self.env)
@@ -7130,6 +7027,11 @@ class CPPDomain(Domain):
logger.warning(msg, location=docname)
else:
ourNames[name] = docname
+ if Symbol.debug_show_tree:
+ print("\tresult:")
+ print(self.data['root_symbol'].dump(1))
+ print("\tresult end")
+ print("merge_domaindata end")
def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref,
@@ -7137,8 +7039,7 @@ class CPPDomain(Domain):
# add parens again for those that could be functions
if typ == 'any' or typ == 'func':
target += '()'
- parser = DefinitionParser(target, location=node,
- config=env.config)
+ parser = DefinitionParser(target, location=node, config=env.config)
try:
ast, isShorthand = parser.parse_xref_object()
except DefinitionError as e:
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 59c4b4f1e..11bf0ac4d 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -76,8 +76,13 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str),
def _parse_annotation(annotation: str) -> List[Node]:
"""Parse type annotation."""
def make_xref(text: str) -> addnodes.pending_xref:
+ if text == 'None':
+ reftype = 'obj'
+ else:
+ reftype = 'class'
+
return pending_xref('', nodes.Text(text),
- refdomain='py', reftype='class', reftarget=text)
+ refdomain='py', reftype=reftype, reftarget=text)
def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute):
@@ -105,11 +110,16 @@ def _parse_annotation(annotation: str) -> List[Node]:
result.append(addnodes.desc_sig_punctuation('', ']'))
return result
elif isinstance(node, ast.Tuple):
- result = []
- for elem in node.elts:
- result.extend(unparse(elem))
- result.append(addnodes.desc_sig_punctuation('', ', '))
- result.pop()
+ if node.elts:
+ result = []
+ for elem in node.elts:
+ result.extend(unparse(elem))
+ result.append(addnodes.desc_sig_punctuation('', ', '))
+ result.pop()
+ else:
+ result = [addnodes.desc_sig_punctuation('', '('),
+ addnodes.desc_sig_punctuation('', ')')]
+
return result
else:
raise SyntaxError # unsupported syntax
@@ -1318,7 +1328,7 @@ def builtin_resolver(app: Sphinx, env: BuildEnvironment,
if node.get('refdomain') != 'py':
return None
- elif node.get('reftype') == 'obj' and node.get('reftarget') == 'None':
+ elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None':
return contnode
elif node.get('reftype') in ('class', 'exc'):
reftarget = node.get('reftarget')
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 590736e77..cae250b2a 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -303,7 +303,7 @@ def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index
term['ids'].append(node_id)
std = cast(StandardDomain, env.get_domain('std'))
- std.note_object('term', termtext.lower(), node_id, location=term)
+ std.note_object('term', termtext, node_id, location=term)
# add an index entry too
indexnode = addnodes.index()
@@ -563,7 +563,7 @@ class StandardDomain(Domain):
# links to tokens in grammar productions
'token': TokenXRefRole(),
# links to terms in glossary
- 'term': XRefRole(lowercase=True, innernodeclass=nodes.inline,
+ 'term': XRefRole(innernodeclass=nodes.inline,
warn_dangling=True),
# links to headings or arbitrary labels
'ref': XRefRole(lowercase=True, innernodeclass=nodes.inline,
diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py
index 2156156c2..1d3320af2 100644
--- a/sphinx/environment/adapters/toctree.py
+++ b/sphinx/environment/adapters/toctree.py
@@ -152,7 +152,7 @@ class TocTree:
logger.warning(__('circular toctree references '
'detected, ignoring: %s <- %s'),
ref, ' <- '.join(parents),
- location=ref)
+ location=ref, type='toc', subtype='circular')
continue
refdoc = ref
toc = self.env.tocs[ref].deepcopy()
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 7ec39a40b..f62b0c1a8 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -556,6 +556,9 @@ class Documenter:
isattr = False
doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings)
+ if not isinstance(doc, str):
+ # Ignore non-string __doc__
+ doc = None
# if the member __doc__ is the same as self's __doc__, it's just
# inherited and therefore not the member's doc
@@ -1173,7 +1176,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
return ret
def format_args(self, **kwargs: Any) -> str:
- if self.env.config.autodoc_typehints == 'none':
+ if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
# for classes, the relevant signature is the __init__ method's
@@ -1429,7 +1432,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return ret
def format_args(self, **kwargs: Any) -> str:
- if self.env.config.autodoc_typehints == 'none':
+ if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
unwrapped = inspect.unwrap(self.object)
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 6ff1fda0e..a4045ed28 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -33,7 +33,7 @@ import sphinx.locale
from sphinx import __display_version__
from sphinx import package_dir
from sphinx.builders import Builder
-from sphinx.deprecation import RemovedInSphinx40Warning
+from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.jinja2glue import BuiltinTemplateLoader
@@ -120,6 +120,11 @@ class AutosummaryRenderer:
self.env.filters['e'] = rst.escape
self.env.filters['underline'] = _underline
+ if builder:
+ if builder.app.translator:
+ self.env.add_extension("jinja2.ext.i18n")
+ self.env.install_gettext_translations(builder.app.translator) # type: ignore
+
def exists(self, template_name: str) -> bool:
"""Check if template file exists."""
try:
@@ -328,6 +333,10 @@ def find_autosummary_in_docstring(name: str, module: str = None, filename: str =
See `find_autosummary_in_lines`.
"""
+ if module:
+ warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.',
+ RemovedInSphinx50Warning)
+
try:
real_name, obj, parent, modname = import_by_name(name)
lines = pydoc.getdoc(obj).splitlines()
diff --git a/sphinx/ext/autosummary/templates/autosummary/class.rst b/sphinx/ext/autosummary/templates/autosummary/class.rst
index 8861b79a9..0f7d6f32e 100644
--- a/sphinx/ext/autosummary/templates/autosummary/class.rst
+++ b/sphinx/ext/autosummary/templates/autosummary/class.rst
@@ -8,7 +8,7 @@
.. automethod:: __init__
{% if methods %}
- .. rubric:: Methods
+ .. rubric:: {{ _('Methods') }}
.. autosummary::
{% for item in methods %}
@@ -19,7 +19,7 @@
{% block attributes %}
{% if attributes %}
- .. rubric:: Attributes
+ .. rubric:: {{ _('Attributes') }}
.. autosummary::
{% for item in attributes %}
diff --git a/sphinx/ext/autosummary/templates/autosummary/module.rst b/sphinx/ext/autosummary/templates/autosummary/module.rst
index 6ec89e05e..db3bee8b7 100644
--- a/sphinx/ext/autosummary/templates/autosummary/module.rst
+++ b/sphinx/ext/autosummary/templates/autosummary/module.rst
@@ -4,7 +4,7 @@
{% block functions %}
{% if functions %}
- .. rubric:: Functions
+ .. rubric:: {{ _('Functions') }}
.. autosummary::
{% for item in functions %}
@@ -15,7 +15,7 @@
{% block classes %}
{% if classes %}
- .. rubric:: Classes
+ .. rubric:: {{ _('Classes') }}
.. autosummary::
{% for item in classes %}
@@ -26,7 +26,7 @@
{% block exceptions %}
{% if exceptions %}
- .. rubric:: Exceptions
+ .. rubric:: {{ _('Exceptions') }}
.. autosummary::
{% for item in exceptions %}
diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py
index 4d8aa8955..fb2a7152d 100644
--- a/sphinx/pycode/ast.py
+++ b/sphinx/pycode/ast.py
@@ -114,7 +114,10 @@ def unparse(node: ast.AST) -> str:
elif isinstance(node, ast.UnaryOp):
return "%s %s" % (unparse(node.op), unparse(node.operand))
elif isinstance(node, ast.Tuple):
- return ", ".join(unparse(e) for e in node.elts)
+ if node.elts:
+ return ", ".join(unparse(e) for e in node.elts)
+ else:
+ return "()"
elif sys.version_info > (3, 6) and isinstance(node, ast.Constant):
# this branch should be placed at last
return repr(node.value)
diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html
index cfd7ee38b..26c37b10e 100644
--- a/sphinx/themes/agogo/layout.html
+++ b/sphinx/themes/agogo/layout.html
@@ -14,17 +14,17 @@
<div class="header-wrapper" role="banner">
<div class="header">
{%- if logo %}
- <p class="logo"><a href="{{ pathto(master_doc) }}">
- <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+ <p class="logo"><a href="{{ pathto(master_doc)|e }}">
+ <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/>
</a></p>
{%- endif %}
{%- block headertitle %}
<div class="headertitle"><a
- href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a></div>
+ href="{{ pathto(master_doc)|e }}">{{ shorttitle|e }}</a></div>
{%- endblock %}
<div class="rel" role="navigation" aria-label="related navigation">
{%- for rellink in rellinks|reverse %}
- <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
+ <a href="{{ pathto(rellink[0])|e }}" title="{{ rellink[1]|striptags|e }}"
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
{%- if not loop.last %}{{ reldelim2 }}{% endif %}
{%- endfor %}
@@ -78,7 +78,7 @@
<div class="left">
<div role="navigation" aria-label="related navigaton">
{%- for rellink in rellinks|reverse %}
- <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
+ <a href="{{ pathto(rellink[0])|e }}" title="{{ rellink[1]|striptags|e }}"
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
{%- if not loop.last %}{{ reldelim2 }}{% endif %}
{%- endfor %}
diff --git a/sphinx/themes/basic/domainindex.html b/sphinx/themes/basic/domainindex.html
index 931435f49..f4f742338 100644
--- a/sphinx/themes/basic/domainindex.html
+++ b/sphinx/themes/basic/domainindex.html
@@ -43,7 +43,7 @@
id="toggle-{{ groupid.next() }}" style="display: none" alt="-" />
{%- endif %}</td>
<td>{% if grouptype == 2 %}&#160;&#160;&#160;{% endif %}
- {% if page %}<a href="{{ pathto(page) }}#{{ anchor }}">{% endif -%}
+ {% if page %}<a href="{{ pathto(page)|e }}#{{ anchor }}">{% endif -%}
<code class="xref">{{ name|e }}</code>
{%- if page %}</a>{% endif %}
{%- if extra %} <em>({{ extra|e }})</em>{% endif -%}
diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html
index 49c1dac08..59cab989b 100644
--- a/sphinx/themes/basic/globaltoc.html
+++ b/sphinx/themes/basic/globaltoc.html
@@ -7,5 +7,5 @@
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
-<h3><a href="{{ pathto(master_doc) }}">{{ _('Table of Contents') }}</a></h3>
+<h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3>
{{ toctree() }}
diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html
index 9692ea2c4..0ab2afe35 100644
--- a/sphinx/themes/basic/layout.html
+++ b/sphinx/themes/basic/layout.html
@@ -32,12 +32,12 @@
<ul>
{%- for rellink in rellinks %}
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
- <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
+ <a href="{{ pathto(rellink[0])|e }}" title="{{ rellink[1]|striptags|e }}"
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
{%- endfor %}
{%- block rootrellink %}
- <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
+ <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc)|e }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
{%- endblock %}
{%- for parent in parents %}
<li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
@@ -53,8 +53,8 @@
<div class="sphinxsidebarwrapper">
{%- block sidebarlogo %}
{%- if logo %}
- <p class="logo"><a href="{{ pathto(master_doc) }}">
- <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+ <p class="logo"><a href="{{ pathto(master_doc)|e }}">
+ <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/>
</a></p>
{%- endif %}
{%- endblock %}
@@ -94,13 +94,13 @@
{%- endmacro %}
{%- macro css() %}
- <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
+ <link rel="stylesheet" href="{{ pathto('_static/' + style, 1)|e }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- for css in css_files %}
{%- if css|attr("filename") %}
{{ css_tag(css) }}
{%- else %}
- <link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
+ <link rel="stylesheet" href="{{ pathto(css, 1)|e }}" type="text/css" />
{%- endif %}
{%- endfor %}
{%- endmacro %}
@@ -108,7 +108,7 @@
{%- if html_tag %}
{{ html_tag }}
{%- else %}
-<html xmlns="http://www.w3.org/1999/xhtml"{% if language is not none %} lang="{{ language }}"{% endif %}>
+<html{% if not html5_doctype %} xmlns="http://www.w3.org/1999/xhtml"{% endif %}{% if language is not none %} lang="{{ language }}"{% endif %}>
{%- endif %}
<head>
{%- if not html5_doctype and not skip_ua_compatible %}
@@ -139,7 +139,7 @@
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %}
{%- if favicon %}
- <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
+ <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1)|e }}"/>
{%- endif %}
{%- endif %}
{%- block linktags %}
diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html
index 5ecacad0d..0e1c5511b 100644
--- a/sphinx/themes/basic/localtoc.html
+++ b/sphinx/themes/basic/localtoc.html
@@ -8,6 +8,6 @@
:license: BSD, see LICENSE for details.
#}
{%- if display_toc %}
- <h3><a href="{{ pathto(master_doc) }}">{{ _('Table of Contents') }}</a></h3>
+ <h3><a href="{{ pathto(master_doc)|e }}">{{ _('Table of Contents') }}</a></h3>
{{ toc }}
{%- endif %}
diff --git a/sphinx/themes/basic/opensearch.xml b/sphinx/themes/basic/opensearch.xml
index bac08b4a2..0cad2967c 100644
--- a/sphinx/themes/basic/opensearch.xml
+++ b/sphinx/themes/basic/opensearch.xml
@@ -7,7 +7,7 @@
template="{{ use_opensearch }}/{{ pathto('search') }}?q={searchTerms}"/>
<LongName>{{ docstitle|e }}</LongName>
{%- if favicon %}
- <Image height="16" width="16" type="image/x-icon">{{ use_opensearch }}/{{ pathto('_static/' + favicon, 1) }}</Image>
+ <Image height="16" width="16" type="image/x-icon">{{ use_opensearch }}/{{ pathto('_static/' + favicon, 1)|e }}</Image>
{%- endif %}
{% block extra %} {# Put e.g. an <Image> element here. #} {% endblock %}
</OpenSearchDescription>
diff --git a/sphinx/themes/haiku/layout.html b/sphinx/themes/haiku/layout.html
index 6dffe70df..c47b8eccc 100644
--- a/sphinx/themes/haiku/layout.html
+++ b/sphinx/themes/haiku/layout.html
@@ -21,7 +21,7 @@
«&#160;&#160;<a href="{{ prev.link|e }}">{{ prev.title }}</a>
&#160;&#160;::&#160;&#160;
{%- endif %}
- <a class="uplink" href="{{ pathto(master_doc) }}">{{ _('Contents') }}</a>
+ <a class="uplink" href="{{ pathto(master_doc)|e }}">{{ _('Contents') }}</a>
{%- if next %}
&#160;&#160;::&#160;&#160;
<a href="{{ next.link|e }}">{{ next.title }}</a>&#160;&#160;»
@@ -36,11 +36,11 @@
{%- block haikuheader %}
{%- if theme_full_logo != "false" %}
<a href="{{ pathto('index') }}">
- <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+ <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/>
</a>
{%- else %}
{%- if logo -%}
- <img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+ <img class="rightlogo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/>
{%- endif -%}
<h1 class="heading"><a href="{{ pathto('index') }}">
<span>{{ shorttitle|e }}</span></a></h1>
diff --git a/sphinx/themes/pyramid/layout.html b/sphinx/themes/pyramid/layout.html
index 2f891158d..02eec1cfb 100644
--- a/sphinx/themes/pyramid/layout.html
+++ b/sphinx/themes/pyramid/layout.html
@@ -12,8 +12,8 @@
{%- if logo %}
<div class="header" role="banner">
<div class="logo">
- <a href="{{ pathto(master_doc) }}">
- <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+ <a href="{{ pathto(master_doc)|e }}">
+ <img class="logo" src="{{ pathto('_static/' + logo, 1)|e }}" alt="Logo"/>
</a>
</div>
</div>
diff --git a/sphinx/themes/scrolls/layout.html b/sphinx/themes/scrolls/layout.html
index 7474f6f31..7866a5162 100644
--- a/sphinx/themes/scrolls/layout.html
+++ b/sphinx/themes/scrolls/layout.html
@@ -30,7 +30,7 @@
{%- if prev %}
<a href="{{ prev.link|e }}">&laquo; {{ prev.title }}</a> |
{%- endif %}
- <a href="{{ pathto(current_page_name) if current_page_name else '#' }}">{{ title }}</a>
+ <a href="{{ pathto(current_page_name)|e if current_page_name else '#' }}">{{ title }}</a>
{%- if next %}
| <a href="{{ next.link|e }}">{{ next.title }} &raquo;</a>
{%- endif %}
diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py
index 73bc62d6f..cdac9231f 100644
--- a/sphinx/util/cfamily.py
+++ b/sphinx/util/cfamily.py
@@ -16,7 +16,9 @@ from typing import (
)
from docutils import nodes
+from docutils.nodes import TextElement
+from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.util import logging
@@ -112,6 +114,92 @@ class ASTBaseBase:
return '<%s>' % self.__class__.__name__
+################################################################################
+# Attributes
+################################################################################
+
+class ASTAttribute(ASTBaseBase):
+ def describe_signature(self, signode: TextElement) -> None:
+ raise NotImplementedError(repr(self))
+
+
+class ASTCPPAttribute(ASTAttribute):
+ def __init__(self, arg: str) -> None:
+ self.arg = arg
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "[[" + self.arg + "]]"
+
+ def describe_signature(self, signode: TextElement) -> None:
+ txt = str(self)
+ signode.append(nodes.Text(txt, txt))
+
+
+class ASTGnuAttribute(ASTBaseBase):
+ def __init__(self, name: str, args: Any) -> None:
+ self.name = name
+ self.args = args
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = [self.name]
+ if self.args:
+ res.append('(')
+ res.append(transform(self.args))
+ res.append(')')
+ return ''.join(res)
+
+
+class ASTGnuAttributeList(ASTAttribute):
+ def __init__(self, attrs: List[ASTGnuAttribute]) -> None:
+ self.attrs = attrs
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['__attribute__((']
+ first = True
+ for attr in self.attrs:
+ if not first:
+ res.append(', ')
+ first = False
+ res.append(transform(attr))
+ res.append('))')
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement) -> None:
+ txt = str(self)
+ signode.append(nodes.Text(txt, txt))
+
+
+class ASTIdAttribute(ASTAttribute):
+ """For simple attributes defined by the user."""
+
+ def __init__(self, id: str) -> None:
+ self.id = id
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.id
+
+ def describe_signature(self, signode: TextElement) -> None:
+ signode.append(nodes.Text(self.id, self.id))
+
+
+class ASTParenAttribute(ASTAttribute):
+ """For paren attributes defined by the user."""
+
+ def __init__(self, id: str, arg: str) -> None:
+ self.id = id
+ self.arg = arg
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.id + '(' + self.arg + ')'
+
+ def describe_signature(self, signode: TextElement) -> None:
+ txt = str(self)
+ signode.append(nodes.Text(txt, txt))
+
+
+################################################################################
+
+
class UnsupportedMultiCharacterCharLiteral(Exception):
@property
def decoded(self) -> str:
@@ -132,9 +220,11 @@ class DefinitionError(Exception):
class BaseParser:
def __init__(self, definition: str, *,
- location: Union[nodes.Node, Tuple[str, int]]) -> None:
+ location: Union[nodes.Node, Tuple[str, int]],
+ config: "Config") -> None:
self.definition = definition.strip()
self.location = location # for warnings
+ self.config = config
self.pos = 0
self.end = len(self.definition)
@@ -252,3 +342,92 @@ class BaseParser:
self.skip_ws()
if not self.eof:
self.fail('Expected end of definition.')
+
+ ################################################################################
+
+ @property
+ def id_attributes(self):
+ raise NotImplementedError
+
+ @property
+ def paren_attributes(self):
+ raise NotImplementedError
+
+ def _parse_balanced_token_seq(self, end: List[str]) -> str:
+ # TODO: add handling of string literals and similar
+ brackets = {'(': ')', '[': ']', '{': '}'}
+ startPos = self.pos
+ symbols = [] # type: List[str]
+ 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) -> ASTAttribute:
+ 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.id_attributes:
+ if self.skip_word_and_ws(id):
+ return ASTIdAttribute(id)
+
+ # try the paren attributes defined by the user
+ for id in self.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
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 4240fd753..7b021e33c 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -17,7 +17,7 @@ import typing
import warnings
from functools import partial, partialmethod
from inspect import ( # NOQA
- Parameter, isclass, ismethod, ismethoddescriptor, unwrap
+ Parameter, isclass, ismethod, ismethoddescriptor
)
from io import StringIO
from typing import Any, Callable, Mapping, List, Tuple
@@ -116,6 +116,15 @@ def getargspec(func: Callable) -> Any:
kwonlyargs, kwdefaults, annotations)
+def unwrap(obj: Any) -> Any:
+ """Get an original object from wrapped object (wrapped functions)."""
+ try:
+ return inspect.unwrap(obj)
+ except ValueError:
+ # might be a mock object
+ return obj
+
+
def unwrap_all(obj: Any) -> Any:
"""
Get an original object from wrapped object (unwrapping partials, wrapped
@@ -217,7 +226,7 @@ def isattributedescriptor(obj: Any) -> bool:
return True
elif isdescriptor(obj):
# non data descriptor
- unwrapped = inspect.unwrap(obj)
+ unwrapped = unwrap(obj)
if isfunction(unwrapped) or isbuiltin(unwrapped) or inspect.ismethod(unwrapped):
# attribute must not be either function, builtin and method
return False
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index 1074d6c78..5526d7538 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -411,7 +411,7 @@ class WarningIsErrorFilter(logging.Filter):
message = record.msg # use record.msg itself
if location:
- raise SphinxWarning(location + ":" + message)
+ raise SphinxWarning(location + ":" + str(message))
else:
raise SphinxWarning(message)
else: