Skip to content

Commit

Permalink
Scoped rendering of enums
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobandersen committed May 1, 2020
1 parent 6da693e commit a2a1085
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 29 deletions.
1 change: 1 addition & 0 deletions breathe/renderer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def create_renderer(self, node_stack, state, document, filter_, target_handler):
self.project_info,
self,
create_node_factory(),
node_stack,
state,
document,
target_handler,
Expand Down
199 changes: 171 additions & 28 deletions breathe/renderer/sphinxrenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

debug_trace_directives = False
debug_trace_doxygen_ids = False
debug_trace_qualification = False
debug_trace_directives_indent = 0


Expand All @@ -35,6 +36,40 @@ def __exit__(self, et, ev, bt):
self.previous = None


class BaseObject:
# Use this class as the first base class to make sure the overrides are used.
# Set the content_callback attribute to a function taking a docutils node.

def transform_content(self, contentnode):
super().transform_content(contentnode)
callback = getattr(self, "breathe_content_callback", None)
if callback is None:
return
callback(contentnode)


# ----------------------------------------------------------------------------

class CPPEnumObject(BaseObject, cpp.CPPEnumObject):
pass


class CPPEnumeratorObject(BaseObject, cpp.CPPEnumeratorObject):
pass


# ----------------------------------------------------------------------------

class CEnumObject(BaseObject, c.CEnumObject):
pass


class CEnumeratorObject(BaseObject, c.CEnumeratorObject):
pass


# ----------------------------------------------------------------------------

class DoxyCPPInterfaceObject(cpp.CPPClassObject):
object_type = 'class'

Expand All @@ -49,7 +84,7 @@ def parse_definition(self, parser):


class DomainDirectiveFactory(object):
# A mapping from node kinds to cpp domain classes and directive names.
# A mapping from node kinds to domain directives and their names.
cpp_classes = {
'class': (cpp.CPPClassObject, 'class'),
'struct': (cpp.CPPClassObject, 'struct'),
Expand All @@ -58,12 +93,12 @@ class DomainDirectiveFactory(object):
'friend': (cpp.CPPFunctionObject, 'function'),
'signal': (cpp.CPPFunctionObject, 'function'),
'slot': (cpp.CPPFunctionObject, 'function'),
'enum': (cpp.CPPEnumObject, 'enum'),
'enum': (CPPEnumObject, 'enum'),
'typedef': (cpp.CPPTypeObject, 'type'),
'using': (cpp.CPPTypeObject, 'type'),
'union': (cpp.CPPUnionObject, 'union'),
'namespace': (cpp.CPPTypeObject, 'type'),
'enumvalue': (cpp.CPPEnumeratorObject, 'enumerator'),
'enumvalue': (CPPEnumeratorObject, 'enumerator'),
'define': (c.CMacroObject, 'macro'),
}
c_classes = {
Expand All @@ -72,11 +107,10 @@ class DomainDirectiveFactory(object):
'define': (c.CMacroObject, 'macro'),
'struct': (c.CStructObject, 'struct'),
'union': (c.CUnionObject, 'union'),
'enum': (c.CEnumObject, 'enum'),
'enumvalue': (c.CEnumeratorObject, 'enumerator'),
'enum': (CEnumObject, 'enum'),
'enumvalue': (CEnumeratorObject, 'enumerator'),
'typedef': (c.CTypeObject, 'type'),
}

