mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #1715 from jakobandersen/cpp-enum-support
C++, enum support and fix of HTML search.
This commit is contained in:
commit
5d30675e5b
3
CHANGES
3
CHANGES
@ -13,6 +13,8 @@ Features added
|
||||
* The ``language`` config value is now available in the HTML templates.
|
||||
* The ``env-updated`` event can now return a value, which is interpreted
|
||||
as an iterable of additional docnames that need to be rewritten.
|
||||
* #772: Support for scoped and unscoped enums in C++. Enumerators in unscoped
|
||||
enums are injected into the parent scope in addition to the enum scope.
|
||||
* Add ``todo_include_todos`` config option to quickstart conf file, handled as
|
||||
described in documentation.
|
||||
|
||||
@ -27,6 +29,7 @@ Bugs fixed
|
||||
* #1687: linkcheck now treats 401 Unauthorized responses as "working".
|
||||
* #1690: toctrees with ``glob`` option now can also contain entries for single
|
||||
documents with explicit title.
|
||||
* #1591: html search results for C++ elements now has correct interpage links.
|
||||
|
||||
|
||||
Release 1.3b2 (released Dec 5, 2014)
|
||||
|
@ -520,8 +520,8 @@ The C++ Domain
|
||||
|
||||
The C++ domain (name **cpp**) supports documenting C++ projects.
|
||||
|
||||
The following directives are available. All declarations can start with a visibility statement
|
||||
(``public``, ``private`` or ``protected``).
|
||||
The following directives are available. All declarations can start with
|
||||
a visibility statement (``public``, ``private`` or ``protected``).
|
||||
|
||||
.. rst:directive:: .. cpp:class:: class speicifer
|
||||
|
||||
@ -578,29 +578,63 @@ The following directives are available. All declarations can start with a visibi
|
||||
|
||||
Declaration of a type alias with unspecified type.
|
||||
|
||||
.. rst:directive:: .. cpp:enum:: unscoped enum declaration
|
||||
.. cpp:enum-struct:: scoped enum declaration
|
||||
.. cpp:enum-class:: scoped enum declaration
|
||||
|
||||
Describe a (scoped) enum, possibly with the underlying type specified.
|
||||
Any enumerators declared inside an unscoped enum will be declared both in the enum scope
|
||||
and in the parent scope.
|
||||
Examples:
|
||||
|
||||
.. cpp:enum:: MyEnum
|
||||
|
||||
An unscoped enum.
|
||||
|
||||
.. cpp:enum:: MySpecificEnum : long
|
||||
|
||||
An unscoped enum with specified underlying type.
|
||||
|
||||
.. cpp:enum-class:: MyScopedEnum
|
||||
|
||||
A scoped enum.
|
||||
|
||||
.. cpp:enum-struct:: protected MyScopedVisibilityEnum : std::underlying_type<MySpecificEnum>::type
|
||||
|
||||
A scoped enum with non-default visibility, and with a specified underlying type.
|
||||
|
||||
.. rst:directive:: .. cpp:enumerator:: name
|
||||
.. cpp:enumerator:: name = constant
|
||||
|
||||
Describe an enumerator, optionally with its value defined.
|
||||
|
||||
.. rst:directive:: .. cpp:namespace:: namespace
|
||||
|
||||
Select the current namespace for the following objects. Note that the namespace
|
||||
Select the current namespace for the subsequent objects. Note that the namespace
|
||||
does not need to correspond to C++ namespaces, but can end in names of classes, e.g.,::
|
||||
|
||||
.. cpp:namespace:: Namespace1::Namespace2::SomeClass::AnInnerClass
|
||||
|
||||
All following objects will be defined as if their name were declared with the namespace
|
||||
prepended. The following cross-references will be search for by both their specified name
|
||||
All subsequent objects will be defined as if their name were declared with the namespace
|
||||
prepended. The subsequent cross-references will be searched for by both their specified name
|
||||
and with the namespace prepended.
|
||||
|
||||
Using ``NULL``, ``0``, or ``nullptr`` as the namespace will reset it to the global namespace.
|
||||
|
||||
|
||||
.. _cpp-roles:
|
||||
|
||||
These roles link to the given object types:
|
||||
|
||||
.. rst:role:: cpp:class
|
||||
cpp:func
|
||||
cpp:member
|
||||
cpp:type
|
||||
cpp:func
|
||||
cpp:member
|
||||
cpp:type
|
||||
cpp:enum
|
||||
cpp:enumerator
|
||||
|
||||
Reference a C++ object. You can give the full specification (and need to, for
|
||||
overloaded functions.)
|
||||
Reference a C++ object by name. The name must be properly qualified relative to the
|
||||
position of the link.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -121,6 +121,15 @@
|
||||
# (note: only "0" is allowed as the value, according to the standard,
|
||||
# right?)
|
||||
|
||||
enum-head ->
|
||||
enum-key attribute-specifier-seq[opt] nested-name-specifier[opt]
|
||||
identifier enum-base[opt]
|
||||
enum-key -> "enum" | "enum struct" | "enum class"
|
||||
enum-base ->
|
||||
":" type
|
||||
enumerator-definition ->
|
||||
identifier
|
||||
| identifier "=" constant-expression
|
||||
|
||||
We additionally add the possibility for specifying the visibility as the
|
||||
first thing.
|
||||
@ -139,7 +148,6 @@
|
||||
grammar, typedef-like: no initilizer
|
||||
decl-specifier-seq declerator
|
||||
|
||||
|
||||
member_object:
|
||||
goal: as a type_object which must have a declerator, and optionally
|
||||
with a initializer
|
||||
@ -150,6 +158,30 @@
|
||||
goal: a function declaration, TODO: what about templates? for now: skip
|
||||
grammar: no initializer
|
||||
decl-specifier-seq declerator
|
||||
|
||||
class_object:
|
||||
goal: a class declaration, but with specification of a base class
|
||||
TODO: what about templates? for now: skip
|
||||
grammar:
|
||||
nested-name
|
||||
| nested-name ":"
|
||||
'comma-separated list of nested-name optionally with visibility'
|
||||
|
||||
enum_object:
|
||||
goal: an unscoped enum or a scoped enum, optionally with the underlying
|
||||
type specified
|
||||
grammar:
|
||||
("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt]
|
||||
enumerator_object:
|
||||
goal: an element in a scoped or unscoped enum. The name should be
|
||||
injected according to the scopedness.
|
||||
grammar:
|
||||
nested-name ("=" constant-expression)
|
||||
|
||||
namespace_object:
|
||||
goal: a directive to put all following declarations in a specific scope
|
||||
grammar:
|
||||
nested-name
|
||||
"""
|
||||
|
||||
import re
|
||||
@ -1300,12 +1332,13 @@ class ASTBaseClass(ASTBase):
|
||||
signode += addnodes.desc_annotation(
|
||||
self.visibility, self.visibility)
|
||||
signode += nodes.Text(' ')
|
||||
self.name.describe_signature(signode, mode, env)
|
||||
self.name.describe_signature(signode, mode, env)
|
||||
|
||||
|
||||
class ASTClass(ASTBase):
|
||||
def __init__(self, name, bases):
|
||||
def __init__(self, name, visibility, bases):
|
||||
self.name = name
|
||||
self.visibility = visibility
|
||||
self.bases = bases
|
||||
|
||||
def get_id_v1(self):
|
||||
@ -1320,6 +1353,9 @@ class ASTClass(ASTBase):
|
||||
|
||||
def __unicode__(self):
|
||||
res = []
|
||||
if self.visibility != 'public':
|
||||
res.append(self.visibility)
|
||||
res.append(' ')
|
||||
res.append(text_type(self.name))
|
||||
if len(self.bases) > 0:
|
||||
res.append(' : ')
|
||||
@ -1333,6 +1369,10 @@ class ASTClass(ASTBase):
|
||||
|
||||
def describe_signature(self, signode, mode, env):
|
||||
_verify_description_mode(mode)
|
||||
if self.visibility != 'public':
|
||||
signode += addnodes.desc_annotation(
|
||||
self.visibility, self.visibility)
|
||||
signode += nodes.Text(' ')
|
||||
self.name.describe_signature(signode, mode, env)
|
||||
if len(self.bases) > 0:
|
||||
signode += nodes.Text(' : ')
|
||||
@ -1341,6 +1381,69 @@ class ASTClass(ASTBase):
|
||||
signode += nodes.Text(', ')
|
||||
signode.pop()
|
||||
|
||||
class ASTEnum(ASTBase):
|
||||
def __init__(self, name, visibility, scoped, underlyingType):
|
||||
self.name = name
|
||||
self.visibility = visibility
|
||||
self.scoped = scoped
|
||||
self.underlyingType = underlyingType
|
||||
|
||||
def get_id_v1(self):
|
||||
return None # did not exist at that time
|
||||
|
||||
def get_id_v2(self):
|
||||
return _id_prefix_v2 + self.prefixedName.get_id_v2()
|
||||
|
||||
def __unicode__(self):
|
||||
res = []
|
||||
if self.scoped:
|
||||
res.append(self.scoped)
|
||||
res.append(' ')
|
||||
if self.visibility != 'public':
|
||||
res.append(self.visibility)
|
||||
res.append(' ')
|
||||
res.append(text_type(self.name))
|
||||
if self.underlyingType:
|
||||
res.append(' : ')
|
||||
res.append(text_type(self.underlyingType))
|
||||
return u''.join(res)
|
||||
|
||||
def describe_signature(self, signode, mode, env):
|
||||
_verify_description_mode(mode)
|
||||
# self.scoped has been done by the CPPEnumObject
|
||||
if self.visibility != 'public':
|
||||
signode += addnodes.desc_annotation(
|
||||
self.visibility, self.visibility)
|
||||
signode += nodes.Text(' ')
|
||||
self.name.describe_signature(signode, mode, env)
|
||||
if self.underlyingType:
|
||||
signode += nodes.Text(' : ')
|
||||
self.underlyingType.describe_signature(signode, 'noneIsName', env)
|
||||
|
||||
class ASTEnumerator(ASTBase):
|
||||
def __init__(self, name, init):
|
||||
self.name = name
|
||||
self.init = init
|
||||
|
||||
def get_id_v1(self):
|
||||
return None # did not exist at that time
|
||||
|
||||
def get_id_v2(self):
|
||||
return _id_prefix_v2 + self.prefixedName.get_id_v2()
|
||||
|
||||
def __unicode__(self):
|
||||
res = []
|
||||
res.append(text_type(self.name))
|
||||
if self.init:
|
||||
res.append(text_type(self.init))
|
||||
return u''.join(res)
|
||||
|
||||
def describe_signature(self, signode, mode, env):
|
||||
_verify_description_mode(mode)
|
||||
self.name.describe_signature(signode, mode, env)
|
||||
if self.init:
|
||||
self.init.describe_signature(signode, 'noneIsName')
|
||||
|
||||
|
||||
class DefinitionParser(object):
|
||||
# those without signedness and size modifiers
|
||||
@ -1891,6 +1994,9 @@ class DefinitionParser(object):
|
||||
return ASTTypeWithInit(type, init)
|
||||
|
||||
def _parse_class(self):
|
||||
classVisibility = 'public'
|
||||
if self.match(_visibility_re):
|
||||
classVisibility = self.matched_text
|
||||
name = self._parse_nested_name()
|
||||
bases = []
|
||||
self.skip_ws()
|
||||
@ -1907,7 +2013,30 @@ class DefinitionParser(object):
|
||||
continue
|
||||
else:
|
||||
break
|
||||
return ASTClass(name, bases)
|
||||
return ASTClass(name, classVisibility, bases)
|
||||
|
||||
def _parse_enum(self):
|
||||
scoped = None # is set by CPPEnumObject
|
||||
self.skip_ws()
|
||||
visibility = 'public'
|
||||
if self.match(_visibility_re):
|
||||
visibility = self.matched_text
|
||||
self.skip_ws()
|
||||
name = self._parse_nested_name()
|
||||
self.skip_ws()
|
||||
underlyingType = None
|
||||
if self.skip_string(':'):
|
||||
underlyingType = self._parse_type()
|
||||
return ASTEnum(name, visibility, scoped, underlyingType)
|
||||
|
||||
def _parse_enumerator(self):
|
||||
name = self._parse_nested_name()
|
||||
self.skip_ws()
|
||||
init = None
|
||||
if self.skip_string('='):
|
||||
self.skip_ws()
|
||||
init = ASTInitializer(self.read_rest())
|
||||
return ASTEnumerator(name, init)
|
||||
|
||||
def parse_type_object(self):
|
||||
res = self._parse_type(outer='type')
|
||||
@ -1929,6 +2058,16 @@ class DefinitionParser(object):
|
||||
res.objectType = 'class'
|
||||
return res
|
||||
|
||||
def parse_enum_object(self):
|
||||
res = self._parse_enum()
|
||||
res.objectType = 'enum'
|
||||
return res
|
||||
|
||||
def parse_enumerator_object(self):
|
||||
res = self._parse_enumerator()
|
||||
res.objectType = 'enumerator'
|
||||
return res
|
||||
|
||||
def parse_namespace_object(self):
|
||||
res = self._parse_nested_name()
|
||||
res.objectType = 'namespace'
|
||||
@ -1960,6 +2099,8 @@ class CPPObject(ObjectDescription):
|
||||
ast.get_id_v1()
|
||||
]
|
||||
theid = ids[0]
|
||||
ast.newestId = theid
|
||||
assert theid # shouldn't be None
|
||||
name = text_type(ast.prefixedName)
|
||||
if theid not in self.state.document.ids:
|
||||
# if the name is not unique, the first one will win
|
||||
@ -1970,18 +2111,35 @@ class CPPObject(ObjectDescription):
|
||||
pass
|
||||
#print("[CPP] non-unique name:", name)
|
||||
for id in ids:
|
||||
signode['ids'].append(id)
|
||||
if id: # is None when the element didn't exist in that version
|
||||
signode['ids'].append(id)
|
||||
signode['first'] = (not self.names)
|
||||
self.state.document.note_explicit_target(signode)
|
||||
if name not in objects:
|
||||
objects.setdefault(name,
|
||||
(self.env.docname, ast.objectType, theid))
|
||||
if not name in objects:
|
||||
objects.setdefault(name, (self.env.docname, ast))
|
||||
if ast.objectType == 'enumerator':
|
||||
# find the parent, if it exists && is an enum
|
||||
# && it's unscoped,
|
||||
# then add the name to the parent scope
|
||||
assert len(ast.prefixedName.names) > 0
|
||||
parentPrefixedAstName = ASTNestedName(ast.prefixedName.names[:-1])
|
||||
parentPrefixedName = text_type(parentPrefixedAstName)
|
||||
if parentPrefixedName in objects:
|
||||
docname, parentAst = objects[parentPrefixedName]
|
||||
if parentAst.objectType == 'enum' and not parentAst.scoped:
|
||||
enumeratorName = ASTNestedName([ast.prefixedName.names[-1]])
|
||||
assert len(parentAst.prefixedName.names) > 0
|
||||
enumScope = ASTNestedName(parentAst.prefixedName.names[:-1])
|
||||
unscopedName = enumeratorName.prefix_nested_name(enumScope)
|
||||
txtUnscopedName = text_type(unscopedName)
|
||||
if not txtUnscopedName in objects:
|
||||
objects.setdefault(txtUnscopedName,
|
||||
(self.env.docname, ast))
|
||||
# add the uninstantiated template if it doesn't exist
|
||||
uninstantiated = ast.prefixedName.get_name_no_last_template()
|
||||
if uninstantiated != name and uninstantiated not in objects:
|
||||
signode['names'].append(uninstantiated)
|
||||
objects.setdefault(uninstantiated, (
|
||||
self.env.docname, ast.objectType, theid))
|
||||
objects.setdefault(uninstantiated, (self.env.docname, ast))
|
||||
self.env.ref_context['cpp:lastname'] = ast.prefixedName
|
||||
|
||||
indextext = self.get_index_text(name)
|
||||
@ -2057,12 +2215,14 @@ class CPPClassObject(CPPObject):
|
||||
return _('%s (C++ class)') % name
|
||||
|
||||
def before_content(self):
|
||||
lastname = self.env.ref_context['cpp:lastname']
|
||||
assert lastname
|
||||
if 'cpp:parent' in self.env.ref_context:
|
||||
self.env.ref_context['cpp:parent'].append(lastname)
|
||||
else:
|
||||
self.env.ref_context['cpp:parent'] = [lastname]
|
||||
# lastname may not be set if there was an error
|
||||
if 'cpp:lastname' in self.env.ref_context:
|
||||
lastname = self.env.ref_context['cpp:lastname']
|
||||
assert lastname
|
||||
if 'cpp:parent' in self.env.ref_context:
|
||||
self.env.ref_context['cpp:parent'].append(lastname)
|
||||
else:
|
||||
self.env.ref_context['cpp:parent'] = [lastname]
|
||||
|
||||
def after_content(self):
|
||||
self.env.ref_context['cpp:parent'].pop()
|
||||
@ -2075,6 +2235,57 @@ class CPPClassObject(CPPObject):
|
||||
ast.describe_signature(signode, 'lastIsName', self.env)
|
||||
|
||||
|
||||
class CPPEnumObject(CPPObject):
|
||||
def get_index_text(self, name):
|
||||
return _('%s (C++ enum)') % name
|
||||
|
||||
def before_content(self):
|
||||
# lastname may not be set if there was an error
|
||||
if 'cpp:lastname' in self.env.ref_context:
|
||||
lastname = self.env.ref_context['cpp:lastname']
|
||||
assert lastname
|
||||
if 'cpp:parent' in self.env.ref_context:
|
||||
self.env.ref_context['cpp:parent'].append(lastname)
|
||||
else:
|
||||
self.env.ref_context['cpp:parent'] = [lastname]
|
||||
|
||||
def after_content(self):
|
||||
self.env.ref_context['cpp:parent'].pop()
|
||||
|
||||
def parse_definition(self, parser):
|
||||
ast = parser.parse_enum_object()
|
||||
# self.objtype is set by ObjectDescription in run()
|
||||
if self.objtype == "enum":
|
||||
ast.scoped = None
|
||||
elif self.objtype == "enum-struct":
|
||||
ast.scoped = "struct"
|
||||
elif self.objtype == "enum-class":
|
||||
ast.scoped = "class"
|
||||
else:
|
||||
assert False
|
||||
return ast
|
||||
|
||||
def describe_signature(self, signode, ast):
|
||||
prefix = 'enum '
|
||||
if ast.scoped:
|
||||
prefix += ast.scoped
|
||||
prefix += ' '
|
||||
signode += addnodes.desc_annotation(prefix, prefix)
|
||||
ast.describe_signature(signode, 'lastIsName', self.env)
|
||||
|
||||
|
||||
class CPPEnumeratorObject(CPPObject):
|
||||
def get_index_text(self, name):
|
||||
return _('%s (C++ enumerator)') % name
|
||||
|
||||
def parse_definition(self, parser):
|
||||
return parser.parse_enumerator_object()
|
||||
|
||||
def describe_signature(self, signode, ast):
|
||||
signode += addnodes.desc_annotation('enumerator ', 'enumerator ')
|
||||
ast.describe_signature(signode, 'lastIsName', self.env)
|
||||
|
||||
|
||||
class CPPNamespaceObject(Directive):
|
||||
"""
|
||||
This directive is just to tell Sphinx that we're documenting stuff in
|
||||
@ -2120,7 +2331,6 @@ class CPPXRefRole(XRefRole):
|
||||
title = title[dcolon + 2:]
|
||||
return title, target
|
||||
|
||||
|
||||
class CPPDomain(Domain):
|
||||
"""C++ language domain."""
|
||||
name = 'cpp'
|
||||
@ -2129,7 +2339,9 @@ class CPPDomain(Domain):
|
||||
'class': ObjType(l_('class'), 'class'),
|
||||
'function': ObjType(l_('function'), 'func'),
|
||||
'member': ObjType(l_('member'), 'member'),
|
||||
'type': ObjType(l_('type'), 'type')
|
||||
'type': ObjType(l_('type'), 'type'),
|
||||
'enum': ObjType(l_('enum'), 'enum'),
|
||||
'enumerator': ObjType(l_('enumerator'), 'enumerator')
|
||||
}
|
||||
|
||||
directives = {
|
||||
@ -2137,16 +2349,22 @@ class CPPDomain(Domain):
|
||||
'function': CPPFunctionObject,
|
||||
'member': CPPMemberObject,
|
||||
'type': CPPTypeObject,
|
||||
'enum': CPPEnumObject,
|
||||
'enum-struct': CPPEnumObject,
|
||||
'enum-class': CPPEnumObject,
|
||||
'enumerator': CPPEnumeratorObject,
|
||||
'namespace': CPPNamespaceObject
|
||||
}
|
||||
roles = {
|
||||
'class': CPPXRefRole(),
|
||||
'func': CPPXRefRole(fix_parens=True),
|
||||
'member': CPPXRefRole(),
|
||||
'type': CPPXRefRole()
|
||||
'type': CPPXRefRole(),
|
||||
'enum': CPPXRefRole(),
|
||||
'enumerator': CPPXRefRole()
|
||||
}
|
||||
initial_data = {
|
||||
'objects': {}, # prefixedName -> (docname, objectType, id)
|
||||
'objects': {}, # prefixedName -> (docname, ast)
|
||||
}
|
||||
|
||||
def clear_doc(self, docname):
|
||||
@ -2169,9 +2387,9 @@ class CPPDomain(Domain):
|
||||
name = nameAst.get_name_no_last_template()
|
||||
if name not in self.data['objects']:
|
||||
return None, None
|
||||
docname, objectType, id = self.data['objects'][name]
|
||||
return make_refnode(builder, fromdocname, docname, id, contnode,
|
||||
name), objectType
|
||||
docname, ast = self.data['objects'][name]
|
||||
return make_refnode(builder, fromdocname, docname, ast.newestId,
|
||||
contnode, name), ast.objectType
|
||||
|
||||
parser = DefinitionParser(target)
|
||||
try:
|
||||
@ -2210,5 +2428,5 @@ class CPPDomain(Domain):
|
||||
return []
|
||||
|
||||
def get_objects(self):
|
||||
for refname, (docname, type, theid) in iteritems(self.data['objects']):
|
||||
yield (refname, refname, type, docname, refname, 1)
|
||||
for refname, (docname, ast) in iteritems(self.data['objects']):
|
||||
yield (refname, refname, ast.objectType, docname, ast.newestId, 1)
|
||||
|
@ -134,6 +134,18 @@ def test_type_definitions():
|
||||
check('function', 'int foo(const A&... a)')
|
||||
check('function', 'virtual void f()')
|
||||
|
||||
check('class', 'public A', 'A')
|
||||
check('class', 'private A')
|
||||
|
||||
check('enum', 'A')
|
||||
check('enum', 'A : std::underlying_type<B>::type')
|
||||
check('enum', 'A : unsigned int')
|
||||
check('enum', 'public A', 'A')
|
||||
check('enum', 'private A')
|
||||
|
||||
check('enumerator', 'A')
|
||||
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()')
|
||||
|
||||
def test_bases():
|
||||
check('class', 'A')
|
||||
check('class', 'A::B::C')
|
||||
@ -143,7 +155,6 @@ def test_bases():
|
||||
check('class', 'A : B, C')
|
||||
check('class', 'A : B, protected C, D')
|
||||
|
||||
|
||||
def test_operators():
|
||||
check('function', 'void operator new [ ] ()', 'void operator new[]()')
|
||||
check('function', 'void operator delete ()', 'void operator delete()')
|
||||
@ -155,4 +166,4 @@ def test_operators():
|
||||
# # used for getting all the ids out for checking
|
||||
# for a in ids:
|
||||
# print(a)
|
||||
# raise DefinitionError("")
|
||||
# raise DefinitionError("")
|
||||
|
Loading…
Reference in New Issue
Block a user