You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1775 lines
68 KiB
1775 lines
68 KiB
3 years ago
|
From bbf329963dea00a82fef4a7aa511fbfe216336c4 Mon Sep 17 00:00:00 2001
|
||
|
From: Jakob Lykke Andersen <Jakob@caput.dk>
|
||
|
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
|
||
|
|