diff --git a/breathe/renderer/__init__.py b/breathe/renderer/__init__.py index 3b625f13..7425cfe4 100644 --- a/breathe/renderer/__init__.py +++ b/breathe/renderer/__init__.py @@ -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, diff --git a/breathe/renderer/sphinxrenderer.py b/breathe/renderer/sphinxrenderer.py index 2513e2e0..cfd7c6e7 100644 --- a/breathe/renderer/sphinxrenderer.py +++ b/breathe/renderer/sphinxrenderer.py @@ -13,6 +13,12 @@ import textwrap +debug_trace_directives = False +debug_trace_doxygen_ids = False +debug_trace_qualification = False +debug_trace_directives_indent = 0 + + class WithContext(object): def __init__(self, parent, context): self.context = context @@ -30,51 +36,136 @@ def __exit__(self, et, ev, bt): self.previous = None -class DoxyCPPInterfaceObject(cpp.CPPClassObject): - object_type = 'class' +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 CPPClassObject(BaseObject, cpp.CPPClassObject): + pass + + +class CPPUnionObject(BaseObject, cpp.CPPUnionObject): + pass + + +class CPPFunctionObject(BaseObject, cpp.CPPFunctionObject): + pass + + +class CPPMemberObject(BaseObject, cpp.CPPMemberObject): + pass + + +class CPPTypeObject(BaseObject, cpp.CPPTypeObject): + pass + + +class CPPEnumObject(BaseObject, cpp.CPPEnumObject): + pass + + +class CPPEnumeratorObject(BaseObject, cpp.CPPEnumeratorObject): + pass + + +# ---------------------------------------------------------------------------- + +class CStructObject(BaseObject, c.CStructObject): + pass + + +class CUnionObject(BaseObject, c.CUnionObject): + pass + + +class CFunctionObject(BaseObject, c.CFunctionObject): + pass + + +class CMemberObject(BaseObject, c.CMemberObject): + pass + + +class CTypeObject(BaseObject, c.CTypeObject): + pass + + +class CEnumObject(BaseObject, c.CEnumObject): + pass + + +class CEnumeratorObject(BaseObject, c.CEnumeratorObject): + pass + + +class CMacroObject(BaseObject, c.CMacroObject): + pass + + +# ---------------------------------------------------------------------------- + +class PyFunction(BaseObject, python.PyFunction): + pass + + +class PyAttribute(BaseObject, python.PyAttribute): + pass - @property - def display_object_type(self): - # override because we have the 'interface' type - assert self.objtype == 'interface' - return 'class' # TODO: change to 'interface' if that is the desired rendering - def parse_definition(self, parser): - return parser.parse_declaration("class", "class") +class PyClasslike(BaseObject, python.PyClasslike): + pass +# ---------------------------------------------------------------------------- + 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'), - 'interface': (DoxyCPPInterfaceObject, 'interface'), - 'function': (cpp.CPPFunctionObject, 'function'), - 'friend': (cpp.CPPFunctionObject, 'function'), - 'signal': (cpp.CPPFunctionObject, 'function'), - 'slot': (cpp.CPPFunctionObject, 'function'), - 'enum': (cpp.CPPEnumObject, 'enum'), - 'typedef': (cpp.CPPTypeObject, 'type'), - 'using': (cpp.CPPTypeObject, 'type'), - 'union': (cpp.CPPUnionObject, 'union'), - 'namespace': (cpp.CPPTypeObject, 'type'), - 'enumvalue': (cpp.CPPEnumeratorObject, 'enumerator'), - 'define': (c.CMacroObject, 'macro'), + 'variable': (CPPMemberObject, 'var'), + 'class': (CPPClassObject, 'class'), + 'struct': (CPPClassObject, 'struct'), + 'interface': (CPPClassObject, 'class'), + 'function': (CPPFunctionObject, 'function'), + 'friend': (CPPFunctionObject, 'function'), + 'signal': (CPPFunctionObject, 'function'), + 'slot': (CPPFunctionObject, 'function'), + 'enum': (CPPEnumObject, 'enum'), + 'typedef': (CPPTypeObject, 'type'), + 'using': (CPPTypeObject, 'type'), + 'union': (CPPUnionObject, 'union'), + 'namespace': (CPPTypeObject, 'type'), + 'enumvalue': (CPPEnumeratorObject, 'enumerator'), + 'define': (CMacroObject, 'macro'), } c_classes = { - 'variable': (c.CMemberObject, 'var'), - 'function': (c.CFunctionObject, 'function'), - 'define': (c.CMacroObject, 'macro'), - 'struct': (c.CStructObject, 'struct'), - 'union': (c.CUnionObject, 'union'), - 'enum': (c.CEnumObject, 'enum'), - 'enumvalue': (c.CEnumeratorObject, 'enumerator'), - 'typedef': (c.CTypeObject, 'type'), + 'variable': (CMemberObject, 'var'), + 'function': (CFunctionObject, 'function'), + 'define': (CMacroObject, 'macro'), + 'struct': (CStructObject, 'struct'), + 'union': (CUnionObject, 'union'), + 'enum': (CEnumObject, 'enum'), + 'enumvalue': (CEnumeratorObject, 'enumerator'), + 'typedef': (CTypeObject, 'type'), } - python_classes = { - 'function': (python.PyModulelevel, 'function'), - 'variable': (python.PyClassmember, 'attribute') + # TODO: PyFunction is meant for module-level functions + # and PyAttribute is meant for class attributes, not module-level variables. + # Somehow there should be made a distinction at some point to get the correct + # index-text and whatever other things are different. + 'function': (PyFunction, 'function'), + 'variable': (PyAttribute, 'attribute'), + 'class': (PyClasslike, 'class'), + 'namespace': (PyClasslike, 'class'), } if php is not None: @@ -86,26 +177,12 @@ class DomainDirectiveFactory(object): 'global': (php.PhpGloballevel, 'global'), } - @staticmethod - def fix_python_signature(sig): - def_ = 'def ' - if sig.startswith(def_): - sig = sig[len(def_):] - # Doxygen uses an invalid separator ('::') in Python signatures. Replace them with '.'. - return sig.replace('::', '.') - @staticmethod def create(domain, args): if domain == 'c': - if args[0] not in DomainDirectiveFactory.c_classes: - print("Unknown C directive:", args[0]) - print("args:", args) cls, name = DomainDirectiveFactory.c_classes[args[0]] - args[1] = [n.replace('::', '.') for n in args[1]] elif domain == 'py': - cls, name = DomainDirectiveFactory.python_classes.get( - args[0], (python.PyClasslike, 'class')) - args[1] = [DomainDirectiveFactory.fix_python_signature(n) for n in args[1]] + cls, name = DomainDirectiveFactory.python_classes[args[0]] elif php is not None and domain == 'php': separators = php.separators arg_0 = args[0] @@ -121,8 +198,7 @@ def create(domain, args): arg_0, (php.PhpClasslike, 'class')) else: domain = 'cpp' - cls, name = DomainDirectiveFactory.cpp_classes.get( - args[0], (cpp.CPPMemberObject, 'member')) + cls, name = DomainDirectiveFactory.cpp_classes[args[0]] # Replace the directive name because domain directives don't know how to handle # Breathe's "doxygen" directives. assert ':' not in name @@ -233,6 +309,7 @@ def __init__( project_info, renderer_factory, node_factory, + node_stack, state, document, target_handler, @@ -243,6 +320,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 @@ -287,16 +366,157 @@ 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, options={}): + 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 + for k, v in options.items(): + directive.options[k] = v + + 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 + + # TODO: the directive_args seems to be reused between different run_directives + # so for now, reset the options. + # Remove this once the args are given in a different manner. + for k, v in options.items(): + del directive.options[k] + + 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, + display_obj_type=None, declarator_callback=None, options={}): + 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, options) + 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'])) + + # and then one or more + # each has a sphinx_line_type which hints what is present in that line + assert len(nodes) >= 1 + desc = nodes[1] + assert desc.__class__.__name__ == "desc" + assert len(desc) >= 1 + sig = desc[0] + assert sig.__class__.__name__ == "desc_signature" + # if may or may not be a multiline signature + isMultiline = sig.get('is_multiline', False) + if isMultiline: + declarator = None + for line in sig: + assert line.__class__.__name__ == "desc_signature_line" + if line.sphinx_line_type == 'declarator': + declarator = line + else: + declarator = sig + assert declarator is not None + if display_obj_type is not None: + n = declarator[0] + assert n.__class__.__name__ == "desc_annotation" + assert n.astext()[-1] == " " + txt = display_obj_type + ' ' + declarator[0] = self.node_factory.desc_annotation(txt, txt) + target = self.create_doxygen_target(node) + declarator.insert(0, target) + if declarator_callback: + declarator_callback(declarator) + 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': @@ -338,8 +558,19 @@ def run_domain_directive(self, kind, names): # Translate Breathe's no-link option into the standard noindex option. if 'no-link' in self.context.directive_args[2]: domain_directive.options['noindex'] = True + + if debug_trace_directives: + global debug_trace_directives_indent + print("{}Running directive (old): .. {}:: {}".format( + ' ' * debug_trace_directives_indent, + domain_directive.name, ''.join(names))) + debug_trace_directives_indent += 1 + nodes = domain_directive.run() + 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) @@ -394,6 +625,14 @@ def render_declaration(self, node, declaration=None, description=None, **kwargs) if obj_type is None: obj_type = node.kind nodes = self.run_domain_directive(obj_type, [declaration.replace('\n', ' ')]) + if debug_trace_doxygen_ids: + ts = self.create_doxygen_target(node) + if len(ts) == 0: + print("{}Doxygen target (old): (none)".format( + ' ' * debug_trace_directives_indent)) + else: + print("{}Doxygen target (old): {}".format( + ' ' * debug_trace_directives_indent, ts[0]['ids'])) rst_node = nodes[1] finder = NodeFinder(rst_node.document) @@ -448,21 +687,143 @@ def visit_doxygen(self, node): def visit_doxygendef(self, node): return self.render(node.compounddef) - def visit_compound(self, node, render_empty_node=True, **kwargs): + def visit_union(self, node): + # Read in the corresponding xml file and process + file_data = self.compound_parser.parse(node.refid) + nodeDef = file_data.compounddef + + parent_context = self.context.create_child_context(file_data) + new_context = parent_context.create_child_context(nodeDef) + + with WithContext(self, new_context): + names = self.get_qualification() + if self.nesting_level == 0: + names.extend(nodeDef.compoundname.split('::')) + else: + names.append(nodeDef.compoundname.split('::')[-1]) + declaration = self.join_nested_name(names) + + def content(contentnode): + if nodeDef.includes: + for include in nodeDef.includes: + contentnode.extend(self.render(include, + new_context.create_child_context(include))) + rendered_data = self.render(file_data, parent_context) + contentnode.extend(rendered_data) + nodes = self.handle_declaration(nodeDef, declaration, content_callback=content) + return nodes + + def visit_class(self, node): + # Read in the corresponding xml file and process + file_data = self.compound_parser.parse(node.refid) + nodeDef = file_data.compounddef + + parent_context = self.context.create_child_context(file_data) + new_context = parent_context.create_child_context(nodeDef) + + with WithContext(self, new_context): + # Pretend that the signature is being rendered in context of the + # definition, for proper domain detection + kind = nodeDef.kind + # Defer to domains specific directive. + + names = self.get_qualification() + # TODO: this breaks if it's a template specialization + # and one of the arguments contain '::' + if self.nesting_level == 0: + names.extend(nodeDef.compoundname.split('::')) + else: + names.append(nodeDef.compoundname.split('::')[-1]) + decls = [ + self.create_template_prefix(nodeDef), + self.join_nested_name(names), + ] + # add base classes + if len(nodeDef.basecompoundref) != 0: + decls.append(':') + first = True + for base in nodeDef.basecompoundref: + if not first: + decls.append(',') + else: + first = False + if base.prot is not None: + decls.append(base.prot) + if base.virt == 'virtual': + decls.append('virtual') + decls.append(base.content_[0].value) + declaration = ' '.join(decls) + + def content(contentnode): + if nodeDef.includes: + for include in nodeDef.includes: + contentnode.extend(self.render(include, + new_context.create_child_context(include))) + rendered_data = self.render(file_data, parent_context) + contentnode.extend(rendered_data) + + assert kind in ('class', 'struct', 'interface') + display_obj_type = 'interface' if kind == 'interface' else None + nodes = self.handle_declaration(nodeDef, declaration, content_callback=content, + display_obj_type=display_obj_type) + return nodes + def visit_namespace(self, node): # Read in the corresponding xml file and process file_data = self.compound_parser.parse(node.refid) + nodeDef = file_data.compounddef parent_context = self.context.create_child_context(file_data) new_context = parent_context.create_child_context(file_data.compounddef) - rendered_data = self.render(file_data, parent_context) - if not rendered_data and not render_empty_node: - return [] + with WithContext(self, new_context): + # Pretend that the signature is being rendered in context of the + # definition, for proper domain detection + names = self.get_qualification() + if self.nesting_level == 0: + names.extend(nodeDef.compoundname.split('::')) + else: + names.append(nodeDef.compoundname.split('::')[-1]) + declaration = self.join_nested_name(names) + + def content(contentnode): + if nodeDef.includes: + for include in nodeDef.includes: + contentnode.extend(self.render(include, + new_context.create_child_context(include))) + rendered_data = self.render(file_data, parent_context) + contentnode.extend(rendered_data) + display_obj_type = 'namespace' if self.get_domain() != 'py' else 'module' + nodes = self.handle_declaration(nodeDef, declaration, content_callback=content, + display_obj_type=display_obj_type) + return nodes + + def visit_compound(self, node, render_empty_node=True, **kwargs): + # Read in the corresponding xml file and process + file_data = self.compound_parser.parse(node.refid) def get_node_info(file_data): return node.name, node.kind name, kind = kwargs.get('get_node_info', get_node_info)(file_data) + if kind == 'union': + dom = self.get_domain() + assert not dom or dom in ('c', 'cpp') + return self.visit_union(node) + elif kind in ('struct', 'class', 'interface'): + dom = self.get_domain() + if not dom or dom in ('c', 'cpp', 'py'): + return self.visit_class(node) + elif kind == 'namespace': + dom = self.get_domain() + if not dom or dom in ('c', 'cpp', 'py'): + return self.visit_namespace(node) + + parent_context = self.context.create_child_context(file_data) + new_context = parent_context.create_child_context(file_data.compounddef) + rendered_data = self.render(file_data, parent_context) + + if not rendered_data and not render_empty_node: + return [] def render_signature(file_data, doxygen_target, name, kind): # Defer to domains specific directive. @@ -993,49 +1354,75 @@ def visit_linkedtext(self, node): return self.render_iterable(node.content_) def visit_function(self, node): - # Get full function signature for the domain directive. - param_list = [] - for param in node.param: - param = self.context.mask_factory.mask(param) - param_decl = get_param_decl(param) - param_list.append(param_decl) - templatePrefix = self.create_template_prefix(node) - signature = '{0}{1}({2})'.format( - templatePrefix, - get_definition_without_template_args(node), - ', '.join(param_list)) - - # Add CV-qualifiers. - if node.const == 'yes': - signature += ' const' - # The doxygen xml output doesn't register 'volatile' as the xml attribute for functions - # until version 1.8.8 so we also check argsstring: - # https://bugzilla.gnome.org/show_bug.cgi?id=733451 - if node.volatile == 'yes' or node.argsstring.endswith('volatile'): - signature += ' volatile' - - if node.refqual == 'lvalue': - signature += '&' - elif node.refqual == 'rvalue': - signature += '&&' - - # Add `= 0` for pure virtual members. - if node.virt == 'pure-virtual': - signature += '= 0' - - self.context.directive_args[1] = [signature] - - nodes = self.run_domain_directive(node.kind, self.context.directive_args[1]) - rst_node = nodes[1] - finder = NodeFinder(rst_node.document) - rst_node.walk(finder) + dom = self.get_domain() + if not dom or dom in ('c', 'cpp', 'py'): + names = self.get_qualification() + names.append(node.get_name()) + name = self.join_nested_name(names) + if dom == 'py': + declaration = name + node.get_argsstring() + else: + declaration = ' '.join([ + self.create_template_prefix(node), + ''.join(n.astext() for n in self.render(node.get_type())), + name, + node.get_argsstring() + ]) + nodes = self.handle_declaration(node, declaration) + return nodes + else: + # Get full function signature for the domain directive. + param_list = [] + for param in node.param: + param = self.context.mask_factory.mask(param) + param_decl = get_param_decl(param) + param_list.append(param_decl) + templatePrefix = self.create_template_prefix(node) + signature = '{0}{1}({2})'.format( + templatePrefix, + get_definition_without_template_args(node), + ', '.join(param_list)) + + # Add CV-qualifiers. + if node.const == 'yes': + signature += ' const' + # The doxygen xml output doesn't register 'volatile' as the xml attribute for functions + # until version 1.8.8 so we also check argsstring: + # https://bugzilla.gnome.org/show_bug.cgi?id=733451 + if node.volatile == 'yes' or node.argsstring.endswith('volatile'): + signature += ' volatile' + + if node.refqual == 'lvalue': + signature += '&' + elif node.refqual == 'rvalue': + signature += '&&' + + # Add `= 0` for pure virtual members. + if node.virt == 'pure-virtual': + signature += '= 0' + + self.context.directive_args[1] = [signature] + + nodes = self.run_domain_directive(node.kind, self.context.directive_args[1]) + if debug_trace_doxygen_ids: + ts = self.create_doxygen_target(node) + if len(ts) == 0: + print("{}Doxygen target (old): (none)".format( + ' ' * debug_trace_directives_indent)) + else: + print("{}Doxygen target (old): {}".format( + ' ' * debug_trace_directives_indent, ts[0]['ids'])) + + rst_node = nodes[1] + finder = NodeFinder(rst_node.document) + rst_node.walk(finder) - # Templates have multiple signature nodes in recent versions of Sphinx. - # Insert Doxygen target into the first signature node. - rst_node.children[0].insert(0, self.create_doxygen_target(node)) + # Templates have multiple signature nodes in recent versions of Sphinx. + # Insert Doxygen target into the first signature node. + rst_node.children[0].insert(0, self.create_doxygen_target(node)) - finder.content.extend(self.description(node)) - return nodes + finder.content.extend(self.description(node)) + return nodes def visit_define(self, node): declaration = node.name @@ -1047,51 +1434,45 @@ def visit_define(self, node): declaration += parameter.defname declaration += ")" - def update_define_signature(signature, obj_type): + # TODO: remove this once Sphinx supports definitions for macros + def add_definition(declarator): if node.initializer and self.project_info.show_define_initializer(): - signature.extend([self.node_factory.Text(" ")] + self.render(node.initializer)) + declarator.extend([self.node_factory.Text(" ")] + self.render(node.initializer)) - return self.render_declaration(node, declaration, update_signature=update_define_signature) + return self.handle_declaration(node, declaration, declarator_callback=add_definition) 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) - typedef = "typedef " - using = "using " - obj_type = node.kind - if declaration.startswith(typedef): - declaration = declaration[len(typedef):] - elif declaration.startswith(using): - declaration = declaration[len(using):] - # remove the spurious "typedef " on right hand side added - # by Doxygen observed when type is declared inside a namespace - # See examples/specific/using_in_ns - declaration = declaration.replace(" = typedef ", " = ") - obj_type = "using" - - def update_signature(signature, obj_type): - """Update the signature node if necessary, e.g. add qualifiers.""" - prefix = obj_type + ' ' - annotation = self.node_factory.desc_annotation(prefix, prefix) - if signature[0].tagname != 'desc_annotation': - signature.insert(0, annotation) - else: - signature[0] = annotation - - declaration = self.create_template_prefix(node) + declaration - return self.render_declaration(node, declaration, objtype=obj_type, - update_signature=update_signature) + type_ = ''.join(n.astext() for n in self.render(node.get_type())) + names = self.get_qualification() + names.append(node.get_name()) + name = self.join_nested_name(names) + if node.definition.startswith('typedef '): + declaration = ' '.join([type_, name, node.get_argsstring()]) + elif node.definition.startswith('using '): + # TODO: looks like Doxygen does not generate the proper XML + # for the template paramter list + declaration = self.create_template_prefix(node) + declaration += ' ' + name + " = " + type_ + return self.handle_declaration(node, declaration) def make_initializer(self, node): initializer = node.initializer @@ -1108,21 +1489,47 @@ def make_initializer(self, node): return ''.join(n.astext() for n in signature) def visit_variable(self, node): - declaration = get_definition_without_template_args(node) - enum = 'enum ' - if declaration.startswith(enum): - declaration = declaration[len(enum):] - declaration += self.make_initializer(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) + names = self.get_qualification() + names.append(node.name) + name = self.join_nested_name(names) + dom = self.get_domain() + options = {} + if dom == 'py': + declaration = name + initializer = self.make_initializer(node).strip().lstrip('=').strip() + if len(initializer) != 0: + options['value'] = initializer + else: + declaration = ' '.join([ + self.create_template_prefix(node), + ''.join(n.astext() for n in self.render(node.get_type())), + name, + node.get_argsstring(), + self.make_initializer(node) + ]) + if not dom or dom in ('c', 'cpp', 'py'): + return self.handle_declaration(node, declaration, options=options) + else: + return self.render_declaration(node, declaration) + + def visit_friendclass(self, node): + dom = self.get_domain() + assert not dom or dom == 'cpp' + + assert ''.join(n.astext() for n in self.render(node.get_type())) == "friend class" + desc = self.node_factory.desc() + desc['objtype'] = 'friendclass' + signode = self.node_factory.desc_signature() + desc += signode + + signode += self.node_factory.desc_annotation('friend class') + signode += self.node_factory.Text(' ') + # expr = cpp.CPPExprRole(asCode=False) + # expr.text = node.name + # TODO: set most of the things that SphinxRole.__call__ sets + # signode.extend(expr.run()) + signode += self.node_factory.Text(node.name) + return [desc] def visit_param(self, node): @@ -1253,6 +1660,9 @@ def dispatch_memberdef(self, node): return self.visit_variable(node) if node.kind == "define": return self.visit_define(node) + if node.kind == "friend": + # note, friend functions should be dispatched further up + return self.visit_friendclass(node) return self.render_declaration(node, update_signature=self.update_signature) # A mapping from node types to corresponding dispatch and visit methods. diff --git a/documentation/source/conf.py b/documentation/source/conf.py index 85a8c06c..ec4f7774 100644 --- a/documentation/source/conf.py +++ b/documentation/source/conf.py @@ -216,6 +216,11 @@ "define":"../../examples/specific/define/xml/", "multifile":"../../examples/specific/multifilexml/xml/", "cpp_anon":"../../examples/specific/cpp_anon/xml/", + "cpp_enum":"../../examples/specific/cpp_enum/xml/", + "cpp_union":"../../examples/specific/cpp_union/xml/", + "cpp_function":"../../examples/specific/cpp_function/xml/", + "cpp_inherited_members":"../../examples/specific/cpp_inherited_members/xml/", + "cpp_trailing_return_type":"../../examples/specific/cpp_trailing_return_type/xml/", } breathe_projects_source = { diff --git a/documentation/source/members.rst b/documentation/source/members.rst index 805bcb8d..828eda97 100644 --- a/documentation/source/members.rst +++ b/documentation/source/members.rst @@ -38,3 +38,6 @@ Struct Members .. doxygenfunction:: testnamespace::MyClass::MyClass(const MyClass&) :path: ../../examples/specific/struct_function/xml + +.. doxygenvariable:: testnamespace::MyClass::myMemberVar + :path: ../../examples/specific/struct_function/xml diff --git a/documentation/source/specific.rst b/documentation/source/specific.rst index dc3a6402..feaceeb2 100644 --- a/documentation/source/specific.rst +++ b/documentation/source/specific.rst @@ -178,3 +178,43 @@ C++ Anonymous Entities .. doxygenfile:: cpp_anon.h :project: cpp_anon + +C++ Union +--------- + +.. cpp:namespace:: @ex_specific_cpp_union + +.. doxygenfile:: cpp_union.h + :project: cpp_union + +C++ Enums +--------- + +.. cpp:namespace:: @ex_specific_cpp_enum + +.. doxygenfile:: cpp_enum.h + :project: cpp_enum + +C++ Functions +------------- + +.. cpp:namespace:: @ex_specific_cpp_function + +.. doxygenfile:: cpp_function.h + :project: cpp_function + +C++ Inherited Members +--------------------- + +.. cpp:namespace:: @ex_specific_cpp_inherited_members + +.. doxygenfile:: cpp_inherited_members.h + :project: cpp_inherited_members + +C++ Trailing Return Type +------------------------ + +.. cpp:namespace:: @ex_specific_cpp_trailing_return_type + +.. doxygenfile:: cpp_trailing_return_type.h + :project: cpp_trailing_return_type diff --git a/examples/doxygen/pyexample.py b/examples/doxygen/pyexample.py index 666c25b5..a75cc60e 100644 --- a/examples/doxygen/pyexample.py +++ b/examples/doxygen/pyexample.py @@ -24,7 +24,7 @@ def PyMethod(self): pass ## A class variable. - classVar = 0; + classVar = 0 ## @var _memVar # a member variable diff --git a/examples/specific/Makefile b/examples/specific/Makefile index fc2abb20..ec1a4ce4 100644 --- a/examples/specific/Makefile +++ b/examples/specific/Makefile @@ -24,7 +24,8 @@ 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 cpp_union cpp_function cpp_inherited_members \ + cpp_trailing_return_type \ c_file c_struct c_enum c_typedef c_macro c_union special = programlisting decl_impl multifilexml auto class typedef diff --git a/examples/specific/c_struct.h b/examples/specific/c_struct.h index dda63937..ffd33407 100644 --- a/examples/specific/c_struct.h +++ b/examples/specific/c_struct.h @@ -1,5 +1,9 @@ struct ACStruct { int i; + + struct ANestedStruct { + int i; + }; }; diff --git a/examples/specific/cpp_enum.cfg b/examples/specific/cpp_enum.cfg new file mode 100644 index 00000000..7a2ea410 --- /dev/null +++ b/examples/specific/cpp_enum.cfg @@ -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 diff --git a/examples/specific/cpp_enum.h b/examples/specific/cpp_enum.h new file mode 100644 index 00000000..985f5417 --- /dev/null +++ b/examples/specific/cpp_enum.h @@ -0,0 +1,11 @@ +enum Unscoped : int { + UnscopedEnumerator = 42 +}; + +enum struct ScopedStruct : int { + Enumerator = 42 +}; + +enum class ScopedClass : int { + Enumerator = 42 +}; diff --git a/examples/specific/cpp_function.cfg b/examples/specific/cpp_function.cfg new file mode 100644 index 00000000..43033c1d --- /dev/null +++ b/examples/specific/cpp_function.cfg @@ -0,0 +1,11 @@ +PROJECT_NAME = "C++ Function" +OUTPUT_DIRECTORY = cpp_function +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = cpp_function.h +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES diff --git a/examples/specific/cpp_function.h b/examples/specific/cpp_function.h new file mode 100644 index 00000000..59af74f6 --- /dev/null +++ b/examples/specific/cpp_function.h @@ -0,0 +1,10 @@ +struct Class { + virtual void f1() const volatile & = 0; + virtual void f2() const volatile && = 0; + static void f3(); + + + void (*f_issue_489)(struct Foo *foo, int value); + + int f_issue_338() noexcept; +}; diff --git a/examples/specific/cpp_inherited_members.cfg b/examples/specific/cpp_inherited_members.cfg new file mode 100644 index 00000000..119f5fb8 --- /dev/null +++ b/examples/specific/cpp_inherited_members.cfg @@ -0,0 +1,12 @@ +PROJECT_NAME = "C++ Inherited Members" +OUTPUT_DIRECTORY = cpp_inherited_members +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = cpp_inherited_members.h +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES +INLINE_INHERITED_MEMB = YES diff --git a/examples/specific/cpp_inherited_members.h b/examples/specific/cpp_inherited_members.h new file mode 100644 index 00000000..0235d58a --- /dev/null +++ b/examples/specific/cpp_inherited_members.h @@ -0,0 +1,16 @@ +/** + * @file + */ + +/// Base class +class Base +{ +public: + /// Base-class member function + void f_issue_356(); +}; + +/// Class A +class A : public Base {}; +/// Class B +class B : public Base {}; diff --git a/examples/specific/cpp_trailing_return_type.cfg b/examples/specific/cpp_trailing_return_type.cfg new file mode 100644 index 00000000..4d56fd44 --- /dev/null +++ b/examples/specific/cpp_trailing_return_type.cfg @@ -0,0 +1,11 @@ +PROJECT_NAME = "C++ Trailing Return Type" +OUTPUT_DIRECTORY = cpp_trailing_return_type +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = cpp_trailing_return_type.h +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES diff --git a/examples/specific/cpp_trailing_return_type.h b/examples/specific/cpp_trailing_return_type.h new file mode 100644 index 00000000..25235384 --- /dev/null +++ b/examples/specific/cpp_trailing_return_type.h @@ -0,0 +1,4 @@ +class Thingy; + +//! \brief Function that creates a thingy. +auto f_issue_441() -> Thingy*; diff --git a/examples/specific/cpp_union.cfg b/examples/specific/cpp_union.cfg new file mode 100644 index 00000000..f4324b88 --- /dev/null +++ b/examples/specific/cpp_union.cfg @@ -0,0 +1,11 @@ +PROJECT_NAME = "C++ Union" +OUTPUT_DIRECTORY = cpp_union +GENERATE_LATEX = NO +GENERATE_MAN = NO +GENERATE_RTF = NO +CASE_SENSE_NAMES = NO +INPUT = cpp_union.h +QUIET = YES +JAVADOC_AUTOBRIEF = YES +GENERATE_HTML = NO +GENERATE_XML = YES diff --git a/examples/specific/cpp_union.h b/examples/specific/cpp_union.h new file mode 100644 index 00000000..d9347418 --- /dev/null +++ b/examples/specific/cpp_union.h @@ -0,0 +1,10 @@ + +union Union { + int i; +}; + +struct Class { + union Union { + int i; + }; +}; diff --git a/examples/specific/struct_function.h b/examples/specific/struct_function.h index 9c054efb..054d5b9e 100644 --- a/examples/specific/struct_function.h +++ b/examples/specific/struct_function.h @@ -10,6 +10,8 @@ struct MyClass //! \brief struct copy constructor MyClass(const MyClass&); + + int myMemberVar; }; } diff --git a/examples/specific/typedef.h b/examples/specific/typedef.h index 9735b1fb..8a11a70d 100644 --- a/examples/specific/typedef.h +++ b/examples/specific/typedef.h @@ -23,4 +23,8 @@ class TestClass { public: /** A typedef defined in a class. */ typedef void *MemberTypedef; + + typedef void (*MemberTypedefFuncPointer)(int, double); }; + +using TypeAlias = int; diff --git a/tests/test_renderer.py b/tests/test_renderer.py index 4c9133ab..467ceb90 100644 --- a/tests/test_renderer.py +++ b/tests/test_renderer.py @@ -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(), @@ -257,8 +258,9 @@ def render(app, member_def, domain=None, show_define_initializer=False): def test_render_func(app): - member_def = WrappedMemberDef(kind='function', definition='void foo', argsstring='(int)', virt='non-virtual', - param=[WrappedParam(type_=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'int')]))]) + member_def = WrappedMemberDef(kind='function', definition='void foo', type_='void', name='foo', argsstring='(int)', + virt='non-virtual', + param=[WrappedParam(type_=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'int')]))]) signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext().startswith('void') assert find_node(signature, 'desc_name')[0] == 'foo' @@ -269,19 +271,20 @@ def test_render_func(app): def test_render_typedef(app): - member_def = WrappedMemberDef(kind='typedef', definition='typedef int foo') + member_def = WrappedMemberDef(kind='typedef', definition='typedef int foo', type_='int', name='foo') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext() == 'typedef int foo' def test_render_c_typedef(app): - member_def = WrappedMemberDef(kind='typedef', definition='typedef unsigned int bar') + member_def = WrappedMemberDef(kind='typedef', definition='typedef unsigned int bar', type_='unsigned int', name='bar') signature = find_node(render(app, member_def, domain='c'), 'desc_signature') assert signature.astext() == 'typedef unsigned int bar' def test_render_c_function_typedef(app): - member_def = WrappedMemberDef(kind='typedef', definition='typedef void* (*voidFuncPtr)(float, int)') + member_def = WrappedMemberDef(kind='typedef', definition='typedef void* (*voidFuncPtr)(float, int)', + type_='void* (*', name='voidFuncPtr', argsstring=')(float, int)') signature = find_node(render(app, member_def, domain='c'), 'desc_signature') assert signature.astext().startswith('typedef void *') params = find_node(signature, 'desc_parameterlist') @@ -291,48 +294,49 @@ def test_render_c_function_typedef(app): def test_render_using_alias(app): - member_def = WrappedMemberDef(kind='typedef', definition='using foo = int') + member_def = WrappedMemberDef(kind='typedef', definition='using foo = int', type_='int', name='foo') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext() == 'using foo = int' def test_render_const_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', argsstring='() const', + member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() const', virt='non-virtual', const='yes') signature = find_node(render(app, member_def), 'desc_signature') assert '_CPPv2NK1fEv' in signature['ids'] def test_render_lvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', argsstring='()', + member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() &', virt='non-virtual', refqual='lvalue') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext().endswith('&') def test_render_rvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', argsstring='()', + member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() &&', virt='non-virtual', refqual='rvalue') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext().endswith('&&') def test_render_const_lvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', argsstring='()', + member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f',argsstring='() const &', virt='non-virtual', const='yes', refqual='lvalue') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext().endswith('const &') def test_render_const_rvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', argsstring='()', + member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() const &&', virt='non-virtual', const='yes', refqual='rvalue') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext().endswith('const &&') def test_render_variable_initializer(app): - member_def = WrappedMemberDef(kind='variable', definition='const int EOF', initializer=WrappedMixedContainer(value=u'= -1')) + member_def = WrappedMemberDef(kind='variable', definition='const int EOF', type_='const int', name='EOF', + initializer=WrappedMixedContainer(value=u'= -1')) signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext() == 'const int EOF = -1'