diff -rupN --no-dereference breathe-4.30.0/breathe/directive/base.py breathe-4.30.0-new/breathe/directive/base.py
--- breathe-4.30.0/breathe/directive/base.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/directive/base.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,96 +0,0 @@
-from breathe.finder.factory import FinderFactory
-from breathe.parser import DoxygenParserFactory
-from breathe.parser import FileIOError, ParserError
-from breathe.project import ProjectInfoFactory, ProjectInfo
-from breathe.renderer import format_parser_error, RenderContext
-from breathe.renderer.filter import Filter, FilterFactory
-from breathe.renderer.mask import MaskFactoryBase
-from breathe.renderer.sphinxrenderer import SphinxRenderer
-from breathe.renderer.target import TargetHandler
-
-from sphinx.directives import SphinxDirective
-
-from docutils import nodes
-from docutils.nodes import Node
-
-from typing import Any, Dict, List, Optional, Sequence
-
-
-class WarningHandler:
-    def __init__(self, state, context: Dict[str, Any]) -> None:
-        self.state = state
-        self.context = context
-
-    def warn(self, raw_text: str, *, rendered_nodes: Sequence[nodes.Node] = None,
-             unformatted_suffix: str = '') -> List[nodes.Node]:
-        raw_text = self.format(raw_text) + unformatted_suffix
-        if rendered_nodes is None:
-            rendered_nodes = [nodes.paragraph("", "", nodes.Text(raw_text))]
-        return [
-            nodes.warning("", *rendered_nodes),
-            self.state.document.reporter.warning(raw_text, line=self.context['lineno'])
-        ]
-
-    def format(self, text: str) -> str:
-        return text.format(**self.context)
-
-
-def create_warning(project_info: Optional[ProjectInfo], state, lineno: int,
-                   **kwargs) -> WarningHandler:
-    tail = ''
-    if project_info:
-        tail = 'in doxygen xml output for project "{project}" from directory: {path}'.format(
-            project=project_info.name(),
-            path=project_info.project_path()
-        )
-
-    context = dict(
-        lineno=lineno,
-        tail=tail,
-        **kwargs
-    )
-    return WarningHandler(state, context)
-
-
-class BaseDirective(SphinxDirective):
-    def __init__(self, finder_factory: FinderFactory,
-                 project_info_factory: ProjectInfoFactory,
-                 parser_factory: DoxygenParserFactory,
-                 *args) -> None:
-        super().__init__(*args)
-        self.directive_args = list(args)  # Convert tuple to list to allow modification.
-
-        self.finder_factory = finder_factory
-        self.project_info_factory = project_info_factory
-        self.filter_factory = FilterFactory(self.env.app)
-        self.parser_factory = parser_factory
-
-    @property
-    def kind(self) -> str:
-        raise NotImplementedError
-
-    def render(self, node_stack, project_info: ProjectInfo, filter_: Filter,
-               target_handler: TargetHandler, mask_factory: MaskFactoryBase,
-               directive_args) -> List[Node]:
-        "Standard render process used by subclasses"
-
-        try:
-            object_renderer = SphinxRenderer(
-                self.parser_factory.app,
-                project_info,
-                node_stack,
-                self.state,
-                self.state.document,
-                target_handler,
-                self.parser_factory.create_compound_parser(project_info),
-                filter_
-            )
-        except ParserError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, True)
-        except FileIOError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, True)
-
-        context = RenderContext(node_stack, mask_factory, directive_args)
-        return object_renderer.render(node_stack[0], context)
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/class_like.py breathe-4.30.0-new/breathe/directive/class_like.py
--- breathe-4.30.0/breathe/directive/class_like.py	1970-01-01 01:00:00.000000000 +0100
+++ breathe-4.30.0-new/breathe/directive/class_like.py	2021-08-11 14:08:01.253457636 +0200
@@ -0,0 +1,74 @@
+from breathe.directive import BaseDirective
+from breathe.file_state_cache import MTimeError
+from breathe.project import ProjectError
+from breathe.renderer.mask import NullMaskFactory
+from breathe.renderer.target import create_target_handler
+
+from docutils.nodes import Node
+from docutils.parsers.rst.directives import unchanged_required, unchanged, flag
+
+from typing import Any, List, Optional, Type  # noqa
+
+
+class _DoxygenClassLikeDirective(BaseDirective):
+    required_arguments = 1
+    optional_arguments = 0
+    final_argument_whitespace = True
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "members": unchanged,
+        "membergroups": unchanged_required,
+        "members-only": flag,
+        "protected-members": flag,
+        "private-members": flag,
+        "undoc-members": flag,
+        "show": unchanged_required,
+        "outline": flag,
+        "no-link": flag,
+    }
+    has_content = False
+
+    def run(self) -> List[Node]:
+        name = self.arguments[0]
+
+        try:
+            project_info = self.project_info_factory.create_project_info(self.options)
+        except ProjectError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        try:
+            finder = self.finder_factory.create_finder(project_info)
+        except MTimeError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        finder_filter = self.filter_factory.create_compound_finder_filter(name, self.kind)
+
+        # TODO: find a more specific type for the Doxygen nodes
+        matches = []  # type: List[Any]
+        finder.filter_(finder_filter, matches)
+
+        if len(matches) == 0:
+            warning = self.create_warning(project_info, name=name, kind=self.kind)
+            return warning.warn('doxygen{kind}: Cannot find class "{name}" {tail}')
+
+        target_handler = create_target_handler(self.options, project_info, self.state.document)
+        filter_ = self.filter_factory.create_class_filter(name, self.options)
+
+        mask_factory = NullMaskFactory()
+        return self.render(matches[0], project_info, filter_, target_handler, mask_factory,
+                           self.directive_args)
+
+
+class DoxygenClassDirective(_DoxygenClassLikeDirective):
+    kind = "class"
+
+
+class DoxygenStructDirective(_DoxygenClassLikeDirective):
+    kind = "struct"
+
+
+class DoxygenInterfaceDirective(_DoxygenClassLikeDirective):
+    kind = "interface"
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/content_block.py breathe-4.30.0-new/breathe/directive/content_block.py
--- breathe-4.30.0/breathe/directive/content_block.py	1970-01-01 01:00:00.000000000 +0100
+++ breathe-4.30.0-new/breathe/directive/content_block.py	2021-08-11 14:08:01.253457636 +0200
@@ -0,0 +1,117 @@
+from breathe.directive import BaseDirective
+from breathe.file_state_cache import MTimeError
+from breathe.project import ProjectError
+from breathe.renderer import RenderContext
+from breathe.renderer.mask import NullMaskFactory
+from breathe.renderer.sphinxrenderer import SphinxRenderer
+from breathe.renderer.target import create_target_handler
+
+from docutils.nodes import Node
+from docutils.parsers.rst.directives import unchanged_required, flag
+
+from typing import Any, List, Optional, Type  # noqa
+
+
+class _DoxygenContentBlockDirective(BaseDirective):
+    """Base class for namespace and group directives which have very similar behaviours"""
+
+    required_arguments = 1
+    optional_arguments = 1
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "content-only": flag,
+        "outline": flag,
+        "members": flag,
+        "protected-members": flag,
+        "private-members": flag,
+        "undoc-members": flag,
+        "no-link": flag
+    }
+    has_content = False
+
+    def run(self) -> List[Node]:
+        name = self.arguments[0]
+
+        try:
+            project_info = self.project_info_factory.create_project_info(self.options)
+        except ProjectError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        try:
+            finder = self.finder_factory.create_finder(project_info)
+        except MTimeError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        finder_filter = self.filter_factory.create_finder_filter(self.kind, name)
+
+        # TODO: find a more specific type for the Doxygen nodes
+        matches = []  # type: List[Any]
+        finder.filter_(finder_filter, matches)
+
+        # It shouldn't be possible to have too many matches as namespaces & groups in their nature
+        # are merged together if there are multiple declarations, so we only check for no matches
+        if not matches:
+            warning = self.create_warning(project_info, name=name, kind=self.kind)
+            return warning.warn('doxygen{kind}: Cannot find {kind} "{name}" {tail}')
+
+        if 'content-only' in self.options and self.kind != "page":
+            # Unpack the single entry in the matches list
+            (node_stack,) = matches
+
+            filter_ = self.filter_factory.create_content_filter(self.kind, self.options)
+            # Having found the compound node for the namespace or group in the index we want to grab
+            # the contents of it which match the filter
+            contents_finder = self.finder_factory.create_finder_from_root(node_stack[0],
+                                                                          project_info)
+            # TODO: find a more specific type for the Doxygen nodes
+            contents = []  # type: List[Any]
+            contents_finder.filter_(filter_, contents)
+
+            # Replaces matches with our new starting points
+            matches = contents
+
+        target_handler = create_target_handler(self.options, project_info, self.state.document)
+        filter_ = self.filter_factory.create_render_filter(self.kind, self.options)
+
+        node_list = []
+        for node_stack in matches:
+            object_renderer = SphinxRenderer(
+                self.parser_factory.app,
+                project_info,
+                node_stack,
+                self.state,
+                self.state.document,
+                target_handler,
+                self.parser_factory.create_compound_parser(project_info),
+                filter_
+            )
+
+            mask_factory = NullMaskFactory()
+            context = RenderContext(node_stack, mask_factory, self.directive_args)
+            node_list.extend(object_renderer.render(context.node_stack[0], context))
+
+        return node_list
+
+
+class DoxygenNamespaceDirective(_DoxygenContentBlockDirective):
+    kind = "namespace"
+
+
+class DoxygenGroupDirective(_DoxygenContentBlockDirective):
+    kind = "group"
+    option_spec = {
+        **_DoxygenContentBlockDirective.option_spec,
+        "inner": flag,
+    }
+
+
+class DoxygenPageDirective(_DoxygenContentBlockDirective):
+    kind = "page"
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "content-only": flag,
+    }
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/file.py breathe-4.30.0-new/breathe/directive/file.py
--- breathe-4.30.0/breathe/directive/file.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/directive/file.py	2021-08-11 14:08:01.254457610 +0200
@@ -1,7 +1,6 @@
 from ..renderer.mask import NullMaskFactory
