1from breathe.directives import BaseDirective
2from breathe.file_state_cache import MTimeError
3from breathe.project import ProjectError
4from breathe.renderer import RenderContext
5from breathe.renderer.mask import NullMaskFactory
6from breathe.renderer.sphinxrenderer import SphinxRenderer
7from breathe.renderer.target import create_target_handler
8
9from docutils.nodes import Node
10from docutils.parsers.rst.directives import unchanged_required, flag  # type: ignore
11
12from typing import Any, List, Optional, Type  # noqa
13
14
15class _DoxygenContentBlockDirective(BaseDirective):
16    """Base class for namespace and group directives which have very similar behaviours"""
17
18    required_arguments = 1
19    optional_arguments = 1
20    option_spec = {
21        "path": unchanged_required,
22        "project": unchanged_required,
23        "content-only": flag,
24        "outline": flag,
25        "members": flag,
26        "protected-members": flag,
27        "private-members": flag,
28        "undoc-members": flag,
29        "no-link": flag
30    }
31    has_content = False
32
33    def run(self) -> List[Node]:
34        name = self.arguments[0]
35
36        try:
37            project_info = self.project_info_factory.create_project_info(self.options)
38        except ProjectError as e:
39            warning = self.create_warning(None, kind=self.kind)
40            return warning.warn('doxygen{kind}: %s' % e)
41
42        try:
43            finder = self.finder_factory.create_finder(project_info)
44        except MTimeError as e:
45            warning = self.create_warning(None, kind=self.kind)
46            return warning.warn('doxygen{kind}: %s' % e)
47
48        finder_filter = self.filter_factory.create_finder_filter(self.kind, name)
49
50        # TODO: find a more specific type for the Doxygen nodes
51        matches = []  # type: List[Any]
52        finder.filter_(finder_filter, matches)
53
54        # It shouldn't be possible to have too many matches as namespaces & groups in their nature
55        # are merged together if there are multiple declarations, so we only check for no matches
56        if not matches:
57            warning = self.create_warning(project_info, name=name, kind=self.kind)
58            return warning.warn('doxygen{kind}: Cannot find {kind} "{name}" {tail}')
59
60        if 'content-only' in self.options and self.kind != "page":
61            # Unpack the single entry in the matches list
62            (node_stack,) = matches
63
64            filter_ = self.filter_factory.create_content_filter(self.kind, self.options)
65            # Having found the compound node for the namespace or group in the index we want to grab
66            # the contents of it which match the filter
67            contents_finder = self.finder_factory.create_finder_from_root(node_stack[0],
68                                                                          project_info)
69            # TODO: find a more specific type for the Doxygen nodes
70            contents = []  # type: List[Any]
71            contents_finder.filter_(filter_, contents)
72
73            # Replaces matches with our new starting points
74            matches = contents
75
76        target_handler = create_target_handler(self.options, project_info, self.state.document)
77        filter_ = self.filter_factory.create_render_filter(self.kind, self.options)
78
79        node_list = []
80        for node_stack in matches:
81            object_renderer = SphinxRenderer(
82                self.parser_factory.app,
83                project_info,
84                node_stack,
85                self.state,
86                self.state.document,
87                target_handler,
88                self.parser_factory.create_compound_parser(project_info),
89                filter_
90            )
91
92            mask_factory = NullMaskFactory()
93            context = RenderContext(node_stack, mask_factory, self.directive_args)
94            node_list.extend(object_renderer.render(context.node_stack[0], context))
95
96        return node_list
97
98
99class DoxygenNamespaceDirective(_DoxygenContentBlockDirective):
100    kind = "namespace"
101
102
103class DoxygenGroupDirective(_DoxygenContentBlockDirective):
104    kind = "group"
105    option_spec = {
106        **_DoxygenContentBlockDirective.option_spec,
107        "inner": flag,
108    }
109
110
111class DoxygenPageDirective(_DoxygenContentBlockDirective):
112    kind = "page"
113    option_spec = {
114        "path": unchanged_required,
115        "project": unchanged_required,
116        "content-only": flag,
117    }
118