diff --git a/.gitignore b/.gitignore index 3f8aba7..92e6ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ /breathe-4.29.1.tar.gz.sig /breathe-4.30.0.tar.gz.sig /v4.30.0.tar.gz +/breathe-4.31.0.tar.gz.sig +/v4.31.0.tar.gz diff --git a/0001-Handle-parsing-errors.patch b/0001-Handle-parsing-errors.patch deleted file mode 100644 index 5163a70..0000000 --- a/0001-Handle-parsing-errors.patch +++ /dev/null @@ -1,1774 +0,0 @@ -From bbf329963dea00a82fef4a7aa511fbfe216336c4 Mon Sep 17 00:00:00 2001 -From: Jakob Lykke Andersen -Date: Sun, 2 May 2021 11:26:07 +0200 -Subject: [PATCH] Handle parsing errors - -In a few places the C++ domain in Sphinx is used to parse fragments, but potential errors are not handled. This tries to fix that. -One case is the parsing of the function parameters in doxygenfunction, so .. doxygenfunction:: f( would crash Breathe. -The second case is parsing the function parameters in the XML, I don't know how to trigger that directly, but it should be handled now. -The third case is in the hax to insert the name in template parameters (#681). For parameters typename ...Args it will trigger a parsing error (observed in the pybind11 project). - -Make WarningHandler private - -Directives, make create_warning a method - -Move BaseDirective from directive.base to directive - -Make more base directive classes private - -Move directive setup to separate file - -Make function directive helpers private - -Move function directive to separate file - -Move class-like directives to separate file - -Move content block directives to separate file - -Move item directives to directive module - -Rename directive module to directives - -Handle parsing errors from the C++ domain - -Fix typo - -Co-authored-by: Bruce Merry <1963944+bmerry@users.noreply.github.com> ---- - Makefile | 2 +- - breathe/__init__.py | 12 +- - breathe/directive/__init__.py | 0 - breathe/directives.py | 619 ------------------ - .../base.py => directives/__init__.py} | 35 +- - breathe/directives/class_like.py | 74 +++ - breathe/directives/content_block.py | 117 ++++ - breathe/{directive => directives}/file.py | 28 +- - breathe/directives/function.py | 249 +++++++ - breathe/{directive => directives}/index.py | 22 +- - breathe/directives/item.py | 102 +++ - breathe/directives/setup.py | 133 ++++ - breathe/renderer/sphinxrenderer.py | 32 +- - tests/test_renderer.py | 2 +- - 14 files changed, 738 insertions(+), 689 deletions(-) - delete mode 100644 breathe/directive/__init__.py - delete mode 100644 breathe/directives.py - rename breathe/{directive/base.py => directives/__init__.py} (85%) - create mode 100644 breathe/directives/class_like.py - create mode 100644 breathe/directives/content_block.py - rename breathe/{directive => directives}/file.py (83%) - create mode 100644 breathe/directives/function.py - rename breathe/{directive => directives}/index.py (89%) - create mode 100644 breathe/directives/item.py - create mode 100644 breathe/directives/setup.py - -diff --git a/Makefile b/Makefile -index e34a282..129a441 100644 ---- a/Makefile -+++ b/Makefile -@@ -37,7 +37,7 @@ dev-test: - .PHONY: flake8 - flake8: - flake8 breathe/*.py \ -- breathe/directive/*.py \ -+ breathe/directives/*.py \ - breathe/finder/*.py \ - breathe/renderer/sphinxrenderer.py \ - breathe/renderer/filter.py \ -diff --git a/breathe/__init__.py b/breathe/__init__.py -index 311e958..1821c89 100644 ---- a/breathe/__init__.py -+++ b/breathe/__init__.py -@@ -1,6 +1,6 @@ --from . import directives --from . import file_state_cache --from .renderer import sphinxrenderer -+from breathe.directives.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 --git a/breathe/directive/__init__.py b/breathe/directive/__init__.py -deleted file mode 100644 -index e69de29..0000000 -diff --git a/breathe/directives.py b/breathe/directives.py -deleted file mode 100644 -index a57e075..0000000 ---- a/breathe/directives.py -+++ /dev/null -@@ -1,619 +0,0 @@ --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.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.renderer.filter import Filter --from breathe.renderer.mask import ( -- MaskFactory, NullMaskFactory, NoParameterNamesMask --) --from breathe.renderer.sphinxrenderer import SphinxRenderer --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 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 --# abstracted in a far nicer way to avoid repeating so much code --# --# Now we've removed the definition_list wrap so we really need to refactor this! --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 = 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.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 = create_warning(project_info, self.state, self.lineno, 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') -- -- --# 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 --git a/breathe/directive/base.py b/breathe/directives/__init__.py -similarity index 85% -rename from breathe/directive/base.py -rename to breathe/directives/__init__.py -index f808306..db27fe2 100644 ---- a/breathe/directive/base.py -+++ b/breathe/directives/__init__.py -@@ -16,7 +16,7 @@ from docutils.nodes import Node - from typing import Any, Dict, List, Optional, Sequence - - --class WarningHandler: -+class _WarningHandler: - def __init__(self, state, context: Dict[str, Any]) -> None: - self.state = state - self.context = context -@@ -35,23 +35,6 @@ class WarningHandler: - 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, -@@ -69,6 +52,22 @@ class BaseDirective(SphinxDirective): - 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]: -diff --git a/breathe/directives/class_like.py b/breathe/directives/class_like.py -new file mode 100644 -index 0000000..325251d ---- /dev/null -+++ b/breathe/directives/class_like.py -@@ -0,0 +1,74 @@ -+from breathe.directives 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 --git a/breathe/directives/content_block.py b/breathe/directives/content_block.py -new file mode 100644 -index 0000000..f50d686 ---- /dev/null -+++ b/breathe/directives/content_block.py -@@ -0,0 +1,117 @@ -+from breathe.directives 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 --git a/breathe/directive/file.py b/breathe/directives/file.py -similarity index 83% -rename from breathe/directive/file.py -rename to breathe/directives/file.py -index 923c728..ccdf4aa 100644 ---- a/breathe/directive/file.py -+++ b/breathe/directives/file.py -@@ -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 create_target_handler - 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(BaseFileDirective): - "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(BaseFileDirective): - "outline": flag, - "no-link": flag, - "sections": unchanged_required, -- } -+ } - has_content = False - - def run(self): -@@ -107,11 +100,10 @@ class AutoDoxygenFileDirective(BaseFileDirective): - """ - - 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 --git a/breathe/directives/function.py b/breathe/directives/function.py -new file mode 100644 -index 0000000..ea89f53 ---- /dev/null -+++ b/breathe/directives/function.py -@@ -0,0 +1,249 @@ -+from breathe.directives 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 --git a/breathe/directive/index.py b/breathe/directives/index.py -similarity index 89% -rename from breathe/directive/index.py -rename to breathe/directives/index.py -index 80e7384..d460f74 100644 ---- a/breathe/directive/index.py -+++ b/breathe/directives/index.py -@@ -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 create_target_handler - 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(BaseIndexDirective): - "project": unchanged_required, - "outline": flag, - "no-link": flag, -- } -+ } - has_content = False - - def run(self): -@@ -82,21 +79,20 @@ class DoxygenIndexDirective(BaseIndexDirective): - 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(BaseIndexDirective): - 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 --git a/breathe/directives/item.py b/breathe/directives/item.py -new file mode 100644 -index 0000000..26f901c ---- /dev/null -+++ b/breathe/directives/item.py -@@ -0,0 +1,102 @@ -+from breathe.directives import BaseDirective -+from breathe.file_state_cache import MTimeError -+from breathe.project import ProjectError -+from breathe.renderer.filter import Filter -+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, flag -+ -+from typing import Any, List, Optional, Type # noqa -+ -+ -+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 --git a/breathe/directives/setup.py b/breathe/directives/setup.py -new file mode 100644 -index 0000000..fff6136 ---- /dev/null -+++ b/breathe/directives/setup.py -@@ -0,0 +1,133 @@ -+from breathe.directives import BaseDirective -+from breathe.directives.class_like import ( -+ DoxygenStructDirective, DoxygenClassDirective, DoxygenInterfaceDirective, -+) -+from breathe.directives.content_block import ( -+ DoxygenNamespaceDirective, DoxygenGroupDirective, DoxygenPageDirective, -+) -+from breathe.directives.file import DoxygenFileDirective, AutoDoxygenFileDirective -+from breathe.directives.function import DoxygenFunctionDirective -+from breathe.directives.index import DoxygenIndexDirective, AutoDoxygenIndexDirective -+from breathe.directives.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 --git a/breathe/renderer/sphinxrenderer.py b/breathe/renderer/sphinxrenderer.py -index 8c16d45..07ae46c 100644 ---- a/breathe/renderer/sphinxrenderer.py -+++ b/breathe/renderer/sphinxrenderer.py -@@ -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 --git a/tests/test_renderer.py b/tests/test_renderer.py -index c2c8423..cf6ab7f 100644 ---- a/tests/test_renderer.py -+++ b/tests/test_renderer.py -@@ -422,7 +422,7 @@ def test_render_innergroup(app): - options=['inner'])) - - def get_directive(app): -- from breathe.directives import DoxygenFunctionDirective -+ from breathe.directives.function import DoxygenFunctionDirective - from breathe.project import ProjectInfoFactory - from breathe.parser import DoxygenParserFactory - from breathe.finder.factory import FinderFactory --- -2.31.1 - diff --git a/python-breathe.spec b/python-breathe.spec index 97ffc8d..3ac9f20 100644 --- a/python-breathe.spec +++ b/python-breathe.spec @@ -5,7 +5,7 @@ Breathe is an extension to reStructuredText and Sphinx to be able to read and \ render the Doxygen xml output. Name: python-%{srcname} -Version: 4.30.0 +Version: 4.31.0 Release: %autorelease Summary: Adds support for Doxygen xml output to reStructuredText and Sphinx @@ -14,7 +14,6 @@ URL: https://github.com/%{owner}/%{srcname} Source0: %{URL}/archive/v%{version}.tar.gz Source1: %{URL}/releases/download/v%{version}/%{srcname}-%{version}.tar.gz.sig Source2: https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x8aed58021feacdd5f27ba0e6a72f627716ea9d96#./vermware.key -Patch0: 0001-Handle-parsing-errors.patch BuildArch: noarch diff --git a/sources b/sources index 12f3874..5b19e5a 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (breathe-4.30.0.tar.gz.sig) = a8f74512c245203e8e1eee1d7a6841b9bcf706980f2a812855c093840bab7d09983287b67c10eee9c729da76450c632cdc2c81505cda763a5677e20426ae0d33 -SHA512 (v4.30.0.tar.gz) = fa09ee687a8e67fa480865a0d33affd9aa0ab48b0cb5b685731ec393045d8c9e1ae0622a1315f805449d712e99e6982ed229bb79314c69f69da0c705d075f7f9 +SHA512 (breathe-4.31.0.tar.gz.sig) = 67881e980cf64bb7ca3d1946315e99eab97acabc471896e797c0a8d9cb26d896af72a72404f36e3f096d004077918dce0773958e7e302da59fa61f7271427188 +SHA512 (v4.31.0.tar.gz) = f6fd7db50ca68cad64d150fcb76227deb282c08109c38b343749f8ce9ee607ccddf767e3020328a61a7570e7a0c1159607148b773bc0b695e87d6b17a6068eac