python_classes = {
'function': (python.PyModulelevel, 'function'),
'variable': (python.PyClassmember, 'attribute')
Expand Down Expand Up @@ -238,6 +272,7 @@ def __init__(
project_info,
renderer_factory,
node_factory,
node_stack,
state,
document,
target_handler,
Expand All @@ -248,6 +283,8 @@ def __init__(
self.project_info = project_info
self.renderer_factory = renderer_factory
self.node_factory = node_factory
self.qualification_stack = node_stack
self.nesting_level = 0
self.state = state
self.document = document
self.target_handler = target_handler
Expand Down Expand Up @@ -292,16 +329,123 @@ def get_filename(node):
filename = get_filename(file_data.compounddef)
return self.project_info.domain_for_file(filename) if filename else ''

def join_nested_name(self, names):
dom = self.get_domain()
sep = '::' if not dom or dom == 'cpp' else '.'
return sep.join(names)

def run_directive(self, obj_type, names, contentCallback):
args = [obj_type, names] + self.context.directive_args[2:]
directive = DomainDirectiveFactory.create(self.context.domain, args)
assert issubclass(type(directive), BaseObject)
directive.breathe_content_callback = contentCallback

# Translate Breathe's no-link option into the standard noindex option.
if 'no-link' in self.context.directive_args[2]:
directive.options['noindex'] = True

if debug_trace_directives:
global debug_trace_directives_indent
print("{}Running directive: .. {}:: {}".format(
' ' * debug_trace_directives_indent,
directive.name, ''.join(names)))
debug_trace_directives_indent += 1

self.nesting_level += 1
nodes = directive.run()
self.nesting_level -= 1

if debug_trace_directives:
debug_trace_directives_indent -= 1

# Filter out outer class names if we are rendering a member as a part of a class content.
rst_node = nodes[1]
finder = NodeFinder(rst_node.document)
rst_node.walk(finder)

signode = finder.declarator

if len(names) > 0 and self.context.child:
signode.children = [n for n in signode.children if not n.tagname == 'desc_addname']
return nodes

def handle_declaration(self, node, declaration, *, obj_type=None, content_callback=None):
if obj_type is None:
obj_type = node.kind
if content_callback is None:
def content(contentnode):
contentnode.extend(self.description(node))
content_callback = content
declaration = declaration.replace('\n', ' ')
nodes = self.run_directive(obj_type, [declaration], content_callback)
if debug_trace_doxygen_ids:
ts = self.create_doxygen_target(node)
if len(ts) == 0:
print("{}Doxygen target: (none)".format(
' ' * debug_trace_directives_indent))
else:
print("{}Doxygen target: {}".format(
' ' * debug_trace_directives_indent, ts[0]['ids']))

rst_node = nodes[1]
finder = NodeFinder(rst_node.document)
rst_node.walk(finder)
finder.declarator.insert(0, self.create_doxygen_target(node))
return nodes

def get_qualification(self):
if self.nesting_level > 0:
return []

if debug_trace_qualification:
def debug_print_node(n):
return "node_type={}".format(n.node_type)

global debug_trace_directives_indent
print("{}{}".format(debug_trace_directives_indent * ' ',
debug_print_node(self.qualification_stack[0])))
debug_trace_directives_indent += 1

names = []
for node in self.qualification_stack[1:]:
if debug_trace_qualification:
print("{}{}".format(debug_trace_directives_indent * ' ', debug_print_node(node)))
if node.node_type == 'ref' and len(names) == 0:
if debug_trace_qualification:
print("{}{}".format(debug_trace_directives_indent * ' ', 'res='))
return []
if (node.node_type == 'compound' and
node.kind not in ['file', 'namespace', 'group']) or \
node.node_type == 'memberdef':
# We skip the 'file' entries because the file name doesn't form part of the
# qualified name for the identifier. We skip the 'namespace' entries because if we
# find an object through the namespace 'compound' entry in the index.xml then we'll
# also have the 'compounddef' entry in our node stack and we'll get it from that. We
# need the 'compounddef' entry because if we find the object through the 'file'
# entry in the index.xml file then we need to get the namespace name from somewhere
names.append(node.name)
if (node.node_type == 'compounddef' and node.kind == 'namespace'):
# Nested namespaces include their parent namespace(s) in compoundname. ie,
# compoundname is 'foo::bar' instead of just 'bar' for namespace 'bar' nested in
# namespace 'foo'. We need full compoundname because node_stack doesn't necessarily
# include parent namespaces and we stop here in case it does.
names.extend(node.compoundname.split('::'))
break

names.reverse()

if debug_trace_qualification:
print("{}res={}".format(debug_trace_directives_indent * ' ', names))
debug_trace_directives_indent -= 1
return names

# ===================================================================================

def get_fully_qualified_name(self):

names = []
node_stack = self.context.node_stack
node = node_stack[0]
if node.node_type == 'enumvalue':
names.append(node.name)
# Skip the name of the containing enum because it is not a part of the
# fully qualified name.
node_stack = node_stack[2:]

# If the node is a namespace, use its name because namespaces are skipped in the main loop.
if node.node_type == 'compound' and node.kind == 'namespace':
Expand Down Expand Up @@ -1087,16 +1231,23 @@ def update_define_signature(signature, obj_type):
return self.render_declaration(node, declaration, update_signature=update_define_signature)

def visit_enum(self, node):
name = self.get_fully_qualified_name()
declaration = name
def content(contentnode):
contentnode.extend(self.description(node))
values = self.node_factory.emphasis("", self.node_factory.Text("Values:"))
title = self.node_factory.paragraph("", "", values)
contentnode += title
enums = self.render_iterable(node.enumvalue)
contentnode.extend(enums)
# TODO: scopedness, Doxygen doesn't seem to generate the xml for that
# TODO: underlying type, Doxygen doesn't seem to generate the xml for that
names = self.get_qualification()
names.append(node.name)
declaration = self.join_nested_name(names)
return self.handle_declaration(node, declaration, content_callback=content)

description_nodes = self.description(node)
name = self.node_factory.emphasis("", self.node_factory.Text("Values:"))
title = self.node_factory.paragraph("", "", name)
description_nodes.append(title)
enums = self.render_iterable(node.enumvalue)
description_nodes.extend(enums)
return self.render_declaration(node, declaration, description_nodes)
def visit_enumvalue(self, node):
declaration = node.name + self.make_initializer(node)
return self.handle_declaration(node, declaration, obj_type='enumvalue')

def visit_typedef(self, node):
declaration = get_definition_without_template_args(node)
Expand Down Expand Up @@ -1149,14 +1300,6 @@ def visit_variable(self, node):
declaration = self.create_template_prefix(node) + declaration
return self.render_declaration(node, declaration)

def visit_enumvalue(self, node):
def update_signature(signature, obj_type):
# TODO: should the prefix still be removed after Sphinx supoprts enumerators?
signature.children.pop(0)
declaration = self.get_fully_qualified_name() + self.make_initializer(node)
return self.render_declaration(node, declaration=declaration,
objtype='enumvalue', update_signature=update_signature)

def visit_param(self, node):

nodelist = []
Expand Down
1 change: 1 addition & 0 deletions documentation/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
"define":"../../examples/specific/define/xml/",
"multifile":"../../examples/specific/multifilexml/xml/",
"cpp_anon":"../../examples/specific/cpp_anon/xml/",
"cpp_enum":"../../examples/specific/cpp_enum/xml/",
}

breathe_projects_source = {
Expand Down
8 changes: 8 additions & 0 deletions documentation/source/specific.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,11 @@ C++ Anonymous Entities

.. doxygenfile:: cpp_anon.h
:project: cpp_anon

C++ Enums
---------

.. cpp:namespace:: @ex_specific_cpp_enum

.. doxygenfile:: cpp_enum.h
:project: cpp_enum
2 changes: 1 addition & 1 deletion examples/specific/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ projects = nutshell alias rst inline namespacefile array inheritance \
headings links parameters template_class template_class_non_type \
template_function template_type_alias template_specialisation \
enum define interface \
cpp_anon \
cpp_anon cpp_enum \
c_file c_struct c_enum c_typedef c_macro c_union

special = programlisting decl_impl multifilexml auto class typedef
Expand Down
11 changes: 11 additions & 0 deletions examples/specific/cpp_enum.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PROJECT_NAME = "C++ Enum"
OUTPUT_DIRECTORY = cpp_enum
GENERATE_LATEX = NO
GENERATE_MAN = NO
GENERATE_RTF = NO
CASE_SENSE_NAMES = NO
INPUT = cpp_enum.h
QUIET = YES
JAVADOC_AUTOBRIEF = YES
GENERATE_HTML = NO
GENERATE_XML = YES
11 changes: 11 additions & 0 deletions examples/specific/cpp_enum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
enum Unscoped : int {
UnscopedEnumerator = 42
};

enum struct ScopedStruct : int {
Enumerator = 42
};

enum class ScopedClass : int {
Enumerator = 42
};
1 change: 1 addition & 0 deletions tests/test_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ def render(app, member_def, domain=None, show_define_initializer=False):
renderer = SphinxRenderer(MockProjectInfo(show_define_initializer),
None, # renderer_factory
create_node_factory(),
[], # node_stack
None, # state
None, # document
MockTargetHandler(),
Expand Down

0 comments on commit a2a1085

Please sign in to comment.