Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for innergroup #556

Merged
merged 3 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions breathe/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
6 changes: 6 additions & 0 deletions breathe/renderer/sphinxrenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the event that inner is requested but there are no inner groups, should we emit a warning along the lines of inner groups requested for group {group name} but no inner groups found!?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, and there's also the other way around. If there are innergroups but no inner keyword was given in the options. To preserve compatibility this should obviously not trigger a warning. I am honestly not sure if someone would like the behavior of generating a warning in the case you pointed, but I will assume so and update accordingly. It can always be removed later! Btw, adding an inner in the documentation set I am working on, already triggers lots and lots of warnings because of the double references, because we basically had to create a reference to each group individually due to the lack of innergroup parsing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking a bit better about this, turns out that options is part of a "global" context in a sense. So if I have an innergroup B inside another group A, and I set inner, both A and B will be checked for the existence of innergroups. This will end up being an empty list for the innergroup B (assuming it has no innergroups itself!); but it should not trigger a warning.

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_
Expand Down
28 changes: 27 additions & 1 deletion documentation/source/group.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -42,6 +42,11 @@ If you would like to always specify some combination of ``members``,
use the :ref:`breathe_default_members <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::
Expand Down Expand Up @@ -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
---------------

Expand Down
22 changes: 22 additions & 0 deletions examples/specific/group.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
82 changes: 68 additions & 14 deletions tests/test_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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)


Expand Down Expand Up @@ -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']))