-from ..directive.base import BaseDirective
+from ..directives import BaseDirective
 from ..project import ProjectError
-from .base import create_warning
 
 from breathe.renderer import RenderContext
 from breathe.renderer.sphinxrenderer import SphinxRenderer
@@ -10,7 +9,7 @@ from breathe.renderer.target import crea
 from docutils.parsers.rst.directives import unchanged_required, flag
 
 
-class BaseFileDirective(BaseDirective):
+class _BaseFileDirective(BaseDirective):
     """Base class handle the main work when given the appropriate file and project info to work
     from.
     """
@@ -27,13 +26,10 @@ class BaseFileDirective(BaseDirective):
         finder.filter_(finder_filter, matches)
 
         if len(matches) > 1:
-            warning = create_warning(None, self.state, self.lineno, file=file_,
-                                     directivename=self.directive_name)
+            warning = self.create_warning(None, file=file_, directivename=self.directive_name)
             return warning.warn('{directivename}: Found multiple matches for file "{file} {tail}')
-
         elif not matches:
-            warning = create_warning(None, self.state, self.lineno, file=file_,
-                                     directivename=self.directive_name)
+            warning = self.create_warning(None, file=file_, directivename=self.directive_name)
             return warning.warn('{directivename}: Cannot find file "{file} {tail}')
 
         target_handler = create_target_handler(self.options, project_info, self.state.document)
@@ -59,8 +55,7 @@ class BaseFileDirective(BaseDirective):
         return node_list
 
 
-class DoxygenFileDirective(BaseFileDirective):
-
+class DoxygenFileDirective(_BaseFileDirective):
     directive_name = 'doxygenfile'
 
     required_arguments = 0
@@ -71,25 +66,23 @@ class DoxygenFileDirective(BaseFileDirec
         "outline": flag,
         "no-link": flag,
         "sections": unchanged_required,
-        }
+    }
     has_content = False
 
     def run(self):
         """Get the file from the argument and the project info from the factory."""
 
         file_ = self.arguments[0]
-
         try:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno)
+            warning = self.create_warning(None)
             return warning.warn('doxygenfile: %s' % e)
 
         return self.handle_contents(file_, project_info)
 
 
-class AutoDoxygenFileDirective(BaseFileDirective):
-
+class AutoDoxygenFileDirective(_BaseFileDirective):
     directive_name = 'autodoxygenfile'
 
     required_arguments = 1
@@ -98,7 +91,7 @@ class AutoDoxygenFileDirective(BaseFileD
         "outline": flag,
         "no-link": flag,
         "sections": unchanged_required,
-        }
+    }
     has_content = False
 
     def run(self):
