diff --git a/breathe/directives.py b/breathe/directives.py index 6f09192e..7d34f097 100644 --- a/breathe/directives.py +++ b/breathe/directives.py @@ -406,6 +406,10 @@ class DoxygenNamespaceDirective(_DoxygenContentBlockDirective): class DoxygenGroupDirective(_DoxygenContentBlockDirective): kind = "group" + option_spec = { + **_DoxygenContentBlockDirective.option_spec, + "inner": flag, + } # TODO: is this comment still relevant? diff --git a/breathe/renderer/sphinxrenderer.py b/breathe/renderer/sphinxrenderer.py index 4f63dd95..7bd935b6 100644 --- a/breathe/renderer/sphinxrenderer.py +++ b/breathe/renderer/sphinxrenderer.py @@ -1024,6 +1024,12 @@ def render_derivedcompoundref(node): addnode('innerclass', lambda: self.render_iterable(node.innerclass)) addnode('innernamespace', lambda: self.render_iterable(node.innernamespace)) + if 'inner' in options: + for node in node.innergroup: + file_data = self.compound_parser.parse(node.refid) + inner = file_data.compounddef + addnode('innergroup', lambda: self.visit_compounddef(inner)) + nodelist = [] for i, nodes_ in sorted(nodemap.items()): nodelist += nodes_ diff --git a/documentation/source/group.rst b/documentation/source/group.rst index 68707bd4..bf8b7202 100644 --- a/documentation/source/group.rst +++ b/documentation/source/group.rst @@ -10,7 +10,7 @@ source comments as cover in the `doxygen documentation`_. It takes the standard ``project``, ``path``, ``outline`` and ``no-link`` options and additionally the ``content-only``, ``members``, ``protected-members``, -``private-members`` and ``undoc-members`` options. +``private-members``, ``undoc-members`` and ``inner`` options. ``content-only`` If this flag is specified, then the directive does not output the name of the @@ -42,6 +42,11 @@ If you would like to always specify some combination of ``members``, use the :ref:`breathe_default_members ` configuration variable to set it in the ``conf.py``. +``inner`` + If specified, the groups that were defined inside this group, by either + defining them inside the scope of another group, or by using the Doxygen + \ingroup command, are also parsed and loaded. + .. _doxygen documentation: http://www.stack.nl/~dimitri/doxygen/manual/grouping.html .. contents:: @@ -190,6 +195,27 @@ Produces this output: issue. Please post an issue on github if you would like this resolved. +Inner Example +------------- + +.. cpp:namespace:: @ex_group_inner + +The ``inner`` option changes the output to include groups that are defined +inside other groups. + +.. code-block:: rst + + .. doxygengroup:: mygroup + :project: group + :inner: + +Produces this output: + +.. doxygengroup:: mygroup + :project: group + :inner: + + Outline Example --------------- diff --git a/examples/specific/group.h b/examples/specific/group.h index 65b26d45..2135cb1d 100644 --- a/examples/specific/group.h +++ b/examples/specific/group.h @@ -48,6 +48,28 @@ void groupedFunction(); /** @} */ // end of mygroup +/** @defgroup innergroup Inner Group + * @ingroup mygroup + * This is an inner group + * @{ + */ + +//! \brief inner class inside of namespace +class InnerGroupClassTest { + +public: + //! \brief inner namespaced class function + void function() {}; + +private: + + //! A private function + void innerGroupPrivateFunction() {}; + + class PrivateClass {}; +}; + +/** @} */ // end of innergroup //! \brief second class inside of namespace class UngroupedClassTest { diff --git a/tests/test_renderer.py b/tests/test_renderer.py index 1db00b48..46fa57f5 100644 --- a/tests/test_renderer.py +++ b/tests/test_renderer.py @@ -2,7 +2,10 @@ import sphinx.addnodes import sphinx.environment -from breathe.parser.compound import linkedTextTypeSub, memberdefTypeSub, paramTypeSub, MixedContainer +from breathe.parser.compound import ( + compounddefTypeSub, linkedTextTypeSub, memberdefTypeSub, paramTypeSub, + refTypeSub, MixedContainer +) from breathe.renderer.sphinxrenderer import SphinxRenderer from breathe.renderer.filter import OpenFilter from docutils import frontend, nodes, parsers, utils @@ -44,9 +47,9 @@ class WrappedDoxygenNode: A base class for test wrappers of Doxygen nodes. It allows setting all attributes via keyword arguments in the constructor. """ - def __init__(self, cls, **kwargs): + def __init__(self, cls, *args, **kwargs): if cls: - cls.__init__(self) + cls.__init__(self, args) for name, value in kwargs.items(): if not hasattr(self, name): raise AttributeError('invalid attribute ' + name) @@ -78,6 +81,18 @@ def __init__(self, **kwargs): WrappedDoxygenNode.__init__(self, paramTypeSub, **kwargs) +class WrappedRef(refTypeSub, WrappedDoxygenNode): + """A test wrapper of Doxygen ref.""" + def __init__(self, node_name, **kwargs): + WrappedDoxygenNode.__init__(self, refTypeSub, node_name, **kwargs) + + +class WrappedCompoundDef(compounddefTypeSub, WrappedDoxygenNode): + """A test wrapper of Doxygen compound definition.""" + def __init__(self, **kwargs): + WrappedDoxygenNode.__init__(self, compounddefTypeSub, **kwargs) + + class MockState: def __init__(self, app): env = sphinx.environment.BuildEnvironment(app) @@ -120,17 +135,17 @@ def mask(self, node): class MockContext: - def __init__(self, app, node_stack, domain=None): + def __init__(self, app, node_stack, domain=None, options=[]): self.domain = domain self.node_stack = node_stack self.directive_args = [ - None, # name - None, # arguments - [], # options - None, # content - None, # lineno - None, # content_offset - None, # block_text + None, # name + None, # arguments + options, # options + None, # content + None, # lineno + None, # content_offset + None, # block_text MockState(app), MockStateMachine()] self.child = None self.mask_factory = MockMaskFactory() @@ -152,6 +167,23 @@ def __init__(self): self.reporter = MockReporter() +class MockCompoundParser: + """ + A compound parser reads a doxygen XML file from disk; this mock implements + a mapping of what would be the file name on disk to data using a dict. + """ + def __init__(self, compound_dict): + self.compound_dict = compound_dict + + class MockFileData: + def __init__(self, compounddef): + self.compounddef = compounddef + + def parse(self, compoundname): + compounddef = self.compound_dict[compoundname] + return self.MockFileData(compounddef) + + class NodeFinder(nodes.NodeVisitor): """Find node with specified class name.""" def __init__(self, name, document): @@ -224,7 +256,8 @@ def test_find_node(): 'the number of nodes Text is 2') -def render(app, member_def, domain=None, show_define_initializer=False): +def render(app, member_def, domain=None, show_define_initializer=False, + compound_parser=None, options=[]): """Render Doxygen *member_def* with *renderer_class*.""" app.config.breathe_use_project_refids = False @@ -238,9 +271,9 @@ def render(app, member_def, domain=None, show_define_initializer=False): None, # state None, # document MockTargetHandler(), - None, # compound_parser + compound_parser, OpenFilter()) - renderer.context = MockContext(app, [member_def], domain) + renderer.context = MockContext(app, [member_def], domain, options) return renderer.render(member_def) @@ -346,3 +379,24 @@ def test_render_define_no_initializer(app): member_def = WrappedMemberDef(kind='define', name='USE_MILK') signature = find_node(render(app, member_def), 'desc_signature') assert signature.astext() == 'USE_MILK' + + +def test_render_innergroup(app): + refid = 'group__innergroup' + mock_compound_parser = MockCompoundParser({ + refid: WrappedCompoundDef(kind='group', + compoundname='InnerGroup', + briefdescription='InnerGroup') + }) + ref = WrappedRef('InnerGroup', refid=refid) + compound_def = WrappedCompoundDef(kind='group', + compoundname='OuterGroup', + briefdescription='OuterGroup', + innergroup=[ref]) + assert all(el.astext() != 'InnerGroup' + for el in render(app, compound_def, + compound_parser=mock_compound_parser)) + assert any(el.astext() == 'InnerGroup' + for el in render(app, compound_def, + compound_parser=mock_compound_parser, + options=['inner']))