@@ -107,11 +100,10 @@ class AutoDoxygenFileDirective(BaseFileD
         """
 
         file_ = self.arguments[0]
-
         try:
             project_info = self.project_info_factory.retrieve_project_info_for_auto(self.options)
         except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno)
+            warning = self.create_warning(None)
             return warning.warn('autodoxygenfile: %s' % e)
 
         return self.handle_contents(file_, project_info)
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/function.py breathe-4.30.0-new/breathe/directive/function.py
--- breathe-4.30.0/breathe/directive/function.py	1970-01-01 01:00:00.000000000 +0100
+++ breathe-4.30.0-new/breathe/directive/function.py	2021-08-11 14:08:01.254457610 +0200
@@ -0,0 +1,249 @@
+from breathe.directive import BaseDirective
+from breathe.exception import BreatheError
+from breathe.file_state_cache import MTimeError
+from breathe.parser import ParserError, FileIOError
+from breathe.project import ProjectError
+from breathe.renderer import format_parser_error, RenderContext
+from breathe.renderer.sphinxrenderer import WithContext
+from breathe.renderer.mask import (
+    MaskFactory, NullMaskFactory, NoParameterNamesMask
+)
+from breathe.renderer.sphinxrenderer import SphinxRenderer
+from breathe.renderer.target import create_target_handler
+
+from docutils.nodes import Node
+from docutils.parsers.rst.directives import unchanged_required, flag
+
+from sphinx.domains import cpp
+
+from docutils import nodes
+
+import re
+
+from typing import Any, List, Optional, Type  # noqa
+
+
+class _NoMatchingFunctionError(BreatheError):
+    pass
+
+
+class _UnableToResolveFunctionError(BreatheError):
+    def __init__(self, signatures: List[str]) -> None:
+        self.signatures = signatures
+
+
+class DoxygenFunctionDirective(BaseDirective):
+    required_arguments = 1
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "outline": flag,
+        "no-link": flag,
+    }
+    has_content = False
+    final_argument_whitespace = True
+
+    def run(self) -> List[Node]:
+        # Separate possible arguments (delimited by a "(") from the namespace::name
+        match = re.match(r"([^(]*)(.*)", self.arguments[0])
+        assert match is not None  # TODO: this is probably not appropriate, for now it fixes typing
+        namespaced_function, args = match.group(1), match.group(2)
+
+        # Split the namespace and the function name
+        try:
+            (namespace, function_name) = namespaced_function.rsplit("::", 1)
+        except ValueError:
+            (namespace, function_name) = "", namespaced_function
+        namespace = namespace.strip()
+        function_name = function_name.strip()
+
+        try:
+            project_info = self.project_info_factory.create_project_info(self.options)
+        except ProjectError as e:
+            warning = self.create_warning(None)
+            return warning.warn('doxygenfunction: %s' % e)
+
+        try:
+            finder = self.finder_factory.create_finder(project_info)
+        except MTimeError as e:
+            warning = self.create_warning(None)
+            return warning.warn('doxygenfunction: %s' % e)
+
+        # Extract arguments from the function name.
+        try:
+            args = self._parse_args(args)
+        except cpp.DefinitionError as e:
+            return self.create_warning(
+                project_info,
+                namespace='%s::' % namespace if namespace else '',
+                function=function_name,
+                args=str(args),
+                cpperror=str(e)
+            ).warn('doxygenfunction: Unable to resolve function '
+                   '"{namespace}{function}" with arguments "{args}".\n'
+                   'Could not parse arguments. Parsing eror is\n{cpperror}')
+
+        finder_filter = self.filter_factory.create_function_and_all_friend_finder_filter(
+            namespace, function_name)
+
+        # TODO: find a more specific type for the Doxygen nodes
+        matchesAll = []  # type: List[Any]
+        finder.filter_(finder_filter, matchesAll)
+        matches = []
+        for m in matchesAll:
+            # only take functions and friend functions
+            # ignore friend classes
+            node = m[0]
+            if node.kind == 'friend' and not node.argsstring:
+                continue
+            matches.append(m)
+
+        # Create it ahead of time as it is cheap and it is ugly to declare it for both exception
+        # clauses below
+        warning = self.create_warning(
+            project_info,
+            namespace='%s::' % namespace if namespace else '',
+            function=function_name,
+            args=str(args)
+        )
+
+        try:
+            node_stack = self._resolve_function(matches, args, project_info)
+        except _NoMatchingFunctionError:
+            return warning.warn('doxygenfunction: Cannot find function "{namespace}{function}" '
+                                '{tail}')
+        except _UnableToResolveFunctionError as error:
+            message = 'doxygenfunction: Unable to resolve function ' \
+                '"{namespace}{function}" with arguments {args} {tail}.\n' \
+                'Potential matches:\n'
+
+            text = ''
+            for i, entry in enumerate(sorted(error.signatures)):
+                text += '- %s\n' % entry
+            block = nodes.literal_block('', '', nodes.Text(text))
+            formatted_message = warning.format(message)
+            warning_nodes = [
+                nodes.paragraph("", "", nodes.Text(formatted_message)),
+                block
+            ]
+            result = warning.warn(message, rendered_nodes=warning_nodes,
+                                  unformatted_suffix=text)
+            return result
+        except cpp.DefinitionError as error:
+            warning.context['cpperror'] = str(error)
+            return warning.warn(
+                'doxygenfunction: Unable to resolve function '
+                '"{namespace}{function}" with arguments "{args}".\n'
+                'Candidate function could not be parsed. Parsing error is\n{cpperror}')
+
+        target_handler = create_target_handler(self.options, project_info, self.state.document)
+        filter_ = self.filter_factory.create_outline_filter(self.options)
+
+        return self.render(node_stack, project_info, filter_, target_handler, NullMaskFactory(),
+                           self.directive_args)
+
+    def _parse_args(self, function_description: str) -> Optional[cpp.ASTParametersQualifiers]:
+        # Note: the caller must catch cpp.DefinitionError
+        if function_description == '':
+            return None
+
+        parser = cpp.DefinitionParser(function_description,
+                                      location=self.get_source_info(),
+                                      config=self.config)
+        paramQual = parser._parse_parameters_and_qualifiers(paramMode='function')
+        # now erase the parameter names
+        for p in paramQual.args:
+            if p.arg is None:
+                assert p.ellipsis
+                continue
+            declarator = p.arg.type.decl
+            while hasattr(declarator, 'next'):
+                declarator = declarator.next  # type: ignore
+            assert hasattr(declarator, 'declId')
+            declarator.declId = None  # type: ignore
+            p.arg.init = None  # type: ignore
+        return paramQual
+
+    def _create_function_signature(self, node_stack, project_info, filter_, target_handler,
+                                   mask_factory, directive_args) -> str:
+        "Standard render process used by subclasses"
+
+        try:
+            object_renderer = SphinxRenderer(
+                self.parser_factory.app,
+                project_info,
+                node_stack,
+                self.state,
+                self.state.document,
+                target_handler,
+                self.parser_factory.create_compound_parser(project_info),
+                filter_,
+            )
+        except ParserError as e:
+            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
+                                       self.lineno, True)
+        except FileIOError as e:
+            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
+                                       self.lineno, False)
+
+        context = RenderContext(node_stack, mask_factory, directive_args)
+        node = node_stack[0]
+        with WithContext(object_renderer, context):
+            # this part should be kept in sync with visit_function in sphinxrenderer
+            name = node.get_name()
+            # assume we are only doing this for C++ declarations
+            declaration = ' '.join([
+                object_renderer.create_template_prefix(node),
+                ''.join(n.astext() for n in object_renderer.render(node.get_type())),
+                name,
+                node.get_argsstring()
+            ])
+        parser = cpp.DefinitionParser(declaration,
+                                      location=self.get_source_info(),
+                                      config=self.config)
+        ast = parser.parse_declaration('function', 'function')
+        return str(ast)
+
+    def _resolve_function(self, matches, args: Optional[cpp.ASTParametersQualifiers], project_info):
+        if not matches:
+            raise _NoMatchingFunctionError()
+
+        res = []
+        candSignatures = []
+        for entry in matches:
+            text_options = {'no-link': u'', 'outline': u''}
+
+            # Render the matches to docutils nodes
+            target_handler = create_target_handler({'no-link': u''},
+                                                   project_info, self.state.document)
+            filter_ = self.filter_factory.create_outline_filter(text_options)
+            mask_factory = MaskFactory({'param': NoParameterNamesMask})
+
+            # Override the directive args for this render
+            directive_args = self.directive_args[:]
+            directive_args[2] = text_options
+
+            signature = self._create_function_signature(entry, project_info, filter_,
+                                                        target_handler,
+                                                        mask_factory, directive_args)
+            candSignatures.append(signature)
+
+            if args is not None:
+                match = re.match(r"([^(]*)(.*)", signature)
+                assert match
+                _match_args = match.group(2)
+
+                # Parse the text to find the arguments
+                # This one should succeed as it came from _create_function_signature
+                match_args = self._parse_args(_match_args)
+
+                # Match them against the arg spec
+                if args != match_args:
+                    continue
+
+            res.append((entry, signature))
+
+        if len(res) == 1:
+            return res[0][0]
+        else:
+            raise _UnableToResolveFunctionError(candSignatures)
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/index.py breathe-4.30.0-new/breathe/directive/index.py
--- breathe-4.30.0/breathe/directive/index.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/directive/index.py	2021-08-11 14:08:01.254457610 +0200
@@ -1,8 +1,7 @@
 from ..renderer.mask import NullMaskFactory
-from ..directive.base import BaseDirective
+from ..directives import BaseDirective
 from ..project import ProjectError
 from ..parser import ParserError, FileIOError
-from .base import create_warning
 
 from breathe.renderer import format_parser_error, RenderContext
 from breathe.renderer.sphinxrenderer import SphinxRenderer
@@ -11,12 +10,11 @@ from breathe.renderer.target import crea
 from docutils.parsers.rst.directives import unchanged_required, flag
 
 
-class RootDataObject(object):
-
+class RootDataObject:
     node_type = "root"
 
 
-class BaseIndexDirective(BaseDirective):
+class _BaseIndexDirective(BaseDirective):
     """Base class handle the main work when given the appropriate project info to work from.
     """
 
@@ -25,7 +23,6 @@ class BaseIndexDirective(BaseDirective):
     # pass way too much stuff to a helper object to be reasonable.
 
     def handle_contents(self, project_info):
-
         try:
             finder = self.finder_factory.create_finder(project_info)
         except ParserError as e:
@@ -65,7 +62,7 @@ class BaseIndexDirective(BaseDirective):
         return node_list
 
 
-class DoxygenIndexDirective(BaseIndexDirective):
+class DoxygenIndexDirective(_BaseIndexDirective):
     required_arguments = 0
     optional_arguments = 2
     option_spec = {
@@ -73,7 +70,7 @@ class DoxygenIndexDirective(BaseIndexDir
         "project": unchanged_required,
         "outline": flag,
         "no-link": flag,
-        }
+    }
     has_content = False
 
     def run(self):
@@ -82,21 +79,20 @@ class DoxygenIndexDirective(BaseIndexDir
         try:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno)
+            warning = self.create_warning(None)
             return warning.warn('doxygenindex: %s' % e)
 
         return self.handle_contents(project_info)
 
 
-class AutoDoxygenIndexDirective(BaseIndexDirective):
-
+class AutoDoxygenIndexDirective(_BaseIndexDirective):
     required_arguments = 0
     final_argument_whitespace = True
     option_spec = {
         "project": unchanged_required,
         "outline": flag,
         "no-link": flag,
-        }
+    }
     has_content = False
 
     def run(self):
@@ -107,7 +103,7 @@ class AutoDoxygenIndexDirective(BaseInde
         try:
             project_info = self.project_info_factory.retrieve_project_info_for_auto(self.options)
         except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno)
+            warning = self.create_warning(None)
             return warning.warn('autodoxygenindex: %s' % e)
 
         return self.handle_contents(project_info)
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/__init__.py breathe-4.30.0-new/breathe/directive/__init__.py
--- breathe-4.30.0/breathe/directive/__init__.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/directive/__init__.py	2021-08-11 14:08:01.254457610 +0200
@@ -0,0 +1,95 @@
+from breathe.finder.factory import FinderFactory
+from breathe.parser import DoxygenParserFactory
+from breathe.parser import FileIOError, ParserError
+from breathe.project import ProjectInfoFactory, ProjectInfo
+from breathe.renderer import format_parser_error, RenderContext
+from breathe.renderer.filter import Filter, FilterFactory
+from breathe.renderer.mask import MaskFactoryBase
+from breathe.renderer.sphinxrenderer import SphinxRenderer
+from breathe.renderer.target import TargetHandler
+
+from sphinx.directives import SphinxDirective
+
+from docutils import nodes
+from docutils.nodes import Node
+
+from typing import Any, Dict, List, Optional, Sequence
+
+
+class _WarningHandler:
+    def __init__(self, state, context: Dict[str, Any]) -> None:
+        self.state = state
+        self.context = context
+
+    def warn(self, raw_text: str, *, rendered_nodes: Sequence[nodes.Node] = None,
+             unformatted_suffix: str = '') -> List[nodes.Node]:
+        raw_text = self.format(raw_text) + unformatted_suffix
+        if rendered_nodes is None:
+            rendered_nodes = [nodes.paragraph("", "", nodes.Text(raw_text))]
+        return [
+            nodes.warning("", *rendered_nodes),
+            self.state.document.reporter.warning(raw_text, line=self.context['lineno'])
+        ]
+
+    def format(self, text: str) -> str:
+        return text.format(**self.context)
+
+
+class BaseDirective(SphinxDirective):
+    def __init__(self, finder_factory: FinderFactory,
+                 project_info_factory: ProjectInfoFactory,
+                 parser_factory: DoxygenParserFactory,
+                 *args) -> None:
+        super().__init__(*args)
+        self.directive_args = list(args)  # Convert tuple to list to allow modification.
+
+        self.finder_factory = finder_factory
+        self.project_info_factory = project_info_factory
+        self.filter_factory = FilterFactory(self.env.app)
+        self.parser_factory = parser_factory
+
+    @property
+    def kind(self) -> str:
+        raise NotImplementedError
+
+    def create_warning(self, project_info: Optional[ProjectInfo], **kwargs) -> _WarningHandler:
+        if project_info:
+            tail = 'in doxygen xml output for project "{project}" from directory: {path}'.format(
+                project=project_info.name(),
+                path=project_info.project_path()
+            )
+        else:
+            tail = ''
+
+        context = dict(
+            lineno=self.lineno,
+            tail=tail,
+            **kwargs
+        )
+        return _WarningHandler(self.state, context)
+
+    def render(self, node_stack, project_info: ProjectInfo, filter_: Filter,
+               target_handler: TargetHandler, mask_factory: MaskFactoryBase,
+               directive_args) -> List[Node]:
+        "Standard render process used by subclasses"
+
+        try:
+            object_renderer = SphinxRenderer(
+                self.parser_factory.app,
+                project_info,
+                node_stack,
+                self.state,
+                self.state.document,
+                target_handler,
+                self.parser_factory.create_compound_parser(project_info),
+                filter_
+            )
+        except ParserError as e:
+            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
+                                       self.lineno, True)
+        except FileIOError as e:
+            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
+                                       self.lineno, True)
+
+        context = RenderContext(node_stack, mask_factory, directive_args)
+        return object_renderer.render(node_stack[0], context)
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/item.py breathe-4.30.0-new/breathe/directive/item.py
--- breathe-4.30.0/breathe/directive/item.py	1970-01-01 01:00:00.000000000 +0100
+++ breathe-4.30.0-new/breathe/directive/item.py	2021-08-11 14:08:01.254457610 +0200
@@ -0,0 +1,209 @@
+from breathe.directive import BaseDirective
+from breathe.file_state_cache import MTimeError
+from breathe.project import ProjectError
+from breathe.renderer import RenderContext
+from breathe.renderer.filter import Filter
+from breathe.renderer.mask import NullMaskFactory
+from breathe.renderer.sphinxrenderer import SphinxRenderer
+from breathe.renderer.target import create_target_handler
+
+from docutils.nodes import Node
+from docutils.parsers.rst.directives import unchanged_required, flag
+
+from typing import Any, List, Optional, Type  # noqa
+
+
+class _DoxygenContentBlockDirective(BaseDirective):
+    """Base class for namespace and group directives which have very similar behaviours"""
+
+    required_arguments = 1
+    optional_arguments = 1
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "content-only": flag,
+        "outline": flag,
+        "members": flag,
+        "protected-members": flag,
+        "private-members": flag,
+        "undoc-members": flag,
+        "no-link": flag
+    }
+    has_content = False
+
+    def run(self) -> List[Node]:
+        name = self.arguments[0]
+
+        try:
+            project_info = self.project_info_factory.create_project_info(self.options)
+        except ProjectError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        try:
+            finder = self.finder_factory.create_finder(project_info)
+        except MTimeError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        finder_filter = self.filter_factory.create_finder_filter(self.kind, name)
+
+        # TODO: find a more specific type for the Doxygen nodes
+        matches = []  # type: List[Any]
+        finder.filter_(finder_filter, matches)
+
+        # It shouldn't be possible to have too many matches as namespaces & groups in their nature
+        # are merged together if there are multiple declarations, so we only check for no matches
+        if not matches:
+            warning = self.create_warning(project_info, name=name, kind=self.kind)
+            return warning.warn('doxygen{kind}: Cannot find {kind} "{name}" {tail}')
+
+        if 'content-only' in self.options and self.kind != "page":
+            # Unpack the single entry in the matches list
+            (node_stack,) = matches
+
+            filter_ = self.filter_factory.create_content_filter(self.kind, self.options)
+            # Having found the compound node for the namespace or group in the index we want to grab
+            # the contents of it which match the filter
+            contents_finder = self.finder_factory.create_finder_from_root(node_stack[0],
+                                                                          project_info)
+            # TODO: find a more specific type for the Doxygen nodes
+            contents = []  # type: List[Any]
+            contents_finder.filter_(filter_, contents)
+
+            # Replaces matches with our new starting points
+            matches = contents
+
+        target_handler = create_target_handler(self.options, project_info, self.state.document)
+        filter_ = self.filter_factory.create_render_filter(self.kind, self.options)
+
+        node_list = []
+        for node_stack in matches:
+            object_renderer = SphinxRenderer(
+                self.parser_factory.app,
+                project_info,
+                node_stack,
+                self.state,
+                self.state.document,
+                target_handler,
+                self.parser_factory.create_compound_parser(project_info),
+                filter_
+            )
+
+            mask_factory = NullMaskFactory()
+            context = RenderContext(node_stack, mask_factory, self.directive_args)
+            node_list.extend(object_renderer.render(context.node_stack[0], context))
+
+        return node_list
+
+
+class DoxygenNamespaceDirective(_DoxygenContentBlockDirective):
+    kind = "namespace"
+
+
+class DoxygenGroupDirective(_DoxygenContentBlockDirective):
+    kind = "group"
+    option_spec = {
+        **_DoxygenContentBlockDirective.option_spec,
+        "inner": flag,
+    }
+
+
+class DoxygenPageDirective(_DoxygenContentBlockDirective):
+    kind = "page"
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "content-only": flag,
+    }
+
+
+class _DoxygenBaseItemDirective(BaseDirective):
+    required_arguments = 1
+    optional_arguments = 1
+    option_spec = {
+        "path": unchanged_required,
+        "project": unchanged_required,
+        "outline": flag,
+        "no-link": flag,
+        }
+    has_content = False
+
+    def create_finder_filter(self, namespace: str, name: str) -> Filter:
+        """Creates a filter to find the node corresponding to this item."""
+
+        return self.filter_factory.create_member_finder_filter(
+            namespace, name, self.kind)
+
+    def run(self) -> List[Node]:
+        try:
+            namespace, name = self.arguments[0].rsplit("::", 1)
+        except ValueError:
+            namespace, name = "", self.arguments[0]
+
+        try:
+            project_info = self.project_info_factory.create_project_info(self.options)
+        except ProjectError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        try:
+            finder = self.finder_factory.create_finder(project_info)
+        except MTimeError as e:
+            warning = self.create_warning(None, kind=self.kind)
+            return warning.warn('doxygen{kind}: %s' % e)
+
+        finder_filter = self.create_finder_filter(namespace, name)
+
+        # TODO: find a more specific type for the Doxygen nodes
+        matches = []  # type: List[Any]
+        finder.filter_(finder_filter, matches)
+
+        if len(matches) == 0:
+            display_name = "%s::%s" % (namespace, name) if namespace else name
+            warning = self.create_warning(project_info, kind=self.kind,
+                                          display_name=display_name)
+            return warning.warn('doxygen{kind}: Cannot find {kind} "{display_name}" {tail}')
+
+        target_handler = create_target_handler(self.options, project_info, self.state.document)
+        filter_ = self.filter_factory.create_outline_filter(self.options)
+
+        node_stack = matches[0]
+        mask_factory = NullMaskFactory()
+        return self.render(node_stack, project_info, filter_, target_handler, mask_factory,
+                           self.directive_args)
+
+
+class DoxygenVariableDirective(_DoxygenBaseItemDirective):
+    kind = "variable"
+
+
+class DoxygenDefineDirective(_DoxygenBaseItemDirective):
+    kind = "define"
+
+
+class DoxygenEnumDirective(_DoxygenBaseItemDirective):
+    kind = "enum"
+
+
+class DoxygenEnumValueDirective(_DoxygenBaseItemDirective):
+    kind = "enumvalue"
+
+    def create_finder_filter(self, namespace: str, name: str) -> Filter:
+        return self.filter_factory.create_enumvalue_finder_filter(name)
+
+
+class DoxygenTypedefDirective(_DoxygenBaseItemDirective):
+    kind = "typedef"
+
+
+class DoxygenUnionDirective(_DoxygenBaseItemDirective):
+    kind = "union"
+
+    def create_finder_filter(self, namespace: str, name: str) -> Filter:
+        # Unions are stored in the xml file with their fully namespaced name
+        # We're using C++ namespaces here, it might be best to make this file
+        # type dependent
+        #
+        xml_name = "%s::%s" % (namespace, name) if namespace else name
+        return self.filter_factory.create_compound_finder_filter(xml_name, 'union')
diff -rupN --no-dereference breathe-4.30.0/breathe/directive/setup.py breathe-4.30.0-new/breathe/directive/setup.py
--- breathe-4.30.0/breathe/directive/setup.py	1970-01-01 01:00:00.000000000 +0100
+++ breathe-4.30.0-new/breathe/directive/setup.py	2021-08-11 14:08:01.254457610 +0200
@@ -0,0 +1,133 @@
+from breathe.directive import BaseDirective
+from breathe.directive.class_like import (
+    DoxygenStructDirective, DoxygenClassDirective, DoxygenInterfaceDirective,
+)
+from breathe.directive.content_block import (
+    DoxygenNamespaceDirective, DoxygenGroupDirective, DoxygenPageDirective,
+)
+from breathe.directive.file import DoxygenFileDirective, AutoDoxygenFileDirective
+from breathe.directive.function import DoxygenFunctionDirective
+from breathe.directive.index import DoxygenIndexDirective, AutoDoxygenIndexDirective
+from breathe.directive.item import (
+    DoxygenVariableDirective, DoxygenDefineDirective, DoxygenUnionDirective,
+    DoxygenEnumDirective, DoxygenEnumValueDirective, DoxygenTypedefDirective,
+)
+from breathe.finder.factory import FinderFactory
+from breathe.parser import DoxygenParserFactory
+from breathe.project import ProjectInfoFactory
+from breathe.process import AutoDoxygenProcessHandle
+
+from sphinx.application import Sphinx
+
+import os
+import subprocess
+
+from typing import Any, List, Optional, Type  # noqa
+
+
+class DirectiveContainer:
+    def __init__(self, app: Sphinx, directive: Type[BaseDirective],
+                 finder_factory: FinderFactory, project_info_factory: ProjectInfoFactory,
+                 parser_factory: DoxygenParserFactory):
+        self.app = app
+        self.directive = directive
+        self.finder_factory = finder_factory
+        self.project_info_factory = project_info_factory
+        self.parser_factory = parser_factory
+
+        # Required for sphinx to inspect
+        self.required_arguments = directive.required_arguments
+        self.optional_arguments = directive.optional_arguments
+        self.option_spec = directive.option_spec
+        self.has_content = directive.has_content
+        self.final_argument_whitespace = directive.final_argument_whitespace
+
+    def __call__(self, *args):
+        call_args = [
+            self.finder_factory,
+            self.project_info_factory,
+            self.parser_factory
+        ]
+        call_args.extend(args)
+        return self.directive(*call_args)
+
+
+def setup(app: Sphinx) -> None:
+    directives = {
+        "doxygenindex": DoxygenIndexDirective,
+        "autodoxygenindex": AutoDoxygenIndexDirective,
+        "doxygenfunction": DoxygenFunctionDirective,
+        "doxygenstruct": DoxygenStructDirective,
+        "doxygenclass": DoxygenClassDirective,
+        "doxygeninterface": DoxygenInterfaceDirective,
+        "doxygenvariable": DoxygenVariableDirective,
+        "doxygendefine": DoxygenDefineDirective,
+        "doxygenenum": DoxygenEnumDirective,
+        "doxygenenumvalue": DoxygenEnumValueDirective,
+        "doxygentypedef": DoxygenTypedefDirective,
+        "doxygenunion": DoxygenUnionDirective,
+        "doxygennamespace": DoxygenNamespaceDirective,
+        "doxygengroup": DoxygenGroupDirective,
+        "doxygenfile": DoxygenFileDirective,
+        "autodoxygenfile": AutoDoxygenFileDirective,
+        "doxygenpage": DoxygenPageDirective,
+    }
+
+    # note: the parser factory contains a cache of the parsed XML
+    # note: the project_info_factory also contains some caching stuff
+    # TODO: is that actually safe for when reading in parallel?
+    project_info_factory = ProjectInfoFactory(app)
+    parser_factory = DoxygenParserFactory(app)
+    finder_factory = FinderFactory(app, parser_factory)
+    for name, directive in directives.items():
+        # ordinarily app.add_directive takes a class it self, but we need to inject extra arguments
+        # so we give a DirectiveContainer object which has an overloaded __call__ operator.
+        app.add_directive(name, DirectiveContainer(  # type: ignore
+            app,
+            directive,
+            finder_factory,
+            project_info_factory,
+            parser_factory
+            ))
+
+    app.add_config_value("breathe_projects", {}, True)  # Dict[str, str]
+    app.add_config_value("breathe_default_project", "", True)  # str
+    # Provide reasonable defaults for domain_by_extension mapping. Can be overridden by users.
+    app.add_config_value("breathe_domain_by_extension",
+                         {'py': 'py', 'cs': 'cs'}, True)  # Dict[str, str]
+    app.add_config_value("breathe_domain_by_file_pattern", {}, True)  # Dict[str, str]
+    app.add_config_value("breathe_projects_source", {}, True)
+    app.add_config_value("breathe_build_directory", '', True)
+    app.add_config_value("breathe_default_members", (), True)
+    app.add_config_value("breathe_show_define_initializer", False, 'env')
+    app.add_config_value("breathe_show_enumvalue_initializer", False, 'env')
+    app.add_config_value("breathe_implementation_filename_extensions", ['.c', '.cc', '.cpp'], True)
+    app.add_config_value("breathe_doxygen_config_options", {}, True)
+    app.add_config_value("breathe_use_project_refids", False, "env")
+    app.add_config_value("breathe_order_parameters_first", False, 'env')
+    app.add_config_value("breathe_separate_member_pages", False, 'env')
+
+    breathe_css = "breathe.css"
+    if (os.path.exists(os.path.join(app.confdir, "_static", breathe_css))):  # type: ignore
+        app.add_css_file(breathe_css)
+
+    def write_file(directory, filename, content):
+        # Check the directory exists
+        if not os.path.exists(directory):
+            os.makedirs(directory)
+
+        # Write the file with the provided contents
+        with open(os.path.join(directory, filename), "w") as f:
+            f.write(content)
+
+    doxygen_handle = AutoDoxygenProcessHandle(
+        subprocess.check_call,
+        write_file,
+        project_info_factory)
+
+    def doxygen_hook(app):
+        doxygen_handle.generate_xml(
+            app.config.breathe_projects_source,
+            app.config.breathe_doxygen_config_options
+        )
+    app.connect("builder-inited", doxygen_hook)
diff -rupN --no-dereference breathe-4.30.0/breathe/directives.py breathe-4.30.0-new/breathe/directives.py
--- breathe-4.30.0/breathe/directives.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/directives.py	2021-08-11 14:08:01.255457584 +0200
@@ -1,416 +1,16 @@
-from breathe.directive.base import create_warning
-from breathe.directive.file import DoxygenFileDirective, AutoDoxygenFileDirective
-from breathe.directive.index import DoxygenIndexDirective, AutoDoxygenIndexDirective
-from breathe.exception import BreatheError
-from breathe.finder.factory import FinderFactory
-from breathe.directive.base import BaseDirective
+from breathe.directive import BaseDirective
 from breathe.file_state_cache import MTimeError
-from breathe.parser import DoxygenParserFactory, ParserError, FileIOError
-from breathe.process import AutoDoxygenProcessHandle
-from breathe.project import ProjectInfoFactory, ProjectError
-from breathe.renderer import format_parser_error, RenderContext
-from breathe.renderer.sphinxrenderer import WithContext
+from breathe.project import ProjectError
 from breathe.renderer.filter import Filter
-from breathe.renderer.mask import (
-    MaskFactory, NullMaskFactory, NoParameterNamesMask
-)
-from breathe.renderer.sphinxrenderer import SphinxRenderer
+from breathe.renderer.mask import NullMaskFactory
 from breathe.renderer.target import create_target_handler
 
-from sphinx.application import Sphinx
-from sphinx.domains import cpp
-
-from docutils import nodes
 from docutils.nodes import Node
-from docutils.parsers.rst.directives import unchanged_required, unchanged, flag
-
-import os
-import re
-import subprocess
+from docutils.parsers.rst.directives import unchanged_required, flag
 
 from typing import Any, List, Optional, Type  # noqa
 
 
-class NoMatchingFunctionError(BreatheError):
-    pass
-
-
-class UnableToResolveFunctionError(BreatheError):
-    def __init__(self, signatures: List[str]) -> None:
-        self.signatures = signatures
-
-
-class DoxygenFunctionDirective(BaseDirective):
-    required_arguments = 1
-    option_spec = {
-        "path": unchanged_required,
-        "project": unchanged_required,
-        "outline": flag,
-        "no-link": flag,
-    }
-    has_content = False
-    final_argument_whitespace = True
-
-    def run(self) -> List[Node]:
-        # Separate possible arguments (delimited by a "(") from the namespace::name
-        match = re.match(r"([^(]*)(.*)", self.arguments[0])
-        assert match is not None  # TODO: this is probably not appropriate, for now it fixes typing
-        namespaced_function, args = match.group(1), match.group(2)
-
-        # Split the namespace and the function name
-        try:
-            (namespace, function_name) = namespaced_function.rsplit("::", 1)
-        except ValueError:
-            (namespace, function_name) = "", namespaced_function
-        namespace = namespace.strip()
-        function_name = function_name.strip()
-
-        try:
-            project_info = self.project_info_factory.create_project_info(self.options)
-        except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno)
-            return warning.warn('doxygenfunction: %s' % e)
-
-        try:
-            finder = self.finder_factory.create_finder(project_info)
-        except MTimeError as e:
-            warning = create_warning(None, self.state, self.lineno)
-            return warning.warn('doxygenfunction: %s' % e)
-
-        # Extract arguments from the function name.
-        args = self._parse_args(args)
-
-        finder_filter = self.filter_factory.create_function_and_all_friend_finder_filter(
-            namespace, function_name)
-
-        # TODO: find a more specific type for the Doxygen nodes
-        matchesAll = []  # type: List[Any]
-        finder.filter_(finder_filter, matchesAll)
-        matches = []
-        for m in matchesAll:
-            # only take functions and friend functions
-            # ignore friend classes
-            node = m[0]
-            if node.kind == 'friend' and not node.argsstring:
-                continue
-            matches.append(m)
-
-        # Create it ahead of time as it is cheap and it is ugly to declare it for both exception
-        # clauses below
-        warning = create_warning(
-            project_info,
-            self.state,
-            self.lineno,
-            namespace='%s::' % namespace if namespace else '',
-            function=function_name,
-            args=str(args)
-            )
-
-        try:
-            node_stack = self._resolve_function(matches, args, project_info)
-        except NoMatchingFunctionError:
-            return warning.warn('doxygenfunction: Cannot find function "{namespace}{function}" '
-                                '{tail}')
-        except UnableToResolveFunctionError as error:
-            message = 'doxygenfunction: Unable to resolve function ' \
-                '"{namespace}{function}" with arguments {args} {tail}.\n' \
-                'Potential matches:\n'
-
-            text = ''
-            for i, entry in enumerate(sorted(error.signatures)):
-                text += '- %s\n' % entry
-            block = nodes.literal_block('', '', nodes.Text(text))
-            formatted_message = warning.format(message)
-            warning_nodes = [
-                nodes.paragraph("", "", nodes.Text(formatted_message)),
-                block
-            ]
-            result = warning.warn(message, rendered_nodes=warning_nodes,
-                                  unformatted_suffix=text)
-            return result
-
-        target_handler = create_target_handler(self.options, project_info, self.state.document)
-        filter_ = self.filter_factory.create_outline_filter(self.options)
-
-        return self.render(node_stack, project_info, filter_, target_handler, NullMaskFactory(),
-                           self.directive_args)
-
-    def _parse_args(self, function_description: str) -> Optional[cpp.ASTParametersQualifiers]:
-        if function_description == '':
-            return None
-
-        parser = cpp.DefinitionParser(function_description,
-                                      location=self.get_source_info(),
-                                      config=self.config)
-        paramQual = parser._parse_parameters_and_qualifiers(paramMode='function')
-        # now erase the parameter names
-        for p in paramQual.args:
-            if p.arg is None:
-                assert p.ellipsis
-                continue
-            declarator = p.arg.type.decl
-            while hasattr(declarator, 'next'):
-                declarator = declarator.next  # type: ignore
-            assert hasattr(declarator, 'declId')
-            declarator.declId = None  # type: ignore
-            p.arg.init = None  # type: ignore
-        return paramQual
-
-    def _create_function_signature(self, node_stack, project_info, filter_, target_handler,
-                                   mask_factory, directive_args) -> str:
-        "Standard render process used by subclasses"
-
-        try:
-            object_renderer = SphinxRenderer(
-                self.parser_factory.app,
-                project_info,
-                node_stack,
-                self.state,
-                self.state.document,
-                target_handler,
-                self.parser_factory.create_compound_parser(project_info),
-                filter_,
-            )
-        except ParserError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, True)
-        except FileIOError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, False)
-
-        context = RenderContext(node_stack, mask_factory, directive_args)
-        node = node_stack[0]
-        with WithContext(object_renderer, context):
-            # this part should be kept in sync with visit_function in sphinxrenderer
-            name = node.get_name()
-            # assume we are only doing this for C++ declarations
-            declaration = ' '.join([
-                object_renderer.create_template_prefix(node),
-                ''.join(n.astext() for n in object_renderer.render(node.get_type())),
-                name,
-                node.get_argsstring()
-            ])
-        parser = cpp.DefinitionParser(declaration,
-                                      location=self.get_source_info(),
-                                      config=self.config)
-        ast = parser.parse_declaration('function', 'function')
-        return str(ast)
-
-    def _resolve_function(self, matches, args: Optional[cpp.ASTParametersQualifiers], project_info):
-        if not matches:
-            raise NoMatchingFunctionError()
-
-        res = []
-        candSignatures = []
-        for entry in matches:
-            text_options = {'no-link': u'', 'outline': u''}
-
-            # Render the matches to docutils nodes
-            target_handler = create_target_handler({'no-link': u''},
-                                                   project_info, self.state.document)
-            filter_ = self.filter_factory.create_outline_filter(text_options)
-            mask_factory = MaskFactory({'param': NoParameterNamesMask})
-
-            # Override the directive args for this render
-            directive_args = self.directive_args[:]
-            directive_args[2] = text_options
-
-            signature = self._create_function_signature(entry, project_info, filter_,
-                                                        target_handler,
-                                                        mask_factory, directive_args)
-            candSignatures.append(signature)
-
-            if args is not None:
-                match = re.match(r"([^(]*)(.*)", signature)
-                assert match
-                _match_args = match.group(2)
-
-                # Parse the text to find the arguments
-                match_args = self._parse_args(_match_args)
-
-                # Match them against the arg spec
-                if args != match_args:
-                    continue
-
-            res.append((entry, signature))
-
-        if len(res) == 1:
-            return res[0][0]
-        else:
-            raise UnableToResolveFunctionError(candSignatures)
-
-
-class _DoxygenClassLikeDirective(BaseDirective):
-    required_arguments = 1
-    optional_arguments = 0
-    final_argument_whitespace = True
-    option_spec = {
-        "path": unchanged_required,
-        "project": unchanged_required,
-        "members": unchanged,
-        "membergroups": unchanged_required,
-        "members-only": flag,
-        "protected-members": flag,
-        "private-members": flag,
-        "undoc-members": flag,
-        "show": unchanged_required,
-        "outline": flag,
-        "no-link": flag,
-    }
-    has_content = False
-
-    def run(self) -> List[Node]:
-        name = self.arguments[0]
-
-        try:
-            project_info = self.project_info_factory.create_project_info(self.options)
-        except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
-
-        try:
-            finder = self.finder_factory.create_finder(project_info)
-        except MTimeError as e:
-            warning = create_warning(None, self.state, self.lineno, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
-
-        finder_filter = self.filter_factory.create_compound_finder_filter(name, self.kind)
-
-        # TODO: find a more specific type for the Doxygen nodes
-        matches = []  # type: List[Any]
-        finder.filter_(finder_filter, matches)
-
-        if len(matches) == 0:
-            warning = create_warning(project_info, self.state, self.lineno, name=name,
-                                     kind=self.kind)
-            return warning.warn('doxygen{kind}: Cannot find class "{name}" {tail}')
-
-        target_handler = create_target_handler(self.options, project_info, self.state.document)
-        filter_ = self.filter_factory.create_class_filter(name, self.options)
-
-        mask_factory = NullMaskFactory()
-        return self.render(matches[0], project_info, filter_, target_handler, mask_factory,
-                           self.directive_args)
-
-
-class DoxygenClassDirective(_DoxygenClassLikeDirective):
-    kind = "class"
-
-
-class DoxygenStructDirective(_DoxygenClassLikeDirective):
-    kind = "struct"
-
-
-class DoxygenInterfaceDirective(_DoxygenClassLikeDirective):
-    kind = "interface"
-
-
-class _DoxygenContentBlockDirective(BaseDirective):
-    """Base class for namespace and group directives which have very similar behaviours"""
-
-    required_arguments = 1
-    optional_arguments = 1
-    option_spec = {
-        "path": unchanged_required,
-        "project": unchanged_required,
-        "content-only": flag,
-        "outline": flag,
-        "members": flag,
-        "protected-members": flag,
-        "private-members": flag,
-        "undoc-members": flag,
-        "no-link": flag
-    }
-    has_content = False
-
-    def run(self) -> List[Node]:
-        name = self.arguments[0]
-
-        try:
-            project_info = self.project_info_factory.create_project_info(self.options)
-        except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
-
-        try:
-            finder = self.finder_factory.create_finder(project_info)
-        except MTimeError as e:
-            warning = create_warning(None, self.state, self.lineno, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
-
-        finder_filter = self.filter_factory.create_finder_filter(self.kind, name)
-
-        # TODO: find a more specific type for the Doxygen nodes
-        matches = []  # type: List[Any]
-        finder.filter_(finder_filter, matches)
-
-        # It shouldn't be possible to have too many matches as namespaces & groups in their nature
-        # are merged together if there are multiple declarations, so we only check for no matches
-        if not matches:
-            warning = create_warning(project_info, self.state, self.lineno, name=name,
-                                     kind=self.kind)
-            return warning.warn('doxygen{kind}: Cannot find {kind} "{name}" {tail}')
-
-        if 'content-only' in self.options and self.kind != "page":
-            # Unpack the single entry in the matches list
-            (node_stack,) = matches
-
-            filter_ = self.filter_factory.create_content_filter(self.kind, self.options)
-            # Having found the compound node for the namespace or group in the index we want to grab
-            # the contents of it which match the filter
-            contents_finder = self.finder_factory.create_finder_from_root(node_stack[0],
-                                                                          project_info)
-            # TODO: find a more specific type for the Doxygen nodes
-            contents = []  # type: List[Any]
-            contents_finder.filter_(filter_, contents)
-
-            # Replaces matches with our new starting points
-            matches = contents
-
-        target_handler = create_target_handler(self.options, project_info, self.state.document)
-        filter_ = self.filter_factory.create_render_filter(self.kind, self.options)
-
-        node_list = []
-        for node_stack in matches:
-            object_renderer = SphinxRenderer(
-                self.parser_factory.app,
-                project_info,
-                node_stack,
-                self.state,
-                self.state.document,
-                target_handler,
-                self.parser_factory.create_compound_parser(project_info),
-                filter_
-            )
-
-            mask_factory = NullMaskFactory()
-            context = RenderContext(node_stack, mask_factory, self.directive_args)
-            node_list.extend(object_renderer.render(context.node_stack[0], context))
-
-        return node_list
-
-
-class DoxygenNamespaceDirective(_DoxygenContentBlockDirective):
-    kind = "namespace"
-
-
-class DoxygenGroupDirective(_DoxygenContentBlockDirective):
-    kind = "group"
-    option_spec = {
-        **_DoxygenContentBlockDirective.option_spec,
-        "inner": flag,
-    }
-
-
-class DoxygenPageDirective(_DoxygenContentBlockDirective):
-    kind = "page"
-    option_spec = {
-        "path": unchanged_required,
-        "project": unchanged_required,
-        "content-only": flag,
-    }
-
-
 # TODO: is this comment still relevant?
 # This class was the same as the DoxygenBaseDirective above, except that it
 # wraps the output in a definition_list before passing it back. This should be
@@ -443,13 +43,13 @@ class _DoxygenBaseItemDirective(BaseDire
         try:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
-            warning = create_warning(None, self.state, self.lineno, kind=self.kind)
+            warning = self.create_warning(None, kind=self.kind)
             return warning.warn('doxygen{kind}: %s' % e)
 
         try:
             finder = self.finder_factory.create_finder(project_info)
         except MTimeError as e:
-            warning = create_warning(None, self.state, self.lineno, kind=self.kind)
+            warning = self.create_warning(None, kind=self.kind)
             return warning.warn('doxygen{kind}: %s' % e)
 
         finder_filter = self.create_finder_filter(namespace, name)
@@ -460,8 +60,8 @@ class _DoxygenBaseItemDirective(BaseDire
 
         if len(matches) == 0:
             display_name = "%s::%s" % (namespace, name) if namespace else name
-            warning = create_warning(project_info, self.state, self.lineno, kind=self.kind,
-                                     display_name=display_name)
+            warning = self.create_warning(project_info, kind=self.kind,
+                                          display_name=display_name)
             return warning.warn('doxygen{kind}: Cannot find {kind} "{display_name}" {tail}')
 
         target_handler = create_target_handler(self.options, project_info, self.state.document)
@@ -506,114 +106,3 @@ class DoxygenUnionDirective(_DoxygenBase
         #
         xml_name = "%s::%s" % (namespace, name) if namespace else name
         return self.filter_factory.create_compound_finder_filter(xml_name, 'union')
-
-
-# Setup Administration
-# --------------------
-
-class DirectiveContainer:
-    def __init__(self, app: Sphinx, directive: Type[BaseDirective],
-                 finder_factory: FinderFactory, project_info_factory: ProjectInfoFactory,
-                 parser_factory: DoxygenParserFactory):
-        self.app = app
-        self.directive = directive
-        self.finder_factory = finder_factory
-        self.project_info_factory = project_info_factory
-        self.parser_factory = parser_factory
-
-        # Required for sphinx to inspect
-        self.required_arguments = directive.required_arguments
-        self.optional_arguments = directive.optional_arguments
-        self.option_spec = directive.option_spec
-        self.has_content = directive.has_content
-        self.final_argument_whitespace = directive.final_argument_whitespace
-
-    def __call__(self, *args):
-        call_args = [
-            self.finder_factory,
-            self.project_info_factory,
-            self.parser_factory
-        ]
-        call_args.extend(args)
-        return self.directive(*call_args)
-
-
-def setup(app: Sphinx) -> None:
-    directives = {
-        "doxygenindex": DoxygenIndexDirective,
-        "autodoxygenindex": AutoDoxygenIndexDirective,
-        "doxygenfunction": DoxygenFunctionDirective,
-        "doxygenstruct": DoxygenStructDirective,
-        "doxygenclass": DoxygenClassDirective,
-        "doxygeninterface": DoxygenInterfaceDirective,
-        "doxygenvariable": DoxygenVariableDirective,
-        "doxygendefine": DoxygenDefineDirective,
-        "doxygenenum": DoxygenEnumDirective,
-        "doxygenenumvalue": DoxygenEnumValueDirective,
-        "doxygentypedef": DoxygenTypedefDirective,
-        "doxygenunion": DoxygenUnionDirective,
-        "doxygennamespace": DoxygenNamespaceDirective,
-        "doxygengroup": DoxygenGroupDirective,
-        "doxygenfile": DoxygenFileDirective,
-        "autodoxygenfile": AutoDoxygenFileDirective,
-        "doxygenpage": DoxygenPageDirective,
-    }
-
-    # note: the parser factory contains a cache of the parsed XML
-    # note: the project_info_factory also contains some caching stuff
-    # TODO: is that actually safe for when reading in parallel?
-    project_info_factory = ProjectInfoFactory(app)
-    parser_factory = DoxygenParserFactory(app)
-    finder_factory = FinderFactory(app, parser_factory)
-    for name, directive in directives.items():
-        # ordinarily app.add_directive takes a class it self, but we need to inject extra arguments
-        # so we give a DirectiveContainer object which has an overloaded __call__ operator.
-        app.add_directive(name, DirectiveContainer(  # type: ignore
-            app,
-            directive,
-            finder_factory,
-            project_info_factory,
-            parser_factory
-            ))
-
-    app.add_config_value("breathe_projects", {}, True)  # Dict[str, str]
-    app.add_config_value("breathe_default_project", "", True)  # str
-    # Provide reasonable defaults for domain_by_extension mapping. Can be overridden by users.
-    app.add_config_value("breathe_domain_by_extension",
-                         {'py': 'py', 'cs': 'cs'}, True)  # Dict[str, str]
-    app.add_config_value("breathe_domain_by_file_pattern", {}, True)  # Dict[str, str]
-    app.add_config_value("breathe_projects_source", {}, True)
-    app.add_config_value("breathe_build_directory", '', True)
-    app.add_config_value("breathe_default_members", (), True)
-    app.add_config_value("breathe_show_define_initializer", False, 'env')
-    app.add_config_value("breathe_show_enumvalue_initializer", False, 'env')
-    app.add_config_value("breathe_implementation_filename_extensions", ['.c', '.cc', '.cpp'], True)
-    app.add_config_value("breathe_doxygen_config_options", {}, True)
-    app.add_config_value("breathe_use_project_refids", False, "env")
-    app.add_config_value("breathe_order_parameters_first", False, 'env')
-    app.add_config_value("breathe_separate_member_pages", False, 'env')
-
-    breathe_css = "breathe.css"
-    if (os.path.exists(os.path.join(app.confdir, "_static", breathe_css))):  # type: ignore
-        app.add_css_file(breathe_css)
-
-    def write_file(directory, filename, content):
-        # Check the directory exists
-        if not os.path.exists(directory):
-            os.makedirs(directory)
-
-        # Write the file with the provided contents
-        with open(os.path.join(directory, filename), "w") as f:
-            f.write(content)
-
-    doxygen_handle = AutoDoxygenProcessHandle(
-        subprocess.check_call,
-        write_file,
-        project_info_factory)
-
-    def doxygen_hook(app):
-        doxygen_handle.generate_xml(
-            app.config.breathe_projects_source,
-            app.config.breathe_doxygen_config_options
-        )
-    app.connect("builder-inited", doxygen_hook)
diff -rupN --no-dereference breathe-4.30.0/breathe/__init__.py breathe-4.30.0-new/breathe/__init__.py
--- breathe-4.30.0/breathe/__init__.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/__init__.py	2021-08-11 14:08:01.255457584 +0200
@@ -1,6 +1,6 @@
-from . import directives
-from . import file_state_cache
-from .renderer import sphinxrenderer
+from breathe.directive.setup import setup as directive_setup
+from breathe.file_state_cache import setup as file_state_cache_setup
+from breathe.renderer.sphinxrenderer import setup as renderer_setup
 
 from sphinx.application import Sphinx
 
@@ -8,9 +8,9 @@ __version__ = '4.30.0'
 
 
 def setup(app: Sphinx):
-    directives.setup(app)
-    file_state_cache.setup(app)
-    sphinxrenderer.setup(app)
+    directive_setup(app)
+    file_state_cache_setup(app)
+    renderer_setup(app)
 
     return {
         'version': __version__,
diff -rupN --no-dereference breathe-4.30.0/breathe/renderer/sphinxrenderer.py breathe-4.30.0-new/breathe/renderer/sphinxrenderer.py
--- breathe-4.30.0/breathe/renderer/sphinxrenderer.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/breathe/renderer/sphinxrenderer.py	2021-08-11 14:08:01.255457584 +0200
@@ -2019,8 +2019,8 @@ class SphinxRenderer:
         signode += nodes.Text(node.name)
         return [desc]
 
-    def visit_param(self, node: compound.paramTypeSub, *,
-                    insertDeclNameByParsing: bool = False) -> List[Node]:
+    def visit_templateparam(self, node: compound.paramTypeSub, *,
+                            insertDeclNameByParsing: bool = False) -> List[Node]:
         nodelist = []
 
         # Parameter type
@@ -2048,15 +2048,22 @@ class SphinxRenderer:
                         ''.join(n.astext() for n in nodelist),
                         location=self.state.state_machine.get_source_and_line(),
                         config=self.app.config)
-                    ast = parser._parse_type(named='single', outer='templateParam')
-                    assert ast.name is None
-                    nn = cpp.ASTNestedName(
-                        names=[cpp.ASTNestedNameElement(cpp.ASTIdentifier(node.declname), None)],
-                        templates=[False], rooted=False)
-                    ast.name = nn
-                    # the actual nodes don't matter, as it is astext()-ed later
-                    nodelist = [nodes.Text(str(ast))]
-                    appendDeclName = False
+                    try:
+                        # we really should use _parse_template_parameter()
+                        # but setting a name there is non-trivial, so we use type
+                        ast = parser._parse_type(named='single', outer='templateParam')
+                        assert ast.name is None
+                        nn = cpp.ASTNestedName(
+                            names=[cpp.ASTNestedNameElement(
+                                cpp.ASTIdentifier(node.declname), None)],
+                            templates=[False], rooted=False)
+                        ast.name = nn
+                        # the actual nodes don't matter, as it is astext()-ed later
+                        nodelist = [nodes.Text(str(ast))]
+                        appendDeclName = False
+                    except cpp.DefinitionError:
+                        # happens with "typename ...Args", so for now, just append
+                        pass
 
             if appendDeclName:
                 if nodelist:
@@ -2086,7 +2093,7 @@ class SphinxRenderer:
         for i, item in enumerate(node.param):
             if i:
                 nodelist.append(nodes.Text(", "))
-            nodelist.extend(self.visit_param(item, insertDeclNameByParsing=True))
+            nodelist.extend(self.visit_templateparam(item, insertDeclNameByParsing=True))
         self.output_defname = True
         return nodelist
 
@@ -2225,7 +2232,6 @@ class SphinxRenderer:
         "compoundref": visit_compoundref,
         "mixedcontainer": visit_mixedcontainer,
         "description": visit_description,
-        "param": visit_param,
         "templateparamlist": visit_templateparamlist,
         "docparamlist": visit_docparamlist,
         "docxrefsect": visit_docxrefsect,
diff -rupN --no-dereference breathe-4.30.0/tests/test_renderer.py breathe-4.30.0-new/tests/test_renderer.py
--- breathe-4.30.0/tests/test_renderer.py	2021-05-06 21:43:49.000000000 +0200
+++ breathe-4.30.0-new/tests/test_renderer.py	2021-08-11 14:08:01.256457559 +0200
@@ -422,7 +422,7 @@ def test_render_innergroup(app):
                                 options=['inner']))
 
 def get_directive(app):
-    from breathe.directives import DoxygenFunctionDirective
+    from breathe.directive.function import DoxygenFunctionDirective
     from breathe.project import ProjectInfoFactory
     from breathe.parser import DoxygenParserFactory
     from breathe.finder.factory import FinderFactory
