diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e5c3d0ada..6b8962e6a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,10 +19,12 @@ jobs: - name: install dependencies run: | pip install -r requirements/development.txt - pip install flake8 - name: lint the source code run: make flake8 + - name: check source code formatting + run: make black + - name: type check the source code run: make type-check diff --git a/Makefile b/Makefile index 129a441e6..c0b9706b4 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,10 @@ flake8: breathe/renderer/filter.py \ breathe/parser/compound.py +.PHONY: black +black: + black --check . + .PHONY: type-check type-check: mypy breathe tests diff --git a/breathe-apidoc.py b/breathe-apidoc.py index 79b659a33..a34378120 100755 --- a/breathe-apidoc.py +++ b/breathe-apidoc.py @@ -3,6 +3,7 @@ import sys -if __name__ == '__main__': +if __name__ == "__main__": from breathe.apidoc import main + sys.exit(main()) diff --git a/breathe/__init__.py b/breathe/__init__.py index 41c569e2e..060f3e5da 100644 --- a/breathe/__init__.py +++ b/breathe/__init__.py @@ -4,7 +4,7 @@ from sphinx.application import Sphinx -__version__ = '4.31.0' +__version__ = "4.31.0" def setup(app: Sphinx): @@ -12,8 +12,4 @@ def setup(app: Sphinx): file_state_cache_setup(app) renderer_setup(app) - return { - 'version': __version__, - 'parallel_read_safe': True, - 'parallel_write_safe': True - } + return {"version": __version__, "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/breathe/apidoc.py b/breathe/apidoc.py index ac73ecc5e..e7ff33eb9 100644 --- a/breathe/apidoc.py +++ b/breathe/apidoc.py @@ -35,22 +35,18 @@ # Reference: Doxygen XSD schema file, CompoundKind only # Only what breathe supports are included # Translates identifier to English -TYPEDICT = {'class': 'Class', - 'interface': 'Interface', - 'struct': 'Struct', - 'union': 'Union', - 'file': 'File', - 'namespace': 'Namespace', - 'group': 'Group'} +TYPEDICT = { + "class": "Class", + "interface": "Interface", + "struct": "Struct", + "union": "Union", + "file": "File", + "namespace": "Namespace", + "group": "Group", +} # Types that accept the :members: option. -MEMBERS_TYPES = [ - 'class', - 'group', - 'interface', - 'namespace', - 'struct' -] +MEMBERS_TYPES = ["class", "group", "interface", "namespace", "struct"] def print_info(msg, args): @@ -60,14 +56,14 @@ def print_info(msg, args): def write_file(name, text, args): """Write the output file for module/package .""" - fname = os.path.join(args.destdir, '%s.%s' % (name, args.suffix)) + fname = os.path.join(args.destdir, "%s.%s" % (name, args.suffix)) if args.dryrun: - print_info('Would create file %s.' % fname, args) + print_info("Would create file %s." % fname, args) return if not args.force and os.path.isfile(fname): - print_info('File %s already exists, skipping.' % fname, args) + print_info("File %s already exists, skipping." % fname, args) else: - print_info('Creating file %s.' % fname, args) + print_info("Creating file %s." % fname, args) if not os.path.exists(os.path.dirname(fname)): try: os.makedirs(os.path.dirname(fname)) @@ -75,32 +71,34 @@ def write_file(name, text, args): if exc.errno != errno.EEXIST: raise try: - with open(fname, 'r') as target: + with open(fname, "r") as target: orig = target.read() if orig == text: - print_info('File %s up to date, skipping.' % fname, args) + print_info("File %s up to date, skipping." % fname, args) return except FileNotFoundError: # Don't mind if it isn't there pass - with open(fname, 'w') as target: + with open(fname, "w") as target: target.write(text) def format_heading(level, text): """Create a heading of [1, 2 or 3 supported].""" - underlining = ['=', '-', '~', ][level - 1] * len(text) - return '%s\n%s\n\n' % (text, underlining) + underlining = ["=", "-", "~",][ + level - 1 + ] * len(text) + return "%s\n%s\n\n" % (text, underlining) def format_directive(package_type, package, args): """Create the breathe directive and add the options.""" - directive = '.. doxygen%s:: %s\n' % (package_type, package) + directive = ".. doxygen%s:: %s\n" % (package_type, package) if args.project: - directive += ' :project: %s\n' % args.project + directive += " :project: %s\n" % args.project if args.members and package_type in MEMBERS_TYPES: - directive += ' :members:\n' + directive += " :members:\n" return directive @@ -109,7 +107,7 @@ def create_package_file(package, package_type, package_id, args): # Skip over types that weren't requested if package_type not in args.outtypes: return - text = format_heading(1, '%s %s' % (TYPEDICT[package_type], package)) + text = format_heading(1, "%s %s" % (TYPEDICT[package_type], package)) text += format_directive(package_type, package, args) write_file(os.path.join(package_type, package_id), text, args) @@ -119,12 +117,12 @@ def create_modules_toc_file(key, value, args): """Create the module's index.""" if not os.path.isdir(os.path.join(args.destdir, key)): return - text = format_heading(1, '%s list' % value) - text += '.. toctree::\n' - text += ' :glob:\n\n' - text += ' %s/*\n' % key + text = format_heading(1, "%s list" % value) + text += ".. toctree::\n" + text += " :glob:\n\n" + text += " %s/*\n" % key - write_file('%slist' % key, text, args) + write_file("%slist" % key, text, args) def recurse_tree(args): @@ -132,22 +130,23 @@ def recurse_tree(args): Look for every file in the directory tree and create the corresponding ReST files. """ - index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, 'index.xml')) + index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, "index.xml")) # Assuming this is a valid Doxygen XML for compound in index.getroot(): - create_package_file(compound.findtext('name'), compound.get('kind'), - compound.get('refid'), args) + create_package_file( + compound.findtext("name"), compound.get("kind"), compound.get("refid"), args + ) class TypeAction(argparse.Action): def __init__(self, option_strings, dest, **kwargs): super(TypeAction, self).__init__(option_strings, dest, **kwargs) self.default = TYPEDICT.keys() - self.metavar = ','.join(TYPEDICT.keys()) + self.metavar = ",".join(TYPEDICT.keys()) def __call__(self, parser, namespace, values, option_string=None): - value_list = values.split(',') + value_list = values.split(",") for value in value_list: if value not in TYPEDICT: raise ValueError("%s not a valid option" % value) @@ -162,39 +161,79 @@ def main(): breathe generation directives per definition in the . Note: By default this script will not overwrite already created files.""", - formatter_class=argparse.RawDescriptionHelpFormatter) - - parser.add_argument('-o', '--output-dir', action='store', dest='destdir', - help='Directory to place all output', required=True) - parser.add_argument('-f', '--force', action='store_true', dest='force', - help='Overwrite existing files') - parser.add_argument('-m', '--members', action='store_true', dest='members', - help='Include members for types: %s' % MEMBERS_TYPES) - parser.add_argument('-n', '--dry-run', action='store_true', dest='dryrun', - help='Run the script without creating files') - parser.add_argument('-T', '--no-toc', action='store_true', dest='notoc', - help='Don\'t create a table of contents file') - parser.add_argument('-s', '--suffix', action='store', dest='suffix', - help='file suffix (default: rst)', default='rst') - parser.add_argument('-p', '--project', action='store', dest='project', - help='project to add to generated directives') - parser.add_argument('-g', '--generate', action=TypeAction, dest='outtypes', - help='types of output to generate, comma-separated list') - parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', - help='suppress informational messages') - parser.add_argument('--version', action='version', - version='Breathe (breathe-apidoc) %s' % __version__) - parser.add_argument('rootpath', type=str, - help='The directory contains index.xml') + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "-o", + "--output-dir", + action="store", + dest="destdir", + help="Directory to place all output", + required=True, + ) + parser.add_argument( + "-f", "--force", action="store_true", dest="force", help="Overwrite existing files" + ) + parser.add_argument( + "-m", + "--members", + action="store_true", + dest="members", + help="Include members for types: %s" % MEMBERS_TYPES, + ) + parser.add_argument( + "-n", + "--dry-run", + action="store_true", + dest="dryrun", + help="Run the script without creating files", + ) + parser.add_argument( + "-T", + "--no-toc", + action="store_true", + dest="notoc", + help="Don't create a table of contents file", + ) + parser.add_argument( + "-s", + "--suffix", + action="store", + dest="suffix", + help="file suffix (default: rst)", + default="rst", + ) + parser.add_argument( + "-p", + "--project", + action="store", + dest="project", + help="project to add to generated directives", + ) + parser.add_argument( + "-g", + "--generate", + action=TypeAction, + dest="outtypes", + help="types of output to generate, comma-separated list", + ) + parser.add_argument( + "-q", "--quiet", action="store_true", dest="quiet", help="suppress informational messages" + ) + parser.add_argument( + "--version", action="version", version="Breathe (breathe-apidoc) %s" % __version__ + ) + parser.add_argument("rootpath", type=str, help="The directory contains index.xml") args = parser.parse_args() - if args.suffix.startswith('.'): + if args.suffix.startswith("."): args.suffix = args.suffix[1:] if not os.path.isdir(args.rootpath): - print('%s is not a directory.' % args.rootpath, file=sys.stderr) + print("%s is not a directory." % args.rootpath, file=sys.stderr) sys.exit(1) - if 'index.xml' not in os.listdir(args.rootpath): - print('%s does not contain a index.xml' % args.rootpath, file=sys.stderr) + if "index.xml" not in os.listdir(args.rootpath): + print("%s does not contain a index.xml" % args.rootpath, file=sys.stderr) sys.exit(1) if not os.path.isdir(args.destdir): if not args.dryrun: diff --git a/breathe/directives/__init__.py b/breathe/directives/__init__.py index b95fd4834..70d19157a 100644 --- a/breathe/directives/__init__.py +++ b/breathe/directives/__init__.py @@ -21,14 +21,19 @@ def __init__(self, state, context: Dict[str, Any]) -> None: self.state = state self.context = context - def warn(self, raw_text: str, *, rendered_nodes: Sequence[nodes.Node] = None, - unformatted_suffix: str = '') -> List[nodes.Node]: + def warn( + self, + raw_text: str, + *, + rendered_nodes: Sequence[nodes.Node] = None, + unformatted_suffix: str = "" + ) -> List[nodes.Node]: raw_text = self.format(raw_text) + unformatted_suffix if rendered_nodes is None: rendered_nodes = [nodes.paragraph("", "", nodes.Text(raw_text))] return [ nodes.warning("", *rendered_nodes), - self.state.document.reporter.warning(raw_text, line=self.context['lineno']) + self.state.document.reporter.warning(raw_text, line=self.context["lineno"]), ] def format(self, text: str) -> str: @@ -42,10 +47,13 @@ class BaseDirective(SphinxDirective): has_content: bool final_argument_whitespace: bool - def __init__(self, finder_factory: FinderFactory, - project_info_factory: ProjectInfoFactory, - parser_factory: DoxygenParserFactory, - *args) -> None: + def __init__( + self, + finder_factory: FinderFactory, + project_info_factory: ProjectInfoFactory, + parser_factory: DoxygenParserFactory, + *args + ) -> None: super().__init__(*args) self.directive_args = list(args) # Convert tuple to list to allow modification. @@ -61,22 +69,23 @@ def kind(self) -> str: 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() + project=project_info.name(), path=project_info.project_path() ) else: - tail = '' + tail = "" - context = dict( - lineno=self.lineno, - tail=tail, - **kwargs - ) + 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]: + def render( + self, + node_stack, + project_info: ProjectInfo, + filter_: Filter, + target_handler: TargetHandler, + mask_factory: MaskFactoryBase, + directive_args, + ) -> List[Node]: "Standard render process used by subclasses" try: @@ -88,14 +97,16 @@ def render(self, node_stack, project_info: ProjectInfo, filter_: Filter, self.state.document, target_handler, self.parser_factory.create_compound_parser(project_info), - filter_ + filter_, ) except ParserError as e: - return format_parser_error("doxygenclass", e.error, e.filename, self.state, - self.lineno, True) + return format_parser_error( + "doxygenclass", e.error, e.filename, self.state, self.lineno, True + ) except FileIOError as e: - return format_parser_error("doxygenclass", e.error, e.filename, self.state, - self.lineno, True) + return format_parser_error( + "doxygenclass", e.error, e.filename, self.state, self.lineno, True + ) context = RenderContext(node_stack, mask_factory, directive_args) return object_renderer.render(node_stack[0], context) diff --git a/breathe/directives/class_like.py b/breathe/directives/class_like.py index b94f83af2..ca452b2de 100644 --- a/breathe/directives/class_like.py +++ b/breathe/directives/class_like.py @@ -36,13 +36,13 @@ def run(self) -> List[Node]: 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) + 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) + return warning.warn("doxygen{kind}: %s" % e) finder_filter = self.filter_factory.create_compound_finder_filter(name, self.kind) @@ -58,8 +58,9 @@ def run(self) -> List[Node]: 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) + return self.render( + matches[0], project_info, filter_, target_handler, mask_factory, self.directive_args + ) class DoxygenClassDirective(_DoxygenClassLikeDirective): diff --git a/breathe/directives/content_block.py b/breathe/directives/content_block.py index f0fd20abf..c40a4359d 100644 --- a/breathe/directives/content_block.py +++ b/breathe/directives/content_block.py @@ -26,7 +26,7 @@ class _DoxygenContentBlockDirective(BaseDirective): "protected-members": flag, "private-members": flag, "undoc-members": flag, - "no-link": flag + "no-link": flag, } has_content = False @@ -37,13 +37,13 @@ def run(self) -> List[Node]: 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) + 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) + return warning.warn("doxygen{kind}: %s" % e) finder_filter = self.filter_factory.create_finder_filter(self.kind, name) @@ -57,15 +57,16 @@ def run(self) -> List[Node]: 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": + 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) + 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) @@ -86,7 +87,7 @@ def run(self) -> List[Node]: self.state.document, target_handler, self.parser_factory.create_compound_parser(project_info), - filter_ + filter_, ) mask_factory = NullMaskFactory() diff --git a/breathe/directives/file.py b/breathe/directives/file.py index 8342993c4..4d77d54b6 100644 --- a/breathe/directives/file.py +++ b/breathe/directives/file.py @@ -45,7 +45,7 @@ def handle_contents(self, file_, project_info): self.state.document, target_handler, self.parser_factory.create_compound_parser(project_info), - filter_ + filter_, ) mask_factory = NullMaskFactory() @@ -56,7 +56,7 @@ def handle_contents(self, file_, project_info): class DoxygenFileDirective(_BaseFileDirective): - directive_name = 'doxygenfile' + directive_name = "doxygenfile" required_arguments = 0 optional_arguments = 3 @@ -77,13 +77,13 @@ def run(self): project_info = self.project_info_factory.create_project_info(self.options) except ProjectError as e: warning = self.create_warning(None) - return warning.warn('doxygenfile: %s' % e) + return warning.warn("doxygenfile: %s" % e) return self.handle_contents(file_, project_info) class AutoDoxygenFileDirective(_BaseFileDirective): - directive_name = 'autodoxygenfile' + directive_name = "autodoxygenfile" required_arguments = 1 option_spec = { @@ -104,6 +104,6 @@ def run(self): project_info = self.project_info_factory.retrieve_project_info_for_auto(self.options) except ProjectError as e: warning = self.create_warning(None) - return warning.warn('autodoxygenfile: %s' % e) + 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 index 2634e0477..54fc56093 100644 --- a/breathe/directives/function.py +++ b/breathe/directives/function.py @@ -5,9 +5,7 @@ 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.mask import MaskFactory, NullMaskFactory, NoParameterNamesMask from breathe.renderer.sphinxrenderer import SphinxRenderer from breathe.renderer.target import create_target_handler @@ -61,13 +59,13 @@ def run(self) -> List[Node]: 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) + 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) + return warning.warn("doxygenfunction: %s" % e) # Extract arguments from the function name. try: @@ -75,16 +73,19 @@ def run(self) -> List[Node]: except cpp.DefinitionError as e: return self.create_warning( project_info, - namespace='%s::' % namespace if namespace else '', + 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}') + 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) + namespace, function_name + ) # TODO: find a more specific type for the Doxygen nodes matchesAll = [] # type: List[Any] @@ -94,7 +95,7 @@ def run(self) -> List[Node]: # only take functions and friend functions # ignore friend classes node = m[0] - if node.kind == 'friend' and not node.argsstring: + if node.kind == "friend" and not node.argsstring: continue matches.append(m) @@ -102,55 +103,61 @@ def run(self) -> List[Node]: # clauses below warning = self.create_warning( project_info, - namespace='%s::' % namespace if namespace else '', + namespace="%s::" % namespace if namespace else "", function=function_name, - args=str(args) + 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}') + 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' + message = ( + "doxygenfunction: Unable to resolve function " + '"{namespace}{function}" with arguments {args} {tail}.\n' + "Potential matches:\n" + ) - text = '' + text = "" for i, entry in enumerate(sorted(error.signatures)): - text += '- %s\n' % entry - block = nodes.literal_block('', '', nodes.Text(text)) + 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) + 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) + warning.context["cpperror"] = str(error) return warning.warn( - 'doxygenfunction: Unable to resolve function ' + "doxygenfunction: Unable to resolve function " '"{namespace}{function}" with arguments "{args}".\n' - 'Candidate function could not be parsed. Parsing error is\n{cpperror}') + "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) + 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 == '': + 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') + parser = cpp.DefinitionParser( + function_description, location=self.get_source_info(), config=self.config + ) + paramQual = parser._parse_parameters_and_qualifiers(paramMode="function") # strip everything that doesn't contribute to overloading def stripParamQual(paramQual): @@ -168,23 +175,26 @@ def stripParamQual(paramQual): declarator = p.arg.type.decl def stripDeclarator(declarator): - if hasattr(declarator, 'next'): + if hasattr(declarator, "next"): stripDeclarator(declarator.next) if isinstance(declarator, cpp.ASTDeclaratorParen): - assert hasattr(declarator, 'inner') + assert hasattr(declarator, "inner") stripDeclarator(declarator.inner) else: assert isinstance(declarator, cpp.ASTDeclaratorNameParamQual) - assert hasattr(declarator, 'declId') + assert hasattr(declarator, "declId") declarator.declId = None # type: ignore if declarator.paramQual is not None: stripParamQual(declarator.paramQual) + stripDeclarator(declarator) + stripParamQual(paramQual) return paramQual - def _create_function_signature(self, node_stack, project_info, filter_, target_handler, - mask_factory, directive_args) -> str: + def _create_function_signature( + self, node_stack, project_info, filter_, target_handler, mask_factory, directive_args + ) -> str: "Standard render process used by subclasses" try: @@ -199,11 +209,13 @@ def _create_function_signature(self, node_stack, project_info, filter_, target_h filter_, ) except ParserError as e: - return format_parser_error("doxygenclass", e.error, e.filename, self.state, - self.lineno, True) + 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) + 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] @@ -211,16 +223,18 @@ def _create_function_signature(self, node_stack, project_info, filter_, target_h # 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') + 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): @@ -230,21 +244,22 @@ def _resolve_function(self, matches, args: Optional[cpp.ASTParametersQualifiers] res = [] candSignatures = [] for entry in matches: - text_options = {'no-link': u'', 'outline': u''} + 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) + 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}) + 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) + signature = self._create_function_signature( + entry, project_info, filter_, target_handler, mask_factory, directive_args + ) candSignatures.append(signature) if args is not None: diff --git a/breathe/directives/index.py b/breathe/directives/index.py index 517c0f013..9b1dda112 100644 --- a/breathe/directives/index.py +++ b/breathe/directives/index.py @@ -15,8 +15,7 @@ class RootDataObject: class _BaseIndexDirective(BaseDirective): - """Base class handle the main work when given the appropriate project info to work from. - """ + """Base class handle the main work when given the appropriate project info to work from.""" # We use inheritance here rather than a separate object and composition, because so much # information is present in the Directive class from the docutils framework that we'd have to @@ -26,8 +25,9 @@ def handle_contents(self, project_info): try: finder = self.finder_factory.create_finder(project_info) except ParserError as e: - return format_parser_error(self.name, e.error, e.filename, self.state, - self.lineno, True) + return format_parser_error( + self.name, e.error, e.filename, self.state, self.lineno, True + ) except FileIOError as e: return format_parser_error(self.name, e.error, e.filename, self.state, self.lineno) @@ -44,18 +44,18 @@ def handle_contents(self, project_info): self.state.document, target_handler, self.parser_factory.create_compound_parser(project_info), - filter_ + filter_, ) mask_factory = NullMaskFactory() - context = RenderContext([data_object, RootDataObject()], mask_factory, - self.directive_args) + context = RenderContext([data_object, RootDataObject()], mask_factory, self.directive_args) try: node_list = object_renderer.render(context.node_stack[0], context) except ParserError as e: - return format_parser_error(self.name, e.error, e.filename, self.state, - self.lineno, True) + return format_parser_error( + self.name, e.error, e.filename, self.state, self.lineno, True + ) except FileIOError as e: return format_parser_error(self.name, e.error, e.filename, self.state, self.lineno) @@ -80,7 +80,7 @@ def run(self): project_info = self.project_info_factory.create_project_info(self.options) except ProjectError as e: warning = self.create_warning(None) - return warning.warn('doxygenindex: %s' % e) + return warning.warn("doxygenindex: %s" % e) return self.handle_contents(project_info) @@ -104,6 +104,6 @@ def run(self): project_info = self.project_info_factory.retrieve_project_info_for_auto(self.options) except ProjectError as e: warning = self.create_warning(None) - return warning.warn('autodoxygenindex: %s' % e) + return warning.warn("autodoxygenindex: %s" % e) return self.handle_contents(project_info) diff --git a/breathe/directives/item.py b/breathe/directives/item.py index 786da8bdc..234f16851 100644 --- a/breathe/directives/item.py +++ b/breathe/directives/item.py @@ -20,14 +20,13 @@ class _DoxygenBaseItemDirective(BaseDirective): "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) + return self.filter_factory.create_member_finder_filter(namespace, name, self.kind) def run(self) -> List[Node]: try: @@ -39,13 +38,13 @@ def run(self) -> List[Node]: 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) + 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) + return warning.warn("doxygen{kind}: %s" % e) finder_filter = self.create_finder_filter(namespace, name) @@ -55,8 +54,7 @@ def run(self) -> List[Node]: 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) + 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) @@ -64,8 +62,9 @@ def run(self) -> List[Node]: node_stack = matches[0] mask_factory = NullMaskFactory() - return self.render(node_stack, project_info, filter_, target_handler, mask_factory, - self.directive_args) + return self.render( + node_stack, project_info, filter_, target_handler, mask_factory, self.directive_args + ) class DoxygenVariableDirective(_DoxygenBaseItemDirective): @@ -100,4 +99,4 @@ def create_finder_filter(self, namespace: str, name: str) -> Filter: # type dependent # xml_name = "%s::%s" % (namespace, name) if namespace else name - return self.filter_factory.create_compound_finder_filter(xml_name, 'union') + return self.filter_factory.create_compound_finder_filter(xml_name, "union") diff --git a/breathe/directives/setup.py b/breathe/directives/setup.py index 480cf6b23..fc860a26b 100644 --- a/breathe/directives/setup.py +++ b/breathe/directives/setup.py @@ -1,16 +1,24 @@ from breathe.directives import BaseDirective from breathe.directives.class_like import ( - DoxygenStructDirective, DoxygenClassDirective, DoxygenInterfaceDirective, + DoxygenStructDirective, + DoxygenClassDirective, + DoxygenInterfaceDirective, ) from breathe.directives.content_block import ( - DoxygenNamespaceDirective, DoxygenGroupDirective, DoxygenPageDirective, + 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, + DoxygenVariableDirective, + DoxygenDefineDirective, + DoxygenUnionDirective, + DoxygenEnumDirective, + DoxygenEnumValueDirective, + DoxygenTypedefDirective, ) from breathe.finder.factory import FinderFactory from breathe.parser import DoxygenParserFactory @@ -26,9 +34,14 @@ class DirectiveContainer: - def __init__(self, app: Sphinx, directive: Type[BaseDirective], - finder_factory: FinderFactory, project_info_factory: ProjectInfoFactory, - parser_factory: DoxygenParserFactory): + 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 @@ -43,11 +56,7 @@ def __init__(self, app: Sphinx, directive: Type[BaseDirective], 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 = [self.finder_factory, self.project_info_factory, self.parser_factory] call_args.extend(args) return self.directive(*call_args) @@ -82,34 +91,34 @@ def setup(app: Sphinx) -> None: 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_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_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_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_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_doxygen_aliases", {}, 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') + 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 + 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): @@ -122,14 +131,14 @@ def write_file(directory, filename, content): f.write(content) doxygen_handle = AutoDoxygenProcessHandle( - subprocess.check_call, - write_file, - project_info_factory) + 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.config.breathe_doxygen_aliases + app.config.breathe_doxygen_aliases, ) + app.connect("builder-inited", doxygen_hook) diff --git a/breathe/exception.py b/breathe/exception.py index ebb7b0d10..44c5929d8 100644 --- a/breathe/exception.py +++ b/breathe/exception.py @@ -1,3 +1,2 @@ - class BreatheError(Exception): pass diff --git a/breathe/file_state_cache.py b/breathe/file_state_cache.py index e80cbdde2..6d2d71939 100644 --- a/breathe/file_state_cache.py +++ b/breathe/file_state_cache.py @@ -24,7 +24,7 @@ def _getmtime(filename: str): try: return os.path.getmtime(filename) except OSError: - raise MTimeError('Cannot find file: %s' % os.path.realpath(filename)) + raise MTimeError("Cannot find file: %s" % os.path.realpath(filename)) def update(app: Sphinx, source_file: str) -> None: @@ -33,7 +33,8 @@ def update(app: Sphinx, source_file: str) -> None: new_mtime = _getmtime(source_file) mtime, docnames = app.env.breathe_file_state.setdefault( # type: ignore - source_file, (new_mtime, set())) + source_file, (new_mtime, set()) + ) assert app.env is not None docnames.add(app.env.docname) @@ -41,8 +42,9 @@ def update(app: Sphinx, source_file: str) -> None: app.env.breathe_file_state[source_file] = (new_mtime, docnames) # type: ignore -def _get_outdated(app: Sphinx, env: BuildEnvironment, - added: Set[str], changed: Set[str], removed: Set[str]) -> List[str]: +def _get_outdated( + app: Sphinx, env: BuildEnvironment, added: Set[str], changed: Set[str], removed: Set[str] +) -> List[str]: if not hasattr(app.env, "breathe_file_state"): return [] diff --git a/breathe/finder/__init__.py b/breathe/finder/__init__.py index 3a973168e..4217ce5f0 100644 --- a/breathe/finder/__init__.py +++ b/breathe/finder/__init__.py @@ -12,8 +12,7 @@ def stack(element, list_): class ItemFinder: - def __init__(self, project_info: ProjectInfo, data_object, - item_finder_factory): + def __init__(self, project_info: ProjectInfo, data_object, item_finder_factory): self.data_object = data_object # DoxygenItemFinderFactory, but actually typing it would introduce an import cycle self.item_finder_factory = item_finder_factory diff --git a/breathe/finder/compound.py b/breathe/finder/compound.py index 022effe23..e38d2c6b7 100644 --- a/breathe/finder/compound.py +++ b/breathe/finder/compound.py @@ -49,7 +49,7 @@ def filter_(self, ancestors, filter_: Filter, matches) -> None: if filter_.allow(node_stack): matches.append(node_stack) - if data_object.kind == 'enum': + if data_object.kind == "enum": for value in data_object.enumvalue: value_stack = stack(value, node_stack) if filter_.allow(value_stack): diff --git a/breathe/finder/factory.py b/breathe/finder/factory.py index 5f4fc8fba..ab8918edc 100644 --- a/breathe/finder/factory.py +++ b/breathe/finder/factory.py @@ -17,8 +17,7 @@ def __init__(self, app: Sphinx, parser_factory: DoxygenParserFactory): def __call__(self, project_info: ProjectInfo, *args): compound_parser = self.parser_factory.create_compound_parser(project_info) - return indexfinder.CompoundTypeSubItemFinder(self.app, compound_parser, - project_info, *args) + return indexfinder.CompoundTypeSubItemFinder(self.app, compound_parser, project_info, *args) class DoxygenItemFinderFactory: diff --git a/breathe/finder/index.py b/breathe/finder/index.py index 0893f8077..32e357dd4 100644 --- a/breathe/finder/index.py +++ b/breathe/finder/index.py @@ -55,7 +55,8 @@ def filter_(self, ancestors, filter_: Filter, matches) -> None: for member_stack in member_matches: ref_filter = self.filter_factory.create_id_filter( - 'memberdef', member_stack[0].refid) + "memberdef", member_stack[0].refid + ) finder.filter_(node_stack, ref_filter, matches) else: # Read in the xml file referenced by the compound and descend into that as well diff --git a/breathe/path_handler.py b/breathe/path_handler.py index 6f95ac4d0..88bf46876 100644 --- a/breathe/path_handler.py +++ b/breathe/path_handler.py @@ -6,7 +6,7 @@ def includes_directory(file_path: str): # Check for backslash or forward slash as we don't know what platform we're on and sometimes # the doxygen paths will have forward slash even on Windows. - return bool(file_path.count('\\')) or bool(file_path.count('/')) + return bool(file_path.count("\\")) or bool(file_path.count("/")) def resolve_path(app: Sphinx, directory: str, filename: str): diff --git a/breathe/process.py b/breathe/process.py index 30bf65c85..2387cf3df 100644 --- a/breathe/process.py +++ b/breathe/process.py @@ -57,7 +57,8 @@ def generate_xml(self, projects_source, doxygen_options, doxygen_aliases): contents = file_structure[1] auto_project_info = self.project_info_factory.create_auto_project_info( - project_name, folder) + project_name, folder + ) project_files[project_name] = ProjectData(auto_project_info, contents) @@ -66,10 +67,8 @@ def generate_xml(self, projects_source, doxygen_options, doxygen_aliases): for project_name, data in project_files.items(): project_path = self.process( - data.auto_project_info, - data.files, - doxygen_options, - doxygen_aliases) + data.auto_project_info, data.files, doxygen_options, doxygen_aliases + ) project_info = data.auto_project_info.create_project_info(project_path) @@ -83,27 +82,24 @@ def process(self, auto_project_info, files, doxygen_options, doxygen_aliases): full_paths = map(lambda x: auto_project_info.abs_path_to_source_file(x), files) options = "\n".join("%s=%s" % pair for pair in doxygen_options.items()) - aliases = '\n'.join( - f'ALIASES += {name}="{value}"' for name, value in doxygen_aliases.items()) + aliases = "\n".join( + f'ALIASES += {name}="{value}"' for name, value in doxygen_aliases.items() + ) cfg = AUTOCFG_TEMPLATE.format( project_name=name, output_dir=name, input=" ".join(full_paths), - extra=f"{options}\n{aliases}" - ) + extra=f"{options}\n{aliases}", + ) - build_dir = os.path.join( - auto_project_info.build_dir(), - "breathe", - "doxygen" - ) + build_dir = os.path.join(auto_project_info.build_dir(), "breathe", "doxygen") self.write_file(build_dir, cfgfile, cfg) # Shell-escape the cfg file name to try to avoid any issue where the name might include # malicious shell character - We have to use the shell=True option to make it work on # Windows. See issue #271 - self.run_process('doxygen %s' % quote(cfgfile), cwd=build_dir, shell=True) + self.run_process("doxygen %s" % quote(cfgfile), cwd=build_dir, shell=True) return os.path.join(build_dir, name, "xml") diff --git a/breathe/project.py b/breathe/project.py index f0cbce605..778aab0d1 100644 --- a/breathe/project.py +++ b/breathe/project.py @@ -1,4 +1,3 @@ - from .exception import BreatheError from sphinx.application import Sphinx @@ -48,11 +47,7 @@ def abs_path_to_source_file(self, file_): def create_project_info(self, project_path): """Creates a proper ProjectInfo object based on the information in this AutoProjectInfo""" - return ProjectInfo(self.app, - self._name, - project_path, - self._source_path, - self._reference) + return ProjectInfo(self.app, self._name, project_path, self._source_path, self._reference) class ProjectInfo: @@ -145,8 +140,11 @@ def default_path(self) -> str: return config.breathe_projects[config.breathe_default_project] except KeyError: raise ProjectError( - ("breathe_default_project value '%s' does not seem to be a valid key for the " - "breathe_projects dictionary") % config.breathe_default_project + ( + "breathe_default_project value '%s' does not seem to be a valid key for the " + "breathe_projects dictionary" + ) + % config.breathe_default_project ) def create_project_info(self, options) -> ProjectInfo: @@ -158,8 +156,10 @@ def create_project_info(self, options) -> ProjectInfo: path = config.breathe_projects[options["project"]] name = options["project"] except KeyError: - raise ProjectError("Unable to find project '%s' in breathe_projects dictionary" - % options["project"]) + raise ProjectError( + "Unable to find project '%s' in breathe_projects dictionary" + % options["project"] + ) elif "path" in options: path = options["path"] else: @@ -174,11 +174,7 @@ def create_project_info(self, options) -> ProjectInfo: reference = path self.project_count += 1 - project_info = ProjectInfo(self.app, - name, - path, - "NoSourcePath", - reference) + project_info = ProjectInfo(self.app, name, path, "NoSourcePath", reference) self.project_info_store[path] = project_info return project_info @@ -198,7 +194,7 @@ def retrieve_project_info_for_auto(self, options) -> AutoProjectInfo: sense. """ - name = options.get('project', self.app.config.breathe_default_project) # type: ignore + name = options.get("project", self.app.config.breathe_default_project) # type: ignore if name is None: raise NoDefaultProjectError( "No breathe_default_project config setting to fall back on " @@ -217,10 +213,8 @@ def create_auto_project_info(self, name: str, source_path) -> AutoProjectInfo: reference = source_path self.project_count += 1 - auto_project_info = AutoProjectInfo(self.app, - name, - source_path, - self.build_dir, - reference) + auto_project_info = AutoProjectInfo( + self.app, name, source_path, self.build_dir, reference + ) self.auto_project_info_store[key] = auto_project_info return auto_project_info diff --git a/breathe/renderer/__init__.py b/breathe/renderer/__init__.py index 20282a2f7..5dbdde1de 100644 --- a/breathe/renderer/__init__.py +++ b/breathe/renderer/__init__.py @@ -7,15 +7,21 @@ def format_parser_error(name, error, filename, state, lineno, do_unicode_warning): warning = '%s: Unable to parse xml file "%s". ' % (name, filename) - explanation = 'Reported error: %s. ' % error + explanation = "Reported error: %s. " % error unicode_explanation_text = "" unicode_explanation = [] if do_unicode_warning: - unicode_explanation_text = textwrap.dedent(""" + unicode_explanation_text = ( + textwrap.dedent( + """ Parsing errors are often due to unicode errors associated with the encoding of the original source files. Doxygen propagates invalid characters from the input source files to the - output xml.""").strip().replace("\n", " ") + output xml.""" + ) + .strip() + .replace("\n", " ") + ) unicode_explanation = [nodes.paragraph("", "", nodes.Text(unicode_explanation_text))] return [ @@ -26,13 +32,15 @@ def format_parser_error(name, error, filename, state, lineno, do_unicode_warning *unicode_explanation ), state.document.reporter.warning( - warning + explanation + unicode_explanation_text, line=lineno) + warning + explanation + unicode_explanation_text, line=lineno + ), ] class RenderContext: - def __init__(self, node_stack, mask_factory, directive_args, - domain: str='', child: bool=False) -> None: + def __init__( + self, node_stack, mask_factory, directive_args, domain: str = "", child: bool = False + ) -> None: self.node_stack = node_stack self.mask_factory = mask_factory self.directive_args = directive_args @@ -42,4 +50,4 @@ def __init__(self, node_stack, mask_factory, directive_args, def create_child_context(self, data_object) -> "RenderContext": node_stack = self.node_stack[:] node_stack.insert(0, self.mask_factory.mask(data_object)) - return RenderContext(node_stack, self.mask_factory, self.directive_args, self.domain, True) \ No newline at end of file + return RenderContext(node_stack, self.mask_factory, self.directive_args, self.domain, True) diff --git a/breathe/renderer/filter.py b/breathe/renderer/filter.py index 836e94acb..b8004c7e3 100644 --- a/breathe/renderer/filter.py +++ b/breathe/renderer/filter.py @@ -215,6 +215,7 @@ class UnrecognisedKindError(Exception): # Selectors ############################################################################### + class Selector: def __call__(self, node_stack): raise NotImplementedError @@ -225,35 +226,35 @@ def node_type(self): @property def kind(self): - return AttributeAccessor(self, 'kind') + return AttributeAccessor(self, "kind") @property def node_name(self): - return AttributeAccessor(self, 'node_name') + return AttributeAccessor(self, "node_name") @property def name(self): - return AttributeAccessor(self, 'name') + return AttributeAccessor(self, "name") @property def briefdescription(self): - return AttributeAccessor(self, 'briefdescription') + return AttributeAccessor(self, "briefdescription") @property def detaileddescription(self): - return AttributeAccessor(self, 'detaileddescription') + return AttributeAccessor(self, "detaileddescription") @property def prot(self): - return AttributeAccessor(self, 'prot') + return AttributeAccessor(self, "prot") @property def valueOf(self): - return AttributeAccessor(self, 'valueOf_') + return AttributeAccessor(self, "valueOf_") @property def id(self): - return AttributeAccessor(self, 'id') + return AttributeAccessor(self, "id") class Ancestor(Selector): @@ -278,6 +279,7 @@ def __call__(self, node_stack): # Accessors ############################################################################### + class Accessor: def __init__(self, selector: Selector) -> None: self.selector = selector @@ -367,6 +369,7 @@ def __call__(self, node_stack): # Filters ############################################################################### + class Filter: def allow(self, node_stack) -> bool: raise NotImplementedError @@ -450,7 +453,7 @@ def allow(self, node_stack) -> bool: # If the target_file contains directory separators then # match against the same length at the end of the location # - location_match = location[-len(self.target_file):] + location_match = location[-len(self.target_file) :] return location_match == self.target_file else: # If there are no separators, match against the whole filename @@ -550,6 +553,7 @@ def allow(self, node_stack) -> bool: # Other stuff ############################################################################### + class Glob: def __init__(self, method, pattern): self.method = method @@ -561,14 +565,16 @@ def match(self, name): class FilterFactory: # C++ style public entries - public_kinds = set([ - "public-type", - "public-func", - "public-attrib", - "public-slot", - "public-static-func", - "public-static-attrib", - ]) + public_kinds = set( + [ + "public-type", + "public-func", + "public-attrib", + "public-slot", + "public-static-func", + "public-static-attrib", + ] + ) def __init__(self, app: Sphinx) -> None: self.app = app @@ -576,43 +582,48 @@ def __init__(self, app: Sphinx) -> None: def create_render_filter(self, kind: str, options: Dict[str, Any]) -> Filter: """Render filter for group & namespace blocks""" - if kind not in ['group', 'page', 'namespace']: + if kind not in ["group", "page", "namespace"]: raise UnrecognisedKindError(kind) # Generate new dictionary from defaults - filter_options = dict((entry, '') - for entry in self.app.config.breathe_default_members) # type: ignore + filter_options = dict( + (entry, "") for entry in self.app.config.breathe_default_members + ) # type: ignore # Update from the actual options filter_options.update(options) # Convert the doxygengroup members flag (which just stores None as the value) to an empty # string to allow the create_class_member_filter to process it properly - if 'members' in filter_options: - filter_options['members'] = '' + if "members" in filter_options: + filter_options["members"] = "" node = Node() grandparent = Ancestor(2) has_grandparent = HasAncestorFilter(2) - non_class_memberdef = \ - has_grandparent \ - & (grandparent.node_type == 'compounddef') \ - & (grandparent.kind != 'class') \ - & (grandparent.kind != 'struct') \ - & (grandparent.kind != 'interface') \ - & (node.node_type == 'memberdef') + non_class_memberdef = ( + has_grandparent + & (grandparent.node_type == "compounddef") + & (grandparent.kind != "class") + & (grandparent.kind != "struct") + & (grandparent.kind != "interface") + & (node.node_type == "memberdef") + ) - return (self.create_class_member_filter(filter_options) | non_class_memberdef) \ - & self.create_innerclass_filter(filter_options) \ + return ( + (self.create_class_member_filter(filter_options) | non_class_memberdef) + & self.create_innerclass_filter(filter_options) & self.create_outline_filter(filter_options) + ) def create_class_filter(self, target: str, options: Dict[str, Any]) -> Filter: """Content filter for classes based on various directive options""" # Generate new dictionary from defaults - filter_options = dict((entry, '') - for entry in self.app.config.breathe_default_members) # type: ignore + filter_options = dict( + (entry, "") for entry in self.app.config.breathe_default_members + ) # type: ignore # Update from the actual options filter_options.update(options) @@ -622,9 +633,9 @@ def create_class_filter(self, target: str, options: Dict[str, Any]) -> Filter: self.create_innerclass_filter(filter_options, outerclass=target), self.create_outline_filter(filter_options), self.create_show_filter(filter_options), - ) + ) - def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = '') -> Filter: + def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = "") -> Filter: """ :param outerclass: Should be the class/struct being target by the directive calling this code. If it is a group or namespace directive then it should be left @@ -638,14 +649,14 @@ def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = '' node_is_innerclass = (node.node_type == "ref") & (node.node_name == "innerclass") parent = Parent() - parent_is_compounddef = parent.node_type == 'compounddef' - parent_is_class = parent.kind.is_one_of(['class', 'struct', 'interface']) + parent_is_compounddef = parent.node_type == "compounddef" + parent_is_class = parent.kind.is_one_of(["class", "struct", "interface"]) allowed = set() all_options = { - 'protected-members': 'protected', - 'private-members': 'private', - } + "protected-members": "protected", + "private-members": "private", + } for option, scope in all_options.items(): if option in options: @@ -655,13 +666,13 @@ def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = '' public_innerclass_filter = ClosedFilter() - if 'members' in options: - if options['members'].strip(): + if "members" in options: + if options["members"].strip(): text = options["members"] - prefix = ('%s::' % outerclass) if outerclass else '' + prefix = ("%s::" % outerclass) if outerclass else "" # Matches sphinx-autodoc behaviour of comma separated values - members = set(['%s%s' % (prefix, x.strip()) for x in text.split(",")]) + members = set(["%s%s" % (prefix, x.strip()) for x in text.split(",")]) node_valueOf_is_in_members = node.valueOf.is_one_of(members) # Accept any nodes which don't have a "sectiondef" as a parent or, if they do, only @@ -669,12 +680,12 @@ def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = '' public_innerclass_filter = ~node_is_innerclass_in_class | node_valueOf_is_in_members else: - allowed.add('public') + allowed.add("public") node_is_in_allowed_scope = node.prot.is_one_of(allowed) - innerclass = ~ node_is_innerclass_in_class | node_is_in_allowed_scope - description = self._create_description_filter(True, 'compounddef', options) + innerclass = ~node_is_innerclass_in_class | node_is_in_allowed_scope + description = self._create_description_filter(True, "compounddef", options) # Put parent check last as we only want to check parents of innerclass's otherwise we have # to check the parent's type as well @@ -689,8 +700,8 @@ def create_show_filter(self, options: Dict[str, Any]) -> Filter: # Allow through everything except the header-file includes nodes return OrFilter( NotFilter(InFilter(NodeTypeAccessor(Parent()), ["compounddef"])), - NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"])) - ) + NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"])), + ) if text == "header-file": # Allow through everything, including header-file includes @@ -699,27 +710,27 @@ def create_show_filter(self, options: Dict[str, Any]) -> Filter: # Allow through everything except the header-file includes nodes return OrFilter( NotFilter(InFilter(NodeTypeAccessor(Parent()), ["compounddef"])), - NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"])) - ) + NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"])), + ) - def _create_description_filter(self, allow: bool, level: str, - options: Dict[str, Any]) -> Filter: + def _create_description_filter( + self, allow: bool, level: str, options: Dict[str, Any] + ) -> Filter: """Whether or not we allow descriptions is determined by the calling function and we just do whatever the 'allow' function parameter tells us. """ node = Node() - node_is_description = node.node_type == 'description' + node_is_description = node.node_type == "description" parent = Parent() parent_is_level = parent.node_type == level # Nothing with a parent that's a sectiondef - description_filter = ~ parent_is_level + description_filter = ~parent_is_level # Let through any description children of sectiondefs if we output any kind members if allow: - description_filter = \ - (parent_is_level & node_is_description) | ~ parent_is_level + description_filter = (parent_is_level & node_is_description) | ~parent_is_level return description_filter @@ -733,12 +744,12 @@ def _create_public_members_filter(self, options: Dict[str, Any]) -> Filter: # Nothing with a parent that's a sectiondef is_memberdef = parent_is_sectiondef & node_is_memberdef - public_members_filter = ~ is_memberdef + public_members_filter = ~is_memberdef # If the user has specified the 'members' option with arguments then we only pay attention # to that and not to any other member settings if "members" in options: - if options['members'].strip(): + if options["members"].strip(): text = options["members"] # Matches sphinx-autodoc behaviour of comma separated values @@ -748,17 +759,18 @@ def _create_public_members_filter(self, options: Dict[str, Any]) -> Filter: # Accept any nodes which don't have a "sectiondef" as a parent or, if they do, only # accept them if their names are in the members list - public_members_filter = \ - (parent_is_sectiondef & node_name_is_in_members) | ~ parent_is_sectiondef + public_members_filter = ( + parent_is_sectiondef & node_name_is_in_members + ) | ~parent_is_sectiondef else: # Select anything that doesn't have a parent which is a sectiondef, or, if it does, # only select the public ones - public_members_filter = \ - (is_memberdef & node_is_public) | ~ is_memberdef + public_members_filter = (is_memberdef & node_is_public) | ~is_memberdef return public_members_filter - def _create_non_public_members_filter(self, prot: str, option_name: str, - options: Dict[str, Any]) -> Filter: + def _create_non_public_members_filter( + self, prot: str, option_name: str, options: Dict[str, Any] + ) -> Filter: """'prot' is the doxygen xml term for 'public', 'protected' and 'private' categories.""" node = Node() @@ -770,24 +782,25 @@ def _create_non_public_members_filter(self, prot: str, option_name: str, # Nothing with a parent that's a sectiondef is_memberdef = parent_is_sectiondef & node_is_memberdef - filter_ = ~ is_memberdef + filter_ = ~is_memberdef if option_name in options: # Allow anything that isn't a memberdef, or if it is only allow the public ones - filter_ = ~ is_memberdef | node_is_public + filter_ = ~is_memberdef | node_is_public return filter_ def _create_undoc_members_filter(self, options: Dict[str, Any]) -> Filter: node = Node() - node_is_memberdef = node.node_type == 'memberdef' + node_is_memberdef = node.node_type == "memberdef" - node_has_description = node.briefdescription.has_content() \ - | node.detaileddescription.has_content() + node_has_description = ( + node.briefdescription.has_content() | node.detaileddescription.has_content() + ) # Allow anything that isn't a memberdef, or if it is only allow the ones with a description - undoc_members_filter = ~ node_is_memberdef | node_has_description + undoc_members_filter = ~node_is_memberdef | node_has_description - if 'undoc-members' in options: + if "undoc-members" in options: undoc_members_filter = OpenFilter() return undoc_members_filter @@ -798,26 +811,22 @@ def create_class_member_filter(self, options: Dict[str, Any]) -> Filter: # out when it is needed. This approach reflects the old code that was here but it wasn't # commented (my fault.) I wonder if maybe the public and private declarations themselves can # be documented and we need to let them through. Not sure. - allow = 'members' in options \ - or 'protected-members' in options \ - or 'private-members' in options + allow = ( + "members" in options or "protected-members" in options or "private-members" in options + ) - description = self._create_description_filter(allow, 'sectiondef', options) + description = self._create_description_filter(allow, "sectiondef", options) # Create all necessary filters and combine them public_members = self._create_public_members_filter(options) protected_members = self._create_non_public_members_filter( - 'protected', - 'protected-members', - options - ) + "protected", "protected-members", options + ) private_members = self._create_non_public_members_filter( - 'private', - 'private-members', - options - ) + "private", "private-members", options + ) undoc_members = self._create_undoc_members_filter(options) @@ -826,9 +835,9 @@ def create_class_member_filter(self, options: Dict[str, Any]) -> Filter: return allowed_members | description def create_outline_filter(self, options: Dict[str, Any]) -> Filter: - if 'outline' in options: + if "outline" in options: node = Node() - return ~ node.node_type.is_one_of(["description", "inc"]) + return ~node.node_type.is_one_of(["description", "inc"]) else: return OpenFilter() @@ -847,13 +856,10 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter: AndFilter( InFilter(NodeTypeAccessor(Node()), ["compounddef"]), InFilter(KindAccessor(Node()), ["file"]), - FilePathFilter( - LambdaAccessor(Node(), lambda x: x.location), - filename - ), - Gather(LambdaAccessor(Node(), lambda x: x.namespaces), valid_names) - ) - ), + FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename), + Gather(LambdaAccessor(Node(), lambda x: x.namespaces), valid_names), + ) + ), NotFilter( # Take the valid_names and everytime we handle an # innerclass or innernamespace, check that its name @@ -868,12 +874,11 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter: InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]), NotFilter( InFilter( - LambdaAccessor(Node(), lambda x: x.content_[0].getValue()), - valid_names - ) + LambdaAccessor(Node(), lambda x: x.content_[0].getValue()), valid_names ) - ) - ), + ), + ) + ), NotFilter( # Ignore innerclasses and innernamespaces that are inside a # namespace that is going to be rendered as they will be @@ -884,10 +889,10 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter: InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]), NamespaceFilter( NamespaceAccessor(Parent()), - LambdaAccessor(Node(), lambda x: x.content_[0].getValue()) - ) - ) - ), + LambdaAccessor(Node(), lambda x: x.content_[0].getValue()), + ), + ) + ), NotFilter( # Ignore memberdefs from files which are different to # the one we're rendering. This happens when we have to @@ -896,11 +901,10 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter: AndFilter( InFilter(NodeTypeAccessor(Node()), ["memberdef"]), NotFilter( - FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), - filename) - ) - ) - ), + FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename) + ), + ) + ), NotFilter( # Ignore compounddefs which are from another file # (normally means classes and structs which are in a @@ -914,12 +918,11 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter: InFilter(NodeTypeAccessor(Node()), ["compounddef"]), NotFilter(InFilter(KindAccessor(Node()), ["namespace"])), NotFilter( - FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), - filename) - ) - ) + FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename) + ), ) - ) + ), + ) return AndFilter(self.create_outline_filter(options), filter_) def create_content_filter(self, kind: str, options: Dict[str, Any]) -> Filter: @@ -932,27 +935,28 @@ def create_content_filter(self, kind: str, options: Dict[str, Any]) -> Filter: As a finder/content filter we only need to match exactly what we're interested in. """ - if kind not in ['group', 'page', 'namespace']: + if kind not in ["group", "page", "namespace"]: raise UnrecognisedKindError(kind) node = Node() # Filter for public memberdefs - node_is_memberdef = node.node_type == 'memberdef' - node_is_public = node.prot == 'public' + node_is_memberdef = node.node_type == "memberdef" + node_is_public = node.prot == "public" public_members = node_is_memberdef & node_is_public # Filter for public innerclasses parent = Parent() - parent_is_compounddef = parent.node_type == 'compounddef' + parent_is_compounddef = parent.node_type == "compounddef" parent_is_class = parent.kind == kind node_is_innerclass = (node.node_type == "ref") & (node.node_name == "innerclass") - node_is_public = node.prot == 'public' + node_is_public = node.prot == "public" - public_innerclass = parent_is_compounddef & parent_is_class \ - & node_is_innerclass & node_is_public + public_innerclass = ( + parent_is_compounddef & parent_is_class & node_is_innerclass & node_is_public + ) return public_members | public_innerclass @@ -962,23 +966,20 @@ def create_index_filter(self, options: Dict[str, Any]) -> Filter: AndFilter( InFilter(NodeTypeAccessor(Parent()), ["compounddef"]), InFilter(NodeTypeAccessor(Node()), ["ref"]), - InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]) - ) - ), + InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]), + ) + ), NotFilter( AndFilter( InFilter(NodeTypeAccessor(Parent()), ["compounddef"]), InFilter(KindAccessor(Parent()), ["group"]), InFilter(NodeTypeAccessor(Node()), ["sectiondef"]), - InFilter(KindAccessor(Node()), ["func"]) - ) + InFilter(KindAccessor(Node()), ["func"]), ) - ) + ), + ) - return AndFilter( - self.create_outline_filter(options), - filter_ - ) + return AndFilter(self.create_outline_filter(options), filter_) def create_open_filter(self) -> Filter: """Returns a completely open filter which matches everything""" @@ -993,7 +994,7 @@ def create_file_finder_filter(self, filename: str) -> Filter: filter_ = AndFilter( InFilter(NodeTypeAccessor(Node()), ["compounddef"]), InFilter(KindAccessor(Node()), ["file"]), - FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename) + FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename), ) return filter_ @@ -1003,35 +1004,39 @@ def create_member_finder_filter(self, namespace: str, name: str, kind: str) -> F node = Node() parent = Parent() - node_matches = (node.node_type == 'member') \ - & (node.kind == kind) \ - & (node.name == name) + node_matches = (node.node_type == "member") & (node.kind == kind) & (node.name == name) if namespace: - parent_matches = (parent.node_type == 'compound') \ - & ((parent.kind == 'namespace') | - (parent.kind == 'class') | - (parent.kind == 'struct') | - (parent.kind == 'interface')) \ + parent_matches = ( + (parent.node_type == "compound") + & ( + (parent.kind == "namespace") + | (parent.kind == "class") + | (parent.kind == "struct") + | (parent.kind == "interface") + ) & (parent.name == namespace) + ) return parent_matches & node_matches else: is_implementation_file = parent.name.endswith( - self.app.config.breathe_implementation_filename_extensions) # type: ignore - parent_is_compound = parent.node_type == 'compound' - parent_is_file = (parent.kind == 'file') & (~ is_implementation_file) - parent_is_not_file = parent.kind != 'file' - - return (parent_is_compound & parent_is_file & node_matches) \ - | (parent_is_compound & parent_is_not_file & node_matches) + self.app.config.breathe_implementation_filename_extensions + ) # type: ignore + parent_is_compound = parent.node_type == "compound" + parent_is_file = (parent.kind == "file") & (~is_implementation_file) + parent_is_not_file = parent.kind != "file" + + return (parent_is_compound & parent_is_file & node_matches) | ( + parent_is_compound & parent_is_not_file & node_matches + ) def create_function_and_all_friend_finder_filter(self, namespace: str, name: str) -> Filter: parent = Parent() - parent_is_compound = parent.node_type == 'compound' - parent_is_group = parent.kind == 'group' + parent_is_compound = parent.node_type == "compound" + parent_is_group = parent.kind == "group" - function_filter = self.create_member_finder_filter(namespace, name, 'function') - friend_filter = self.create_member_finder_filter(namespace, name, 'friend') + function_filter = self.create_member_finder_filter(namespace, name, "function") + friend_filter = self.create_member_finder_filter(namespace, name, "friend") # Get matching functions but only ones where the parent is not a group. We want to skip # function entries in groups as we'll find the same functions in a file's xml output # elsewhere and having more than one match is confusing for our logic later on. @@ -1041,13 +1046,13 @@ def create_enumvalue_finder_filter(self, name: str) -> Filter: """Returns a filter which looks for an enumvalue with the specified name.""" node = Node() - return (node.node_type == 'enumvalue') & (node.name == name) + return (node.node_type == "enumvalue") & (node.name == name) def create_compound_finder_filter(self, name: str, kind: str) -> Filter: """Returns a filter which looks for a compound with the specified name and kind.""" node = Node() - return (node.node_type == 'compound') & (node.kind == kind) & (node.name == name) + return (node.node_type == "compound") & (node.kind == kind) & (node.name == name) def create_finder_filter(self, kind: str, name: str) -> Filter: """Returns a filter which looks for the compound node from the index which is a group node @@ -1057,23 +1062,23 @@ def create_finder_filter(self, kind: str, name: str) -> Filter: contents. """ - if kind == 'group': + if kind == "group": filter_ = AndFilter( InFilter(NodeTypeAccessor(Node()), ["compound"]), InFilter(KindAccessor(Node()), ["group"]), - InFilter(NameAccessor(Node()), [name]) - ) - elif kind == 'page': + InFilter(NameAccessor(Node()), [name]), + ) + elif kind == "page": filter_ = AndFilter( InFilter(NodeTypeAccessor(Node()), ["compound"]), InFilter(KindAccessor(Node()), ["page"]), - InFilter(NameAccessor(Node()), [name]) - ) + InFilter(NameAccessor(Node()), [name]), + ) else: # Assume kind == 'namespace' filter_ = AndFilter( InFilter(NodeTypeAccessor(Node()), ["compound"]), InFilter(KindAccessor(Node()), ["namespace"]), - InFilter(NameAccessor(Node()), [name]) - ) + InFilter(NameAccessor(Node()), [name]), + ) return filter_ diff --git a/breathe/renderer/mask.py b/breathe/renderer/mask.py index 076a192a3..3bb158f06 100644 --- a/breathe/renderer/mask.py +++ b/breathe/renderer/mask.py @@ -18,12 +18,13 @@ """ + class NoParameterNamesMask: def __init__(self, data_object) -> None: self.data_object = data_object def __getattr__(self, attr): - if attr in ['declname', 'defname', 'defval']: + if attr in ["declname", "defname", "defval"]: return None return getattr(self.data_object, attr) diff --git a/breathe/renderer/sphinxrenderer.py b/breathe/renderer/sphinxrenderer.py index 136aaf430..a750f4cdb 100644 --- a/breathe/renderer/sphinxrenderer.py +++ b/breathe/renderer/sphinxrenderer.py @@ -69,6 +69,7 @@ def transform_content(self, contentnode: addnodes.desc_content) -> None: # ---------------------------------------------------------------------------- + class CPPClassObject(BaseObject, cpp.CPPClassObject): pass @@ -99,6 +100,7 @@ class CPPEnumeratorObject(BaseObject, cpp.CPPEnumeratorObject): # ---------------------------------------------------------------------------- + class CStructObject(BaseObject, c.CStructObject): pass @@ -133,6 +135,7 @@ class CMacroObject(BaseObject, c.CMacroObject): # ---------------------------------------------------------------------------- + class PyFunction(BaseObject, python.PyFunction): pass @@ -152,8 +155,10 @@ class PyClasslike(BaseObject, python.PyClasslike): # We use capitalization (and the namespace) to differentiate between the two if php is not None: + class PHPNamespaceLevel(BaseObject, php.PhpNamespacelevel): """Description of a PHP item *in* a namespace (not the space itself).""" + pass class PHPClassLike(BaseObject, php.PhpClasslike): @@ -165,9 +170,11 @@ class PHPClassMember(BaseObject, php.PhpClassmember): class PHPGlobalLevel(BaseObject, php.PhpGloballevel): pass + # ---------------------------------------------------------------------------- if cs is not None: + class CSharpCurrentNamespace(BaseObject, cs.CSharpCurrentNamespace): pass @@ -216,78 +223,74 @@ class CSharpXRefRole(BaseObject, cs.CSharpXRefRole): # ---------------------------------------------------------------------------- + class DomainDirectiveFactory: # A mapping from node kinds to domain directives and their names. cpp_classes = { - 'variable': (CPPMemberObject, 'var'), - 'class': (CPPClassObject, 'class'), - 'struct': (CPPClassObject, 'struct'), - 'interface': (CPPClassObject, 'class'), - 'function': (CPPFunctionObject, 'function'), - 'friend': (CPPFunctionObject, 'function'), - 'signal': (CPPFunctionObject, 'function'), - 'slot': (CPPFunctionObject, 'function'), - 'enum': (CPPEnumObject, 'enum'), - 'typedef': (CPPTypeObject, 'type'), - 'using': (CPPTypeObject, 'type'), - 'union': (CPPUnionObject, 'union'), - 'namespace': (CPPTypeObject, 'type'), - 'enumvalue': (CPPEnumeratorObject, 'enumerator'), - 'define': (CMacroObject, 'macro'), + "variable": (CPPMemberObject, "var"), + "class": (CPPClassObject, "class"), + "struct": (CPPClassObject, "struct"), + "interface": (CPPClassObject, "class"), + "function": (CPPFunctionObject, "function"), + "friend": (CPPFunctionObject, "function"), + "signal": (CPPFunctionObject, "function"), + "slot": (CPPFunctionObject, "function"), + "enum": (CPPEnumObject, "enum"), + "typedef": (CPPTypeObject, "type"), + "using": (CPPTypeObject, "type"), + "union": (CPPUnionObject, "union"), + "namespace": (CPPTypeObject, "type"), + "enumvalue": (CPPEnumeratorObject, "enumerator"), + "define": (CMacroObject, "macro"), } c_classes = { - 'variable': (CMemberObject, 'var'), - 'function': (CFunctionObject, 'function'), - 'define': (CMacroObject, 'macro'), - 'struct': (CStructObject, 'struct'), - 'union': (CUnionObject, 'union'), - 'enum': (CEnumObject, 'enum'), - 'enumvalue': (CEnumeratorObject, 'enumerator'), - 'typedef': (CTypeObject, 'type'), + "variable": (CMemberObject, "var"), + "function": (CFunctionObject, "function"), + "define": (CMacroObject, "macro"), + "struct": (CStructObject, "struct"), + "union": (CUnionObject, "union"), + "enum": (CEnumObject, "enum"), + "enumvalue": (CEnumeratorObject, "enumerator"), + "typedef": (CTypeObject, "type"), } python_classes = { # TODO: PyFunction is meant for module-level functions # and PyAttribute is meant for class attributes, not module-level variables. # Somehow there should be made a distinction at some point to get the correct # index-text and whatever other things are different. - 'function': (PyFunction, 'function'), - 'variable': (PyAttribute, 'attribute'), - 'class': (PyClasslike, 'class'), - 'namespace': (PyClasslike, 'class'), + "function": (PyFunction, "function"), + "variable": (PyAttribute, "attribute"), + "class": (PyClasslike, "class"), + "namespace": (PyClasslike, "class"), } if php is not None: php_classes = { - 'function': (PHPNamespaceLevel, 'function'), - 'class': (PHPClassLike, 'class'), - 'attr': (PHPClassMember, 'attr'), - 'method': (PHPClassMember, 'method'), - 'global': (PHPGlobalLevel, 'global'), + "function": (PHPNamespaceLevel, "function"), + "class": (PHPClassLike, "class"), + "attr": (PHPClassMember, "attr"), + "method": (PHPClassMember, "method"), + "global": (PHPGlobalLevel, "global"), } - php_classes_default = php_classes['class'] # Directive when no matching ones were found + php_classes_default = php_classes["class"] # Directive when no matching ones were found if cs is not None: cs_classes = { # 'doxygen-name': (CSharp class, key in CSharpDomain.object_types) - 'namespace': (CSharpNamespacePlain, 'namespace'), - - 'class': (CSharpClass, 'class'), - 'struct': (CSharpStruct, 'struct'), - 'interface': (CSharpInterface, 'interface'), - - 'function': (CSharpMethod, 'function'), - 'method': (CSharpMethod, 'method'), - - 'variable': (CSharpVariable, 'var'), - 'property': (CSharpProperty, 'property'), - 'event': (CSharpEvent, 'event'), - - 'enum': (CSharpEnum, 'enum'), - 'enumvalue': (CSharpEnumValue, 'enumerator'), - 'attribute': (CSharpAttribute, 'attr'), - + "namespace": (CSharpNamespacePlain, "namespace"), + "class": (CSharpClass, "class"), + "struct": (CSharpStruct, "struct"), + "interface": (CSharpInterface, "interface"), + "function": (CSharpMethod, "function"), + "method": (CSharpMethod, "method"), + "variable": (CSharpVariable, "var"), + "property": (CSharpProperty, "property"), + "event": (CSharpEvent, "event"), + "enum": (CSharpEnum, "enum"), + "enumvalue": (CSharpEnumValue, "enumerator"), + "attribute": (CSharpAttribute, "attr"), # Fallback to cpp domain - 'typedef': (CPPTypeObject, 'type'), + "typedef": (CPPTypeObject, "type"), } @staticmethod @@ -296,36 +299,36 @@ def create(domain: str, args) -> ObjectDescription: name = cast(str, None) # TODO: remove the 'type: ignore's below at some point # perhaps something to do with the mypy version - if domain == 'c': + if domain == "c": cls, name = DomainDirectiveFactory.c_classes[args[0]] # type: ignore - elif domain == 'py': + elif domain == "py": cls, name = DomainDirectiveFactory.python_classes[args[0]] # type: ignore - elif php is not None and domain == 'php': + elif php is not None and domain == "php": separators = php.separators arg_0 = args[0] - if any([separators['method'] in n for n in args[1]]): - if any([separators['attr'] in n for n in args[1]]): - arg_0 = 'attr' + if any([separators["method"] in n for n in args[1]]): + if any([separators["attr"] in n for n in args[1]]): + arg_0 = "attr" else: - arg_0 = 'method' + arg_0 = "method" else: - if arg_0 in ['variable']: - arg_0 = 'global' + if arg_0 in ["variable"]: + arg_0 = "global" if arg_0 in DomainDirectiveFactory.php_classes: cls, name = DomainDirectiveFactory.php_classes[arg_0] # type: ignore else: cls, name = DomainDirectiveFactory.php_classes_default # type: ignore - elif cs is not None and domain == 'cs': + elif cs is not None and domain == "cs": cls, name = DomainDirectiveFactory.cs_classes[args[0]] # type: ignore else: - domain = 'cpp' + domain = "cpp" cls, name = DomainDirectiveFactory.cpp_classes[args[0]] # type: ignore # Replace the directive name because domain directives don't know how to handle # Breathe's "doxygen" directives. - assert ':' not in name - args = [domain + ':' + name] + args[1:] + assert ":" not in name + args = [domain + ":" + name] + args[1:] return cls(*args) @@ -370,22 +373,22 @@ def to_string(node): if not isinstance(value, str): value = value.valueOf_ result.append(value) - return ' '.join(result) + return " ".join(result) param_type = to_string(param.type_) param_name = param.declname if param.declname else param.defname if not param_name: param_decl = param_type else: - param_decl, number_of_subs = re.subn(r'(\((?:\w+::)*[*&]+)(\))', - r'\g<1>' + param_name + r'\g<2>', - param_type) + param_decl, number_of_subs = re.subn( + r"(\((?:\w+::)*[*&]+)(\))", r"\g<1>" + param_name + r"\g<2>", param_type + ) if number_of_subs == 0: - param_decl = param_type + ' ' + param_name + param_decl = param_type + " " + param_name if param.array: param_decl += param.array if param.defval: - param_decl += ' = ' + to_string(param.defval) + param_decl += " = " + to_string(param.defval) return param_decl @@ -398,20 +401,20 @@ def get_definition_without_template_args(data_object): For example in 'Result A< B >::f' we want to remove the '< B >' part. """ definition = data_object.definition - if (len(data_object.bitfield) > 0): + if len(data_object.bitfield) > 0: definition += " : " + data_object.bitfield - qual_name = '::' + data_object.name + qual_name = "::" + data_object.name if definition.endswith(qual_name): qual_name_start = len(definition) - len(qual_name) pos = qual_name_start - 1 - if definition[pos] == '>': + if definition[pos] == ">": bracket_count = 0 # Iterate back through the characters of the definition counting matching braces and # then remove all braces and everything between while pos > 0: - if definition[pos] == '>': + if definition[pos] == ">": bracket_count += 1 - elif definition[pos] == '<': + elif definition[pos] == "<": bracket_count -= 1 if bracket_count == 0: definition = definition[:pos] + definition[qual_name_start:] @@ -427,8 +430,9 @@ class InlineText(Text): consumed and a inline element is generated as the parent, instead of the paragraph used by Text. """ - patterns = {'inlinetext': r''} - initial_transitions = [('inlinetext',)] + + patterns = {"inlinetext": r""} + initial_transitions = [("inlinetext",)] def indent(self, match, context, next_state): """ @@ -454,8 +458,7 @@ def inlinetext(self, match, context, next_state): block = self.state_machine.get_text_block() except UnexpectedIndentationError as err: block, src, srcline = err.args - msg = self.reporter.error('Unexpected indentation.', - source=src, line=srcline) + msg = self.reporter.error("Unexpected indentation.", source=src, line=srcline) lines = context + list(block) text, _ = self.inline_text(lines[0], startline) self.parent += text @@ -469,14 +472,17 @@ class SphinxRenderer: Each visit method takes a Doxygen node as an argument and returns a list of RST nodes. """ - def __init__(self, app: Sphinx, - project_info: ProjectInfo, - node_stack, - state, - document: nodes.document, - target_handler: TargetHandler, - compound_parser: DoxygenCompoundParser, - filter_: Filter): + def __init__( + self, + app: Sphinx, + project_info: ProjectInfo, + node_stack, + state, + document: nodes.document, + target_handler: TargetHandler, + compound_parser: DoxygenCompoundParser, + filter_: Filter, + ): self.app = app self.project_info = project_info @@ -495,7 +501,7 @@ def __init__(self, app: Sphinx, def set_context(self, context: RenderContext) -> None: self.context = context - if self.context.domain == '': + if self.context.domain == "": self.context.domain = self.get_domain() # XXX: fix broken links in XML generated by Doxygen when Doxygen's @@ -518,18 +524,18 @@ def _fixup_separate_member_pages(self, refid: str) -> str: if len(parts) == 2 and parts[1].startswith("1"): anchorid = parts[1][1:] if len(anchorid) in set([33, 34]) and parts[0].endswith(anchorid): - return parts[0][:-len(anchorid)] + parts[1] + return parts[0][: -len(anchorid)] + parts[1] elif len(anchorid) > 34: index = 0 - if anchorid.startswith('gg'): + if anchorid.startswith("gg"): index = 1 _len = 35 - elif anchorid.startswith('g'): + elif anchorid.startswith("g"): _len = 34 else: _len = 33 if parts[0].endswith(anchorid[index:_len]): - return parts[0][:-(_len - index)] + parts[1] + return parts[0][: -(_len - index)] + parts[1] return refid @@ -562,15 +568,16 @@ def get_filename(node) -> Optional[str]: if not filename and node.node_type == "compound": file_data = self.compound_parser.parse(node.refid) filename = get_filename(file_data.compounddef) - return self.project_info.domain_for_file(filename) if filename else '' + return self.project_info.domain_for_file(filename) if filename else "" def join_nested_name(self, names: List[str]) -> str: dom = self.get_domain() - sep = '::' if not dom or dom == 'cpp' else '.' + sep = "::" if not dom or dom == "cpp" else "." return sep.join(names) - def run_directive(self, obj_type: str, declaration: str, contentCallback: ContentCallback, - options={}) -> List[Node]: + def run_directive( + self, obj_type: str, declaration: str, contentCallback: ContentCallback, options={} + ) -> List[Node]: self.context = cast(RenderContext, self.context) args = [obj_type, [declaration]] + self.context.directive_args[2:] directive = DomainDirectiveFactory.create(self.context.domain, args) @@ -578,8 +585,8 @@ def run_directive(self, obj_type: str, declaration: str, contentCallback: Conten directive.breathe_content_callback = contentCallback # type: ignore # Translate Breathe's no-link option into the standard noindex option. - if 'no-link' in self.context.directive_args[2]: - directive.options['noindex'] = True + if "no-link" in self.context.directive_args[2]: + directive.options["noindex"] = True for k, v in options.items(): directive.options[k] = v @@ -588,9 +595,11 @@ def run_directive(self, obj_type: str, declaration: str, contentCallback: Conten if config.breathe_debug_trace_directives: global _debug_indent - print("{}Running directive: .. {}:: {}".format( - ' ' * _debug_indent, - directive.name, declaration)) + print( + "{}Running directive: .. {}:: {}".format( + " " * _debug_indent, directive.name, declaration + ) + ) _debug_indent += 1 self.nesting_level += 1 @@ -619,32 +628,38 @@ def run_directive(self, obj_type: str, declaration: str, contentCallback: Conten signode = finder.declarator if self.context.child: - signode.children = [n for n in signode.children if not n.tagname == 'desc_addname'] + signode.children = [n for n in signode.children if not n.tagname == "desc_addname"] return nodes - def handle_declaration(self, node, declaration: str, *, obj_type: str = None, - content_callback: ContentCallback = None, - display_obj_type: str = None, - declarator_callback: DeclaratorCallback = None, - options={}) -> List[Node]: + def handle_declaration( + self, + node, + declaration: str, + *, + obj_type: str = None, + content_callback: ContentCallback = None, + display_obj_type: str = None, + declarator_callback: DeclaratorCallback = None, + options={} + ) -> List[Node]: if obj_type is None: obj_type = node.kind if content_callback is None: + def content(contentnode): contentnode.extend(self.description(node)) content_callback = content - declaration = declaration.replace('\n', ' ') + declaration = declaration.replace("\n", " ") nodes_ = self.run_directive(obj_type, declaration, content_callback, options) assert self.app.env is not None if self.app.env.config.breathe_debug_trace_doxygen_ids: target = self.create_doxygen_target(node) if len(target) == 0: - print("{}Doxygen target: (none)".format(' ' * _debug_indent)) + print("{}Doxygen target: (none)".format(" " * _debug_indent)) else: - print("{}Doxygen target: {}".format(' ' * _debug_indent, - target[0]['ids'])) + print("{}Doxygen target: {}".format(" " * _debug_indent, target[0]["ids"])) # and then one or more # each has a sphinx_line_type which hints what is present in that line @@ -659,12 +674,12 @@ def content(contentnode): sig = desc[0] assert isinstance(sig, addnodes.desc_signature) # if may or may not be a multiline signature - isMultiline = sig.get('is_multiline', False) + isMultiline = sig.get("is_multiline", False) declarator = None # type: Optional[Declarator] if isMultiline: for line in sig: assert isinstance(line, addnodes.desc_signature_line) - if line.sphinx_line_type == 'declarator': + if line.sphinx_line_type == "declarator": declarator = line else: declarator = sig @@ -676,17 +691,18 @@ def content(contentnode): if sphinx.version_info[0] < 4: newStyle = False # but only for the C and C++ domains - if self.get_domain() and self.get_domain() not in ('c', 'cpp'): + if self.get_domain() and self.get_domain() not in ("c", "cpp"): newStyle = False if newStyle: # TODO: remove the "type: ignore" when Sphinx >= 4 is required assert isinstance(n, addnodes.desc_sig_keyword) # type: ignore declarator[0] = addnodes.desc_sig_keyword( # type: ignore - display_obj_type, display_obj_type) + display_obj_type, display_obj_type + ) else: assert isinstance(n, addnodes.desc_annotation) assert n.astext()[-1] == " " - txt = display_obj_type + ' ' + txt = display_obj_type + " " declarator[0] = addnodes.desc_annotation(txt, txt) if not self.app.env.config.breathe_debug_trace_doxygen_ids: target = self.create_doxygen_target(node) @@ -702,25 +718,27 @@ def get_qualification(self) -> List[str]: assert self.app.env is not None config = self.app.env.config if config.breathe_debug_trace_qualification: + def debug_print_node(n): return "node_type={}".format(n.node_type) global _debug_indent - print("{}{}".format(_debug_indent * ' ', - debug_print_node(self.qualification_stack[0]))) + print( + "{}{}".format(_debug_indent * " ", debug_print_node(self.qualification_stack[0])) + ) _debug_indent += 1 names = [] # type: List[str] for node in self.qualification_stack[1:]: if config.breathe_debug_trace_qualification: - print("{}{}".format(_debug_indent * ' ', debug_print_node(node))) - if node.node_type == 'ref' and len(names) == 0: + print("{}{}".format(_debug_indent * " ", debug_print_node(node))) + if node.node_type == "ref" and len(names) == 0: if config.breathe_debug_trace_qualification: - print("{}{}".format(_debug_indent * ' ', 'res=')) + print("{}{}".format(_debug_indent * " ", "res=")) return [] - if (node.node_type == 'compound' and - node.kind not in ['file', 'namespace', 'group']) or \ - node.node_type == 'memberdef': + if ( + node.node_type == "compound" and node.kind not in ["file", "namespace", "group"] + ) or node.node_type == "memberdef": # We skip the 'file' entries because the file name doesn't form part of the # qualified name for the identifier. We skip the 'namespace' entries because if we # find an object through the namespace 'compound' entry in the index.xml then we'll @@ -728,18 +746,18 @@ def debug_print_node(n): # need the 'compounddef' entry because if we find the object through the 'file' # entry in the index.xml file then we need to get the namespace name from somewhere names.append(node.name) - if (node.node_type == 'compounddef' and node.kind == 'namespace'): + if node.node_type == "compounddef" and node.kind == "namespace": # Nested namespaces include their parent namespace(s) in compoundname. ie, # compoundname is 'foo::bar' instead of just 'bar' for namespace 'bar' nested in # namespace 'foo'. We need full compoundname because node_stack doesn't necessarily # include parent namespaces and we stop here in case it does. - names.extend(reversed(node.compoundname.split('::'))) + names.extend(reversed(node.compoundname.split("::"))) break names.reverse() if config.breathe_debug_trace_qualification: - print("{}res={}".format(_debug_indent * ' ', names)) + print("{}res={}".format(_debug_indent * " ", names)) _debug_indent -= 1 return names @@ -752,15 +770,15 @@ def get_fully_qualified_name(self): node = node_stack[0] # If the node is a namespace, use its name because namespaces are skipped in the main loop. - if node.node_type == 'compound' and node.kind == 'namespace': + if node.node_type == "compound" and node.kind == "namespace": names.append(node.name) for node in node_stack: - if node.node_type == 'ref' and len(names) == 0: + if node.node_type == "ref" and len(names) == 0: return node.valueOf_ - if (node.node_type == 'compound' and - node.kind not in ['file', 'namespace', 'group']) or \ - node.node_type == 'memberdef': + if ( + node.node_type == "compound" and node.kind not in ["file", "namespace", "group"] + ) or node.node_type == "memberdef": # We skip the 'file' entries because the file name doesn't form part of the # qualified name for the identifier. We skip the 'namespace' entries because if we # find an object through the namespace 'compound' entry in the index.xml then we'll @@ -768,7 +786,7 @@ def get_fully_qualified_name(self): # need the 'compounddef' entry because if we find the object through the 'file' # entry in the index.xml file then we need to get the namespace name from somewhere names.insert(0, node.name) - if (node.node_type == 'compounddef' and node.kind == 'namespace'): + if node.node_type == "compounddef" and node.kind == "namespace": # Nested namespaces include their parent namespace(s) in compoundname. ie, # compoundname is 'foo::bar' instead of just 'bar' for namespace 'bar' nested in # namespace 'foo'. We need full compoundname because node_stack doesn't necessarily @@ -776,28 +794,31 @@ def get_fully_qualified_name(self): names.insert(0, node.compoundname) break - return '::'.join(names) + return "::".join(names) def create_template_prefix(self, decl) -> str: if not decl.templateparamlist: return "" nodes = self.render(decl.templateparamlist) - return 'template<' + ''.join(n.astext() for n in nodes) + '>' # type: ignore + return "template<" + "".join(n.astext() for n in nodes) + ">" # type: ignore def run_domain_directive(self, kind, names): domain_directive = DomainDirectiveFactory.create( - self.context.domain, [kind, names] + self.context.directive_args[2:]) + self.context.domain, [kind, names] + self.context.directive_args[2:] + ) # Translate Breathe's no-link option into the standard noindex option. - if 'no-link' in self.context.directive_args[2]: - domain_directive.options['noindex'] = True + if "no-link" in self.context.directive_args[2]: + domain_directive.options["noindex"] = True config = self.app.env.config if config.breathe_debug_trace_directives: global _debug_indent - print("{}Running directive (old): .. {}:: {}".format( - ' ' * _debug_indent, - domain_directive.name, ''.join(names))) + print( + "{}Running directive (old): .. {}:: {}".format( + " " * _debug_indent, domain_directive.name, "".join(names) + ) + ) _debug_indent += 1 nodes = domain_directive.run() @@ -813,7 +834,7 @@ def run_domain_directive(self, kind, names): signode = finder.declarator if len(names) > 0 and self.context.child: - signode.children = [n for n in signode.children if not n.tagname == 'desc_addname'] + signode.children = [n for n in signode.children if not n.tagname == "desc_addname"] return nodes def create_doxygen_target(self, node) -> List[Element]: @@ -855,8 +876,11 @@ def pullup(node, typ, dest): pullup(candNode, nodes.warning, admonitions) # and collapse paragraphs for para in candNode.traverse(nodes.paragraph): - if para.parent and len(para.parent) == 1 \ - and isinstance(para.parent, nodes.paragraph): + if ( + para.parent + and len(para.parent) == 1 + and isinstance(para.parent, nodes.paragraph) + ): para.replace_self(para.children) # and remove empty top-level paragraphs @@ -887,25 +911,25 @@ def pullup(node, typ, dest): for fn, fb in retvals: # we created the retvals before, so we made this prefix assert fn.astext().startswith("returns ") - val = nodes.strong('', fn.astext()[8:]) + val = nodes.strong("", fn.astext()[8:]) # assumption from visit_docparamlist: fb is a single paragraph or nothing assert len(fb) <= 1, fb - bodyNodes = [val, nodes.Text(' -- ')] + bodyNodes = [val, nodes.Text(" -- ")] if len(fb) == 1: assert isinstance(fb[0], nodes.paragraph) bodyNodes.extend(fb[0]) - items.append(nodes.paragraph('', '', *bodyNodes)) + items.append(nodes.paragraph("", "", *bodyNodes)) # only make a bullet list if there are multiple retvals if len(items) == 1: body = items[0] else: body = nodes.bullet_list() for i in items: - body.append(nodes.list_item('', i)) - fRetvals = nodes.field('', - nodes.field_name('', 'returns'), - nodes.field_body('', body)) - fl = nodes.field_list('', *others, fRetvals) + body.append(nodes.list_item("", i)) + fRetvals = nodes.field( + "", nodes.field_name("", "returns"), nodes.field_body("", body) + ) + fl = nodes.field_list("", *others, fRetvals) fieldLists = [fl] if self.app.config.breathe_order_parameters_first: # type: ignore @@ -915,9 +939,9 @@ def pullup(node, typ, dest): def update_signature(self, signature, obj_type): """Update the signature node if necessary, e.g. add qualifiers.""" - prefix = obj_type + ' ' + prefix = obj_type + " " annotation = addnodes.desc_annotation(prefix, prefix) - if signature[0].tagname != 'desc_name': + if signature[0].tagname != "desc_name": signature[0] = annotation else: signature.insert(0, annotation) @@ -925,16 +949,16 @@ def update_signature(self, signature, obj_type): def render_declaration(self, node, declaration=None, description=None, **kwargs): if declaration is None: declaration = self.get_fully_qualified_name() - obj_type = kwargs.get('objtype', None) + obj_type = kwargs.get("objtype", None) if obj_type is None: obj_type = node.kind - nodes = self.run_domain_directive(obj_type, [declaration.replace('\n', ' ')]) + nodes = self.run_domain_directive(obj_type, [declaration.replace("\n", " ")]) if self.app.env.config.breathe_debug_trace_doxygen_ids: target = self.create_doxygen_target(node) if len(target) == 0: - print("{}Doxygen target (old): (none)".format(' ' * _debug_indent)) + print("{}Doxygen target (old): (none)".format(" " * _debug_indent)) else: - print("{}Doxygen target (old): {}".format(' ' * _debug_indent, target[0]['ids'])) + print("{}Doxygen target (old): {}".format(" " * _debug_indent, target[0]["ids"])) rst_node = nodes[1] finder = NodeFinder(rst_node.document) @@ -943,7 +967,7 @@ def render_declaration(self, node, declaration=None, description=None, **kwargs) signode = finder.declarator contentnode = finder.content - update_signature = kwargs.get('update_signature', None) + update_signature = kwargs.get("update_signature", None) if update_signature is not None: update_signature(signode, obj_type) if description is None: @@ -977,16 +1001,17 @@ def visit_union(self, node) -> List[Node]: with WithContext(self, new_context): names = self.get_qualification() if self.nesting_level == 0: - names.extend(nodeDef.compoundname.split('::')) + names.extend(nodeDef.compoundname.split("::")) else: - names.append(nodeDef.compoundname.split('::')[-1]) + names.append(nodeDef.compoundname.split("::")[-1]) declaration = self.join_nested_name(names) def content(contentnode): if nodeDef.includes: for include in nodeDef.includes: - contentnode.extend(self.render(include, - new_context.create_child_context(include))) + contentnode.extend( + self.render(include, new_context.create_child_context(include)) + ) rendered_data = self.render(file_data, parent_context) contentnode.extend(rendered_data) @@ -1012,42 +1037,44 @@ def visit_class(self, node) -> List[Node]: # TODO: this breaks if it's a template specialization # and one of the arguments contain '::' if self.nesting_level == 0: - names.extend(nodeDef.compoundname.split('::')) + names.extend(nodeDef.compoundname.split("::")) else: - names.append(nodeDef.compoundname.split('::')[-1]) + names.append(nodeDef.compoundname.split("::")[-1]) decls = [ self.create_template_prefix(nodeDef), self.join_nested_name(names), ] # add base classes if len(nodeDef.basecompoundref) != 0: - decls.append(':') + decls.append(":") first = True for base in nodeDef.basecompoundref: if not first: - decls.append(',') + decls.append(",") else: first = False if base.prot is not None: decls.append(base.prot) - if base.virt == 'virtual': - decls.append('virtual') + if base.virt == "virtual": + decls.append("virtual") decls.append(base.content_[0].value) - declaration = ' '.join(decls) + declaration = " ".join(decls) def content(contentnode) -> None: if nodeDef.includes: for include in nodeDef.includes: - contentnode.extend(self.render(include, - new_context.create_child_context(include))) + contentnode.extend( + self.render(include, new_context.create_child_context(include)) + ) rendered_data = self.render(file_data, parent_context) contentnode.extend(rendered_data) - assert kind in ('class', 'struct', 'interface') - display_obj_type = 'interface' if kind == 'interface' else None - nodes = self.handle_declaration(nodeDef, declaration, content_callback=content, - display_obj_type=display_obj_type) - if 'members-only' in self.context.directive_args[2]: + assert kind in ("class", "struct", "interface") + display_obj_type = "interface" if kind == "interface" else None + nodes = self.handle_declaration( + nodeDef, declaration, content_callback=content, display_obj_type=display_obj_type + ) + if "members-only" in self.context.directive_args[2]: assert len(nodes) >= 2 assert isinstance(nodes[1], addnodes.desc) assert len(nodes[1]) >= 2 @@ -1069,22 +1096,24 @@ def visit_namespace(self, node) -> List[Node]: # definition, for proper domain detection names = self.get_qualification() if self.nesting_level == 0: - names.extend(nodeDef.compoundname.split('::')) + names.extend(nodeDef.compoundname.split("::")) else: - names.append(nodeDef.compoundname.split('::')[-1]) + names.append(nodeDef.compoundname.split("::")[-1]) declaration = self.join_nested_name(names) def content(contentnode): if nodeDef.includes: for include in nodeDef.includes: - contentnode.extend(self.render(include, - new_context.create_child_context(include))) + contentnode.extend( + self.render(include, new_context.create_child_context(include)) + ) rendered_data = self.render(file_data, parent_context) contentnode.extend(rendered_data) - display_obj_type = 'namespace' if self.get_domain() != 'py' else 'module' - nodes = self.handle_declaration(nodeDef, declaration, content_callback=content, - display_obj_type=display_obj_type) + display_obj_type = "namespace" if self.get_domain() != "py" else "module" + nodes = self.handle_declaration( + nodeDef, declaration, content_callback=content, display_obj_type=display_obj_type + ) return nodes def visit_compound(self, node, render_empty_node=True, **kwargs) -> List[Node]: @@ -1094,18 +1123,18 @@ def visit_compound(self, node, render_empty_node=True, **kwargs) -> List[Node]: def get_node_info(file_data): return node.name, node.kind - name, kind = kwargs.get('get_node_info', get_node_info)(file_data) - if kind == 'union': + name, kind = kwargs.get("get_node_info", get_node_info)(file_data) + if kind == "union": dom = self.get_domain() - assert not dom or dom in ('c', 'cpp') + assert not dom or dom in ("c", "cpp") return self.visit_union(node) - elif kind in ('struct', 'class', 'interface'): + elif kind in ("struct", "class", "interface"): dom = self.get_domain() - if not dom or dom in ('c', 'cpp', 'py', 'cs'): + if not dom or dom in ("c", "cpp", "py", "cs"): return self.visit_class(node) - elif kind == 'namespace': + elif kind == "namespace": dom = self.get_domain() - if not dom or dom in ('c', 'cpp', 'py', 'cs'): + if not dom or dom in ("c", "cpp", "py", "cs"): return self.visit_namespace(node) self.context = cast(RenderContext, self.context) @@ -1123,13 +1152,13 @@ def render_signature(file_data, doxygen_target, name, kind): arg = "%s %s" % (templatePrefix, self.get_fully_qualified_name()) # add base classes - if kind in ('class', 'struct'): + if kind in ("class", "struct"): bs = [] for base in file_data.compounddef.basecompoundref: b = [] if base.prot is not None: b.append(base.prot) - if base.virt == 'virtual': + if base.virt == "virtual": b.append("virtual") b.append(base.content_[0].value) bs.append(" ".join(b)) @@ -1145,22 +1174,22 @@ def render_signature(file_data, doxygen_target, name, kind): finder = NodeFinder(rst_node.document) rst_node.walk(finder) - if kind in ('interface', 'namespace'): + if kind in ("interface", "namespace"): # This is not a real C++ declaration type that Sphinx supports, # so we hax the replacement of it. - finder.declarator[0] = addnodes.desc_annotation(kind + ' ', kind + ' ') + finder.declarator[0] = addnodes.desc_annotation(kind + " ", kind + " ") rst_node.children[0].insert(0, doxygen_target) return nodes, finder.content refid = self.get_refid(node.refid) - render_sig = kwargs.get('render_signature', render_signature) + render_sig = kwargs.get("render_signature", render_signature) with WithContext(self, new_context): # Pretend that the signature is being rendered in context of the # definition, for proper domain detection nodes, contentnode = render_sig( - file_data, self.target_handler.create_target(refid), - name, kind) + file_data, self.target_handler.create_target(refid), name, kind + ) if file_data.compounddef.includes: for include in file_data.compounddef.includes: @@ -1194,8 +1223,8 @@ def render_signature(file_data, doxygen_target, name, kind): rst_node.append(title_signode) rst_node.document = self.state.document - rst_node['objtype'] = kind - rst_node['domain'] = self.get_domain() if self.get_domain() else 'cpp' + rst_node["objtype"] = kind + rst_node["domain"] = self.get_domain() if self.get_domain() else "cpp" contentnode = addnodes.desc_content() rst_node.append(contentnode) @@ -1253,11 +1282,11 @@ def visit_compounddef(self, node) -> List[Node]: self.context = cast(RenderContext, self.context) options = self.context.directive_args[2] section_order = None - if 'sections' in options: - section_order = {sec: i for i, sec in enumerate(options['sections'].split(' '))} + if "sections" in options: + section_order = {sec: i for i, sec in enumerate(options["sections"].split(" "))} membergroup_order = None - if 'membergroups' in options: - membergroup_order = {sec: i for i, sec in enumerate(options['membergroups'].split(' '))} + if "membergroups" in options: + membergroup_order = {sec: i for i, sec in enumerate(options["membergroups"].split(" "))} nodemap = {} # type: Dict[int, List[Node]] def addnode(kind, lam): @@ -1266,9 +1295,9 @@ def addnode(kind, lam): elif kind in section_order: nodemap.setdefault(section_order[kind], []).extend(lam()) - if 'members-only' not in options: - addnode('briefdescription', lambda: self.render_optional(node.briefdescription)) - addnode('detaileddescription', lambda: self.render_optional(node.detaileddescription)) + if "members-only" not in options: + addnode("briefdescription", lambda: self.render_optional(node.briefdescription)) + addnode("detaileddescription", lambda: self.render_optional(node.detaileddescription)) def render_derivedcompoundref(node): if node is None: @@ -1276,15 +1305,15 @@ def render_derivedcompoundref(node): output = self.render_iterable(node) if not output: return [] - return [nodes.paragraph( - '', - '', - nodes.Text('Subclassed by '), - *intersperse(output, nodes.Text(', ')) - )] - - addnode('derivedcompoundref', - lambda: render_derivedcompoundref(node.derivedcompoundref)) + return [ + nodes.paragraph( + "", "", nodes.Text("Subclassed by "), *intersperse(output, nodes.Text(", ")) + ) + ] + + addnode( + "derivedcompoundref", lambda: render_derivedcompoundref(node.derivedcompoundref) + ) section_nodelists = {} # type: Dict[str, List[Node]] @@ -1300,9 +1329,9 @@ def render_derivedcompoundref(node): if not child_nodes: # Skip empty section continue - rst_node = nodes.container(classes=['breathe-sectiondef']) + rst_node = nodes.container(classes=["breathe-sectiondef"]) rst_node.document = self.state.document - rst_node['objtype'] = kind + rst_node["objtype"] = kind rst_node.extend(child_nodes) # We store the nodes as a list against the kind in a dictionary as the kind can be # 'user-edited' and that can repeat so this allows us to collect all the 'user-edited' @@ -1314,14 +1343,14 @@ def render_derivedcompoundref(node): addnode(kind, lambda: section_nodelists.get(kind, [])) # Take care of innerclasses - addnode('innerclass', lambda: self.render_iterable(node.innerclass)) - addnode('innernamespace', lambda: self.render_iterable(node.innernamespace)) + addnode("innerclass", lambda: self.render_iterable(node.innerclass)) + addnode("innernamespace", lambda: self.render_iterable(node.innernamespace)) - if 'inner' in options: + if "inner" in options: for node in node.innergroup: file_data = self.compound_parser.parse(node.refid) inner = file_data.compounddef - addnode('innergroup', lambda: self.visit_compounddef(inner)) + addnode("innergroup", lambda: self.visit_compounddef(inner)) nodelist = [] for i, nodes_ in sorted(nodemap.items()): @@ -1341,7 +1370,7 @@ def visit_sectiondef(self, node) -> List[Node]: node_list.extend(self.render_iterable(node.memberdef)) if node_list: - if 'members-only' in options: + if "members-only" in options: return node_list text = self.section_titles[node.kind] @@ -1357,10 +1386,12 @@ def visit_sectiondef(self, node) -> List[Node]: # Use rubric for the title because, unlike the docutils element "section", # it doesn't interfere with the document structure. - idtext = text.replace(' ', '-').lower() - rubric = nodes.rubric(text=text, - classes=['breathe-sectiondef-title'], - ids=['breathe-section-title-' + idtext]) + idtext = text.replace(" ", "-").lower() + rubric = nodes.rubric( + text=text, + classes=["breathe-sectiondef-title"], + ids=["breathe-section-title-" + idtext], + ) res = [rubric] # type: List[Node] return res + node_list return [] @@ -1433,7 +1464,7 @@ def visit_docpara(self, node) -> List[Node]: # note: all these gets pulled up and reordered in description() if len(defs) != 0: - deflist = nodes.definition_list('', *defs) + deflist = nodes.definition_list("", *defs) nodelist.append(deflist) nodelist.extend(paramList) nodelist.extend(fields) @@ -1476,15 +1507,15 @@ def visit_docmarkup(self, node) -> List[Node]: return [creator("", "", *nodelist)] def visit_docsectN(self, node) -> List[Node]: - ''' + """ Docutils titles are defined by their level inside the document so the proper structure is only guaranteed by the Doxygen XML. Doxygen command mapping to XML element name: @section == sect1, @subsection == sect2, @subsubsection == sect3 - ''' + """ section = nodes.section() - section['ids'].append(self.get_refid(node.id)) + section["ids"].append(self.get_refid(node.id)) section += nodes.title(node.title, node.title) section += self.create_doxygen_target(node) section += self.render_iterable(node.content_) @@ -1498,16 +1529,21 @@ def visit_docsimplesect(self, node) -> List[Node]: nodelist = self.render_iterable(node.para) - if node.kind in ('pre', 'post', 'return'): - return [nodes.field_list('', nodes.field( - '', - nodes.field_name('', nodes.Text(node.kind)), - nodes.field_body('', *nodelist) - ))] - elif node.kind == 'warning': - return [nodes.warning('', *nodelist)] - elif node.kind == 'note': - return [nodes.note('', *nodelist)] + if node.kind in ("pre", "post", "return"): + return [ + nodes.field_list( + "", + nodes.field( + "", + nodes.field_name("", nodes.Text(node.kind)), + nodes.field_body("", *nodelist), + ), + ) + ] + elif node.kind == "warning": + return [nodes.warning("", *nodelist)] + elif node.kind == "note": + return [nodes.note("", *nodelist)] if node.kind == "par": text = self.render(node.title) @@ -1533,21 +1569,19 @@ def visit_docformula(self, node) -> List[Node]: # Either inline if latex.startswith("$") and latex.endswith("$"): latex = latex[1:-1] - nodelist.append(nodes.math(text=latex, - label=None, - nowrap=False, - docname=docname, - number=None)) + nodelist.append( + nodes.math(text=latex, label=None, nowrap=False, docname=docname, number=None) + ) # Else we're multiline else: if latex.startswith("\\[") and latex.endswith("\\]"): latex = latex[2:-2:] - nodelist.append(nodes.math_block(text=latex, - label=None, - nowrap=False, - docname=docname, - number=None)) + nodelist.append( + nodes.math_block( + text=latex, label=None, nowrap=False, docname=docname, number=None + ) + ) return nodelist def visit_listing(self, node) -> List[Node]: @@ -1560,11 +1594,7 @@ def visit_listing(self, node) -> List[Node]: # Add blank string at the start otherwise for some reason it renders # the pending_xref tags around the kind in plain text - block = nodes.literal_block( - "", - "", - *nodelist - ) + block = nodes.literal_block("", "", *nodelist) return [block] def visit_codeline(self, node) -> List[Node]: @@ -1583,11 +1613,16 @@ def _nested_inline_parse_with_titles(self, content, node) -> str: self.state.memo.title_styles = [] self.state.memo.section_level = 0 try: - return self.state.nested_parse(content, 0, node, match_titles=1, - state_machine_kwargs={ - 'state_classes': (InlineText,), - 'initial_state': 'InlineText' - }) + return self.state.nested_parse( + content, + 0, + node, + match_titles=1, + state_machine_kwargs={ + "state_classes": (InlineText,), + "initial_state": "InlineText", + }, + ) finally: self.state.memo.title_styles = surrounding_title_styles self.state.memo.section_level = surrounding_section_level @@ -1658,7 +1693,7 @@ def visit_inc(self, node) -> List[Node]: if node.local == u"yes": text = '#include "%s"' % node.content_[0].getValue() else: - text = '#include <%s>' % node.content_[0].getValue() + text = "#include <%s>" % node.content_[0].getValue() return [nodes.emphasis(text=text)] @@ -1677,7 +1712,7 @@ def visit_doclistitem(self, node) -> List[Node]: nodelist = self.render_iterable(node.para) return [nodes.list_item("", *nodelist)] - numeral_kind = ['arabic', 'loweralpha', 'lowerroman', 'upperalpha', 'upperroman'] + numeral_kind = ["arabic", "loweralpha", "lowerroman", "upperalpha", "upperroman"] def render_unordered(self, children) -> List[Node]: nodelist_list = nodes.bullet_list("", *children) @@ -1686,9 +1721,9 @@ def render_unordered(self, children) -> List[Node]: def render_enumerated(self, children, nesting_level) -> List[Node]: nodelist_list = nodes.enumerated_list("", *children) idx = nesting_level % len(SphinxRenderer.numeral_kind) - nodelist_list['enumtype'] = SphinxRenderer.numeral_kind[idx] - nodelist_list['prefix'] = '' - nodelist_list['suffix'] = '.' + nodelist_list["enumtype"] = SphinxRenderer.numeral_kind[idx] + nodelist_list["prefix"] = "" + nodelist_list["suffix"] = "." return [nodelist_list] def visit_doclist(self, node) -> List[Node]: @@ -1696,7 +1731,7 @@ def visit_doclist(self, node) -> List[Node]: The specifics of the actual list rendering are handled by the decorator around the generic render function. - Render all the children depth-first. """ + Render all the children depth-first.""" """ Call the wrapped render function. Update the nesting level for the enumerated lists. """ if node.node_subtype == "itemized": val = self.render_iterable(node.listitem) @@ -1729,7 +1764,7 @@ def visit_docxrefsect(self, node) -> List[Node]: assert self.app.env is not None signode = addnodes.desc_signature() - title = node.xreftitle[0] + ':' + title = node.xreftitle[0] + ":" titlenode = nodes.emphasis(text=title) ref = addnodes.pending_xref( "", @@ -1738,7 +1773,8 @@ def visit_docxrefsect(self, node) -> List[Node]: refexplicit=True, reftarget=node.id, refdoc=self.app.env.docname, - *[titlenode]) + *[titlenode] + ) signode += ref nodelist = self.render(node.xrefdescription) @@ -1746,8 +1782,8 @@ def visit_docxrefsect(self, node) -> List[Node]: contentnode += nodelist descnode = addnodes.desc() - descnode['objtype'] = 'xrefsect' - descnode['domain'] = self.get_domain() if self.get_domain() else 'cpp' + descnode["objtype"] = "xrefsect" + descnode["domain"] = self.get_domain() if self.get_domain() else "cpp" descnode += signode descnode += contentnode @@ -1757,8 +1793,8 @@ def visit_docvariablelist(self, node) -> List[Node]: output = [] for varlistentry, listitem in zip(node.varlistentries, node.listitems): descnode = addnodes.desc() - descnode['objtype'] = 'varentry' - descnode['domain'] = self.get_domain() if self.get_domain() else 'cpp' + descnode["objtype"] = "varentry" + descnode["domain"] = self.get_domain() if self.get_domain() else "cpp" signode = addnodes.desc_signature() signode += self.render_optional(varlistentry) descnode += signode @@ -1778,18 +1814,18 @@ def visit_docanchor(self, node) -> List[Node]: def visit_docentry(self, node) -> List[Node]: col = nodes.entry() col += self.render_iterable(node.para) - if node.thead == 'yes': - col['heading'] = True + if node.thead == "yes": + col["heading"] = True if node.rowspan: - col['morerows'] = int(node.rowspan) - 1 + col["morerows"] = int(node.rowspan) - 1 if node.colspan: - col['morecols'] = int(node.colspan) - 1 + col["morecols"] = int(node.colspan) - 1 return [col] def visit_docrow(self, node) -> List[Node]: row = nodes.row() cols = self.render_iterable(node.entry) - if all(col.get('heading', False) for col in cols): + if all(col.get("heading", False) for col in cols): elem = nodes.thead() else: elem = nodes.tbody() @@ -1799,11 +1835,11 @@ def visit_docrow(self, node) -> List[Node]: def visit_doctable(self, node) -> List[Node]: table = nodes.table() - table['classes'] += ['colwidths-auto'] + table["classes"] += ["colwidths-auto"] tgroup = nodes.tgroup(cols=node.cols) for _ in range(node.cols): colspec = nodes.colspec() - colspec.attributes['colwidth'] = 'auto' + colspec.attributes["colwidth"] = "auto" tgroup += colspec table += tgroup rows = self.render_iterable(node.row) @@ -1839,41 +1875,43 @@ def visit_linkedtext(self, node) -> List[Node]: def visit_function(self, node) -> List[Node]: dom = self.get_domain() - if not dom or dom in ('c', 'cpp', 'py', 'cs'): + if not dom or dom in ("c", "cpp", "py", "cs"): names = self.get_qualification() names.append(node.get_name()) name = self.join_nested_name(names) - if dom == 'py': + if dom == "py": declaration = name + node.get_argsstring() - elif dom == 'cs': - declaration = ' '.join([ - self.create_template_prefix(node), - ''.join(n.astext() for n in self.render(node.get_type())), # type: ignore - name, - node.get_argsstring() - ]) + elif dom == "cs": + declaration = " ".join( + [ + self.create_template_prefix(node), + "".join(n.astext() for n in self.render(node.get_type())), # type: ignore + name, + node.get_argsstring(), + ] + ) else: elements = [self.create_template_prefix(node)] - if node.static == 'yes': - elements.append('static') - if node.inline == 'yes': - elements.append('inline') - if node.kind == 'friend': - elements.append('friend') - if node.virt in ('virtual', 'pure-virtual'): - elements.append('virtual') - if node.explicit == 'yes': - elements.append('explicit') + if node.static == "yes": + elements.append("static") + if node.inline == "yes": + elements.append("inline") + if node.kind == "friend": + elements.append("friend") + if node.virt in ("virtual", "pure-virtual"): + elements.append("virtual") + if node.explicit == "yes": + elements.append("explicit") # TODO: handle constexpr when parser has been updated # but Doxygen seems to leave it in the type anyway - typ = ''.join(n.astext() for n in self.render(node.get_type())) + typ = "".join(n.astext() for n in self.render(node.get_type())) # Doxygen sometimes leaves 'static' in the type, # e.g., for "constexpr static auto f()" - typ = typ.replace('static ', '') + typ = typ.replace("static ", "") elements.append(typ) elements.append(name) elements.append(node.get_argsstring()) - declaration = ' '.join(elements) + declaration = " ".join(elements) nodes = self.handle_declaration(node, declaration) return nodes else: @@ -1885,28 +1923,27 @@ def visit_function(self, node) -> List[Node]: param_decl = get_param_decl(param) param_list.append(param_decl) templatePrefix = self.create_template_prefix(node) - signature = '{0}{1}({2})'.format( - templatePrefix, - get_definition_without_template_args(node), - ', '.join(param_list)) + signature = "{0}{1}({2})".format( + templatePrefix, get_definition_without_template_args(node), ", ".join(param_list) + ) # Add CV-qualifiers. - if node.const == 'yes': - signature += ' const' + if node.const == "yes": + signature += " const" # The doxygen xml output doesn't register 'volatile' as the xml attribute for functions # until version 1.8.8 so we also check argsstring: # https://bugzilla.gnome.org/show_bug.cgi?id=733451 - if node.volatile == 'yes' or node.argsstring.endswith('volatile'): - signature += ' volatile' + if node.volatile == "yes" or node.argsstring.endswith("volatile"): + signature += " volatile" - if node.refqual == 'lvalue': - signature += '&' - elif node.refqual == 'rvalue': - signature += '&&' + if node.refqual == "lvalue": + signature += "&" + elif node.refqual == "rvalue": + signature += "&&" # Add `= 0` for pure virtual members. - if node.virt == 'pure-virtual': - signature += '= 0' + if node.virt == "pure-virtual": + signature += "= 0" self.context = cast(RenderContext, self.context) self.context.directive_args[1] = [signature] @@ -1917,10 +1954,11 @@ def visit_function(self, node) -> List[Node]: if self.app.env.config.breathe_debug_trace_doxygen_ids: target = self.create_doxygen_target(node) if len(target) == 0: - print("{}Doxygen target (old): (none)".format(' ' * _debug_indent)) + print("{}Doxygen target (old): (none)".format(" " * _debug_indent)) else: - print("{}Doxygen target (old): {}".format(' ' * _debug_indent, - target[0]['ids'])) + print( + "{}Doxygen target (old): {}".format(" " * _debug_indent, target[0]["ids"]) + ) rst_node = nodes[1] finder = NodeFinder(rst_node.document) @@ -1974,25 +2012,25 @@ def visit_enumvalue(self, node) -> List[Node]: declaration = node.name + self.make_initializer(node) else: declaration = node.name - return self.handle_declaration(node, declaration, obj_type='enumvalue') + return self.handle_declaration(node, declaration, obj_type="enumvalue") def visit_typedef(self, node) -> List[Node]: - type_ = ''.join(n.astext() for n in self.render(node.get_type())) # type: ignore + type_ = "".join(n.astext() for n in self.render(node.get_type())) # type: ignore names = self.get_qualification() names.append(node.get_name()) name = self.join_nested_name(names) - if node.definition.startswith('using '): + if node.definition.startswith("using "): # TODO: looks like Doxygen does not generate the proper XML # for the template parameter list declaration = self.create_template_prefix(node) - declaration += ' ' + name + " = " + type_ + declaration += " " + name + " = " + type_ else: # TODO: Both "using" and "typedef" keywords get into this function, # and if no @typedef comment was added, the definition should # contain the full text. If a @typedef was used instead, the # definition has only the typename, which makes it impossible to # distinguish between them so fallback to "typedef" behavior here. - declaration = ' '.join([type_, name, node.get_argsstring()]) + declaration = " ".join([type_, name, node.get_argsstring()]) return self.handle_declaration(node, declaration) def make_initializer(self, node) -> str: @@ -2002,12 +2040,12 @@ def make_initializer(self, node) -> str: render_nodes = self.render(initializer) # Do not append separators for paragraphs. if not isinstance(render_nodes[0], nodes.paragraph): - separator = ' ' - if not render_nodes[0].startswith('='): # type: ignore - separator += '= ' + separator = " " + if not render_nodes[0].startswith("="): # type: ignore + separator += "= " signature.append(nodes.Text(separator)) signature.extend(render_nodes) - return ''.join(n.astext() for n in signature) # type: ignore + return "".join(n.astext() for n in signature) # type: ignore def visit_variable(self, node) -> List[Node]: names = self.get_qualification() @@ -2015,63 +2053,65 @@ def visit_variable(self, node) -> List[Node]: name = self.join_nested_name(names) dom = self.get_domain() options = {} - if dom == 'py': + if dom == "py": declaration = name - initializer = self.make_initializer(node).strip().lstrip('=').strip() + initializer = self.make_initializer(node).strip().lstrip("=").strip() if len(initializer) != 0: - options['value'] = initializer - elif dom == 'cs': - declaration = ' '.join([ - self.create_template_prefix(node), - ''.join(n.astext() for n in self.render(node.get_type())), # type: ignore - name, - node.get_argsstring() - ]) + options["value"] = initializer + elif dom == "cs": + declaration = " ".join( + [ + self.create_template_prefix(node), + "".join(n.astext() for n in self.render(node.get_type())), # type: ignore + name, + node.get_argsstring(), + ] + ) if node.get_gettable() or node.get_settable(): - declaration += '{' + declaration += "{" if node.get_gettable(): - declaration += 'get;' + declaration += "get;" if node.get_settable(): - declaration += 'set;' - declaration += '}' + declaration += "set;" + declaration += "}" declaration += self.make_initializer(node) else: elements = [self.create_template_prefix(node)] - if node.static == 'yes': - elements.append('static') - if node.mutable == 'yes': - elements.append('mutable') - typename = ''.join(n.astext() for n in self.render(node.get_type())) - if dom == 'c' and '::' in typename: - typename = typename.replace('::', '.') + if node.static == "yes": + elements.append("static") + if node.mutable == "yes": + elements.append("mutable") + typename = "".join(n.astext() for n in self.render(node.get_type())) + if dom == "c" and "::" in typename: + typename = typename.replace("::", ".") elements.append(typename) elements.append(name) elements.append(node.get_argsstring()) elements.append(self.make_initializer(node)) - declaration = ' '.join(elements) - if not dom or dom in ('c', 'cpp', 'py', 'cs'): + declaration = " ".join(elements) + if not dom or dom in ("c", "cpp", "py", "cs"): return self.handle_declaration(node, declaration, options=options) else: return self.render_declaration(node, declaration) def visit_friendclass(self, node) -> List[Node]: dom = self.get_domain() - assert not dom or dom == 'cpp' + assert not dom or dom == "cpp" desc = addnodes.desc() - desc['objtype'] = 'friendclass' - desc['domain'] = self.get_domain() if self.get_domain() else 'cpp' + desc["objtype"] = "friendclass" + desc["domain"] = self.get_domain() if self.get_domain() else "cpp" signode = addnodes.desc_signature() desc += signode - typ = ''.join(n.astext() for n in self.render(node.get_type())) # type: ignore + typ = "".join(n.astext() for n in self.render(node.get_type())) # type: ignore # in Doxygen < 1.9 the 'friend' part is there, but afterwards not # https://github.com/michaeljones/breathe/issues/616 assert typ in ("friend class", "friend struct", "class", "struct") - if not typ.startswith('friend '): - typ = 'friend ' + typ + if not typ.startswith("friend "): + typ = "friend " + typ signode += addnodes.desc_annotation(typ, typ) - signode += nodes.Text(' ') + signode += nodes.Text(" ") # expr = cpp.CPPExprRole(asCode=False) # expr.text = node.name # TODO: set most of the things that SphinxRole.__call__ sets @@ -2079,8 +2119,9 @@ def visit_friendclass(self, node) -> List[Node]: signode += nodes.Text(node.name) return [desc] - def visit_templateparam(self, node: compound.paramTypeSub, *, - insertDeclNameByParsing: bool = False) -> List[Node]: + def visit_templateparam( + self, node: compound.paramTypeSub, *, insertDeclNameByParsing: bool = False + ) -> List[Node]: nodelist = [] # Parameter type @@ -2089,9 +2130,9 @@ def visit_templateparam(self, node: compound.paramTypeSub, *, # Render keywords as annotations for consistency with the cpp domain. if len(type_nodes) > 0 and isinstance(type_nodes[0], str): first_node = type_nodes[0] - for keyword in ['typename', 'class']: - if first_node.startswith(keyword + ' '): - type_nodes[0] = nodes.Text(first_node.replace(keyword, '', 1)) + for keyword in ["typename", "class"]: + if first_node.startswith(keyword + " "): + type_nodes[0] = nodes.Text(first_node.replace(keyword, "", 1)) type_nodes.insert(0, addnodes.desc_annotation(keyword, keyword)) break nodelist.extend(type_nodes) @@ -2100,23 +2141,27 @@ def visit_templateparam(self, node: compound.paramTypeSub, *, if node.declname: dom = self.get_domain() if not dom: - dom = 'cpp' + dom = "cpp" appendDeclName = True if insertDeclNameByParsing: - if dom == 'cpp' and sphinx.version_info >= (4, 1, 0): + if dom == "cpp" and sphinx.version_info >= (4, 1, 0): parser = cpp.DefinitionParser( - ''.join(n.astext() for n in nodelist), + "".join(n.astext() for n in nodelist), location=self.state.state_machine.get_source_and_line(), - config=self.app.config) + config=self.app.config, + ) 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') + 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) + 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))] @@ -2186,11 +2231,12 @@ def visit_docparamlist(self, node) -> List[Node]: assert len(content) == 1 or len(content) == 2, content thisName = self.render(content[-1]) if len(nameNodes) != 0: - if node.kind == 'exception': + if node.kind == "exception": msg = "Doxygen \\exception commands with multiple names can not be" msg += " converted to a single :throws: field in Sphinx." msg += " Exception '{}' suppresed from output.".format( - ''.join(n.astext() for n in thisName)) + "".join(n.astext() for n in thisName) + ) self.state.document.reporter.warning(msg) continue nameNodes.append(nodes.Text(", ")) @@ -2198,27 +2244,25 @@ def visit_docparamlist(self, node) -> List[Node]: if len(content) == 2: # note, each paramName node seems to have the same direction, # so just use the last one - dir = ''.join(n.astext() for n in self.render(content[0])).strip() - assert dir in ('[in]', '[out]', '[inout]'), ">" + dir + "<" - parameterDirectionNodes = [ - nodes.strong(dir, dir), - nodes.Text(' ', ' ') - ] + dir = "".join(n.astext() for n in self.render(content[0])).strip() + assert dir in ("[in]", "[out]", "[inout]"), ">" + dir + "<" + parameterDirectionNodes = [nodes.strong(dir, dir), nodes.Text(" ", " ")] # it seems that Sphinx expects the name to be a single node, # so let's make it that - txt = fieldListName[node.kind] + ' ' + txt = fieldListName[node.kind] + " " for n in nameNodes: txt += n.astext() - name = nodes.field_name('', nodes.Text(txt)) + name = nodes.field_name("", nodes.Text(txt)) bodyNodes = self.render_optional(item.parameterdescription) # TODO: is it correct that bodyNodes is either empty or a single paragraph? assert len(bodyNodes) <= 1, bodyNodes if len(bodyNodes) == 1: assert isinstance(bodyNodes[0], nodes.paragraph) - bodyNodes = [nodes.paragraph( - '', '', *(parameterDirectionNodes + bodyNodes[0].children))] - body = nodes.field_body('', *bodyNodes) - field = nodes.field('', name, body) + bodyNodes = [ + nodes.paragraph("", "", *(parameterDirectionNodes + bodyNodes[0].children)) + ] + body = nodes.field_body("", *bodyNodes) + field = nodes.field("", name, body) fieldList += field return [fieldList] @@ -2234,8 +2278,9 @@ def dispatch_compound(self, node) -> List[Node]: def dispatch_memberdef(self, node) -> List[Node]: """Dispatch handling of a memberdef node to a suitable visit method.""" - if node.kind in ("function", "signal", "slot") or \ - (node.kind == 'friend' and node.argsstring): + if node.kind in ("function", "signal", "slot") or ( + node.kind == "friend" and node.argsstring + ): return self.visit_function(node) if node.kind == "enum": return self.visit_enum(node) @@ -2320,8 +2365,11 @@ def render_string(self, node: str) -> List[Union[nodes.Text, nodes.paragraph]]: delimiter = "\n" if delimiter: # Render lines as paragraphs because RST doesn't have line breaks. - return [nodes.paragraph('', '', nodes.Text(line.strip())) - for line in node.split(delimiter) if line.strip()] + return [ + nodes.paragraph("", "", nodes.Text(line.strip())) + for line in node.split(delimiter) + if line.strip() + ] # importantly, don't strip whitespace as visit_docpara uses it to collapse # consecutive nodes.Text and rerender them with this function. return [nodes.Text(node)] @@ -2357,6 +2405,6 @@ def render_iterable(self, iterable: List[Node]) -> List[Node]: def setup(app: Sphinx) -> None: - app.add_config_value('breathe_debug_trace_directives', False, '') - app.add_config_value('breathe_debug_trace_doxygen_ids', False, '') - app.add_config_value('breathe_debug_trace_qualification', False, '') + app.add_config_value("breathe_debug_trace_directives", False, "") + app.add_config_value("breathe_debug_trace_doxygen_ids", False, "") + app.add_config_value("breathe_debug_trace_qualification", False, "") diff --git a/breathe/renderer/target.py b/breathe/renderer/target.py index e08f772ff..57c810380 100644 --- a/breathe/renderer/target.py +++ b/breathe/renderer/target.py @@ -33,8 +33,9 @@ def create_target(self, refid: str) -> List[Element]: return [] -def create_target_handler(options: Dict[str, Any], project_info: ProjectInfo, - document: nodes.document) -> TargetHandler: +def create_target_handler( + options: Dict[str, Any], project_info: ProjectInfo, document: nodes.document +) -> TargetHandler: if "no-link" in options: return _NullTargetHandler() return _RealTargetHandler(project_info, document) diff --git a/documentation/source/conf.py b/documentation/source/conf.py index ebaeb8645..82dc903b9 100644 --- a/documentation/source/conf.py +++ b/documentation/source/conf.py @@ -22,86 +22,86 @@ # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ 'breathe', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig' ] +extensions = ["breathe", "sphinx.ext.mathjax", "sphinx.ext.ifconfig"] -read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' -travis_build = os.environ.get('TRAVIS_CI', None) == 'True' +read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" +travis_build = os.environ.get("TRAVIS_CI", None) == "True" # Get a description of the current position. Use Popen for 2.6 compat -git_tag = subprocess.Popen(['git', 'describe', '--tags'], stdout=subprocess.PIPE).communicate()[0] +git_tag = subprocess.Popen(["git", "describe", "--tags"], stdout=subprocess.PIPE).communicate()[0] # convert from bytes to string -git_tag = git_tag.decode('ascii') +git_tag = git_tag.decode("ascii") if travis_build: # Don't attempt to set the path as breathe is installed to virtualenv on travis # Set values with simple strings - version = '\'travis\'' - release = '\'travis\'' - documentation_build = 'travis' + version = "'travis'" + release = "'travis'" + documentation_build = "travis" elif read_the_docs_build: # On RTD we'll be in the 'source' directory - sys.path.append('../../') + sys.path.append("../../") # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. - version = '\'unknown\'' + version = "'unknown'" # The full version, including alpha/beta/rc tags. - release = '\'unknown\'' + release = "'unknown'" # Check if it matches a pure tag number vX.Y.Z, rather than vX.Y.Z-91-g8676988 which is how # non-tagged commits are described (ie. relative to the last tag) - if re.match(r'^v\d+\.\d+\.\d+$', git_tag): + if re.match(r"^v\d+\.\d+\.\d+$", git_tag): # git_tag is a pure version number (no subsequent commits) version = git_tag release = git_tag documentation_build = "readthedocs" else: - version = '\'latest\'' - release = '\'latest\'' + version = "'latest'" + release = "'latest'" documentation_build = "readthedocs_latest" else: # For our usual dev build we'll be in the 'documentation' directory but Sphinx seems to set the # current working directory to 'source' so we append relative to that - sys.path.append('../../') + sys.path.append("../../") # Check if it matches a pure tag number vX.Y.Z, rather than vX.Y.Z-91-g8676988 which is how # non-tagged commits are described (ie. relative to the last tag) - if re.match(r'^v\d+\.\d+\.\d+$', git_tag): + if re.match(r"^v\d+\.\d+\.\d+$", git_tag): # git_tag is a pure version number (no subsequent commits) version = git_tag release = git_tag else: - version = '\'latest\'' - release = '\'latest\'' + version = "'latest'" + release = "'latest'" documentation_build = "development" # If we're doing a comparison then set the version & release to 'compare' so that they are always # the same otherwise they can come up as changes when we really don't care if they are different. -comparison = os.environ.get('BREATHE_COMPARE', None) == 'True' +comparison = os.environ.get("BREATHE_COMPARE", None) == "True" if comparison: - version = 'compare' - release = 'compare' + version = "compare" + release = "compare" # Only add spelling extension if it is available. We don't know if it is installed as we don't want # to put it in the setup.py file as a dependency as we don't want Breathe to be dependent on it as @@ -109,14 +109,15 @@ # this. try: import sphinxcontrib.spelling - extensions.append('sphinxcontrib.spelling') + + extensions.append("sphinxcontrib.spelling") except ImportError: pass # Configuration for spelling extension -spelling_word_list_filename='spelling_wordlist.txt' -spelling_lang='en_US' +spelling_word_list_filename = "spelling_wordlist.txt" +spelling_lang = "en_US" # Configuration for mathjax extension @@ -124,135 +125,134 @@ # Set path for mathjax js to a https URL as sometimes the Breathe docs are displayed under https # and we can't load an http mathjax file from an https view of the docs. So we change to a https # mathjax file which we can load from http or https. We break the url over two lines. -mathjax_path = 'https://c328740.ssl.cf1.rackcdn.com/' \ - 'mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML' +mathjax_path = ( + "https://c328740.ssl.cf1.rackcdn.com/" "mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" +) # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Breathe' -copyright = u'2009-2014, Michael Jones' +project = u"Breathe" +copyright = u"2009-2014, Michael Jones" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # Options for breathe extension # ----------------------------- breathe_projects = { - "class":"../../examples/doxygen/class/xml/", - "classtest":"../../examples/specific/class/xml/", - "struct":"../../examples/specific/struct/xml/", - "interface":"../../examples/specific/interface/xml/", - "decl_impl":"../../examples/specific/decl_impl/xml/", - "structcmd":"../../examples/doxygen/structcmd/xml/", - "tinyxml":"../../examples/tinyxml/tinyxml/xml/", - "restypedef":"../../examples/doxygen/restypedef/xml/", - "nutshell":"../../examples/specific/nutshell/xml/", - "rst":"../../examples/specific/rst/xml/", - "c_file":"../../examples/specific/c_file/xml/", - "namespace":"../../examples/specific/namespacefile/xml/", - "userdefined":"../../examples/specific/userdefined/xml/", - "template_function":"../../examples/specific/template_function/xml/", - "template_class":"../../examples/specific/template_class/xml/", + "class": "../../examples/doxygen/class/xml/", + "classtest": "../../examples/specific/class/xml/", + "struct": "../../examples/specific/struct/xml/", + "interface": "../../examples/specific/interface/xml/", + "decl_impl": "../../examples/specific/decl_impl/xml/", + "structcmd": "../../examples/doxygen/structcmd/xml/", + "tinyxml": "../../examples/tinyxml/tinyxml/xml/", + "restypedef": "../../examples/doxygen/restypedef/xml/", + "nutshell": "../../examples/specific/nutshell/xml/", + "rst": "../../examples/specific/rst/xml/", + "c_file": "../../examples/specific/c_file/xml/", + "namespace": "../../examples/specific/namespacefile/xml/", + "userdefined": "../../examples/specific/userdefined/xml/", + "template_function": "../../examples/specific/template_function/xml/", + "template_class": "../../examples/specific/template_class/xml/", "template_class_non_type": "../../examples/specific/template_class_non_type/xml/", "template_specialisation": "../../examples/specific/template_specialisation/xml/", - "latexmath":"../../examples/specific/latexmath/xml/", - "functionOverload":"../../examples/specific/functionOverload/xml/", - "programlisting":"../../examples/specific/programlisting/xml/", - "image":"../../examples/specific/image/xml/", - "lists":"../../examples/specific/lists/xml/", - "tables":"../../examples/specific/tables/xml/", - "group":"../../examples/specific/group/xml/", - "union":"../../examples/specific/union/xml/", - "qtsignalsandslots":"../../examples/specific/qtsignalsandslots/xml/", - "array":"../../examples/specific/array/xml/", - "c_struct":"../../examples/specific/c_struct/xml/", - "c_enum":"../../examples/specific/c_enum/xml/", - "c_typedef":"../../examples/specific/c_typedef/xml/", - "c_macro":"../../examples/specific/c_macro/xml/", - "c_union":"../../examples/specific/c_union/xml/", - "define":"../../examples/specific/define/xml/", - "multifile":"../../examples/specific/multifilexml/xml/", - "cpp_anon":"../../examples/specific/cpp_anon/xml/", - "cpp_enum":"../../examples/specific/cpp_enum/xml/", - "cpp_union":"../../examples/specific/cpp_union/xml/", - "cpp_function":"../../examples/specific/cpp_function/xml/", - "cpp_function_lookup":"../../examples/specific/cpp_function_lookup/xml/", - "cpp_friendclass":"../../examples/specific/cpp_friendclass/xml/", - "cpp_inherited_members":"../../examples/specific/cpp_inherited_members/xml/", - "cpp_trailing_return_type":"../../examples/specific/cpp_trailing_return_type/xml/", - "cpp_constexpr_hax":"../../examples/specific/cpp_constexpr_hax/xml/", + "latexmath": "../../examples/specific/latexmath/xml/", + "functionOverload": "../../examples/specific/functionOverload/xml/", + "programlisting": "../../examples/specific/programlisting/xml/", + "image": "../../examples/specific/image/xml/", + "lists": "../../examples/specific/lists/xml/", + "tables": "../../examples/specific/tables/xml/", + "group": "../../examples/specific/group/xml/", + "union": "../../examples/specific/union/xml/", + "qtsignalsandslots": "../../examples/specific/qtsignalsandslots/xml/", + "array": "../../examples/specific/array/xml/", + "c_struct": "../../examples/specific/c_struct/xml/", + "c_enum": "../../examples/specific/c_enum/xml/", + "c_typedef": "../../examples/specific/c_typedef/xml/", + "c_macro": "../../examples/specific/c_macro/xml/", + "c_union": "../../examples/specific/c_union/xml/", + "define": "../../examples/specific/define/xml/", + "multifile": "../../examples/specific/multifilexml/xml/", + "cpp_anon": "../../examples/specific/cpp_anon/xml/", + "cpp_enum": "../../examples/specific/cpp_enum/xml/", + "cpp_union": "../../examples/specific/cpp_union/xml/", + "cpp_function": "../../examples/specific/cpp_function/xml/", + "cpp_function_lookup": "../../examples/specific/cpp_function_lookup/xml/", + "cpp_friendclass": "../../examples/specific/cpp_friendclass/xml/", + "cpp_inherited_members": "../../examples/specific/cpp_inherited_members/xml/", + "cpp_trailing_return_type": "../../examples/specific/cpp_trailing_return_type/xml/", + "cpp_constexpr_hax": "../../examples/specific/cpp_constexpr_hax/xml/", "xrefsect": "../../examples/specific/xrefsect/xml/", "membergroups": "../../examples/specific/membergroups/xml/", - } +} -breathe_projects_source = { - "auto" : ( "../../examples/specific", [ "auto_function.h", "auto_class.h" ] ) - } +breathe_projects_source = {"auto": ("../../examples/specific", ["auto_function.h", "auto_class.h"])} breathe_default_project = "tinyxml" breathe_domain_by_extension = { - "h" : "cpp", - "py": "py", - } + "h": "cpp", + "py": "py", +} breathe_domain_by_file_pattern = { - "class.h" : "cpp", - "alias.h" : "c", - "array.h" : "c", - "c_*.h" : "c", - } + "class.h": "cpp", + "alias.h": "c", + "array.h": "c", + "c_*.h": "c", +} breathe_use_project_refids = True -#breathe_debug_trace_directives = True -#breathe_debug_trace_doxygen_ids = True -#breathe_debug_trace_qualification = True +# breathe_debug_trace_directives = True +# breathe_debug_trace_doxygen_ids = True +# breathe_debug_trace_qualification = True # Options for HTML output @@ -267,96 +267,96 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. -#html_copy_source = True +# html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'BreatheExampledoc' +htmlhelp_basename = "BreatheExampledoc" # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ - ('index', 'BreatheExample.tex', 'BreatheExample Documentation', - 'Michael Jones', 'manual'), + ("index", "BreatheExample.tex", "BreatheExample Documentation", "Michael Jones", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True + def run_doxygen(folder): """Run the doxygen make command in the designated folder""" @@ -372,7 +372,7 @@ def run_doxygen(folder): def generate_doxygen_xml(app): """Run the doxygen make commands if we're on the ReadTheDocs server""" - read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' + read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" if read_the_docs_build: @@ -388,14 +388,13 @@ def setup(app): # Approach borrowed from the Sphinx docs app.add_object_type( - 'confval', - 'confval', - objname='configuration value', - indextemplate='pair: %s; configuration value' - ) + "confval", + "confval", + objname="configuration value", + indextemplate="pair: %s; configuration value", + ) # Add hook for building doxygen xml when needed app.connect("builder-inited", generate_doxygen_xml) - app.add_config_value('documentation_build', 'development', True) - + app.add_config_value("documentation_build", "development", True) diff --git a/examples/doxygen/docstring.py b/examples/doxygen/docstring.py index 07b13e01b..7972e3d24 100644 --- a/examples/doxygen/docstring.py +++ b/examples/doxygen/docstring.py @@ -4,6 +4,7 @@ More details. """ + def func(): """Documentation for a function. @@ -11,17 +12,17 @@ def func(): """ pass + class PyClass: """Documentation for a class. More details. """ - + def __init__(self): """The constructor.""" - self._memVar = 0; - + self._memVar = 0 + def PyMethod(self): """Documentation for a method.""" pass - diff --git a/examples/doxygen/pyexample.py b/examples/doxygen/pyexample.py index a75cc60ec..34c09925d 100644 --- a/examples/doxygen/pyexample.py +++ b/examples/doxygen/pyexample.py @@ -9,20 +9,21 @@ def func(): pass + ## Documentation for a class. # # More details. class PyClass: - + ## The constructor. def __init__(self): - self._memVar = 0; - + self._memVar = 0 + ## Documentation for a method. # @param self The object pointer. def PyMethod(self): pass - + ## A class variable. classVar = 0 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..15f421bb2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[tool.black] +line-length = 100 +extend-exclude = ''' +^/breathe/parser/.* +''' diff --git a/requirements/development.txt b/requirements/development.txt index 5daec922d..69363b23f 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -6,3 +6,5 @@ pytest mypy>=0.900 types-docutils>=0.17.0 + +black diff --git a/setup.cfg b/setup.cfg index e2af17b50..35e4a561f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,6 +4,7 @@ license_file = LICENSE [flake8] max-line-length = 100 exclude=compoundsuper.py,indexsuper.py +extend-ignore = E203, E231 [bdist_wheel] universal = 0 diff --git a/setup.py b/setup.py index 045bcd5aa..abc02998d 100644 --- a/setup.py +++ b/setup.py @@ -3,54 +3,55 @@ from setuptools import setup, find_packages except ImportError: import distribute_setup + distribute_setup.use_setuptools() from setuptools import setup, find_packages import sys from breathe import __version__ -long_desc = ''' +long_desc = """ Breathe is an extension to reStructuredText and Sphinx to be able to read and render `Doxygen `__ xml output. -''' +""" -requires = ['Sphinx>=3.0,<5', 'docutils>=0.12'] +requires = ["Sphinx>=3.0,<5", "docutils>=0.12"] if sys.version_info < (3, 6): - print('ERROR: Sphinx requires at least Python 3.6 to run.') + print("ERROR: Sphinx requires at least Python 3.6 to run.") sys.exit(1) setup( - name='breathe', + name="breathe", version=__version__, - url='https://github.com/michaeljones/breathe', - download_url='https://github.com/michaeljones/breathe', - license='BSD', - author='Michael Jones', - author_email='m.pricejones@gmail.com', - description='Sphinx Doxygen renderer', + url="https://github.com/michaeljones/breathe", + download_url="https://github.com/michaeljones/breathe", + license="BSD", + author="Michael Jones", + author_email="m.pricejones@gmail.com", + description="Sphinx Doxygen renderer", long_description=long_desc, zip_safe=False, classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Topic :: Documentation', - 'Topic :: Text Processing', - 'Topic :: Utilities', + "Development Status :: 4 - Beta", + "Environment :: Console", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Topic :: Documentation", + "Topic :: Text Processing", + "Topic :: Utilities", ], - platforms='any', + platforms="any", packages=find_packages(), include_package_data=True, entry_points={ - 'console_scripts': [ - 'breathe-apidoc = breathe.apidoc:main', + "console_scripts": [ + "breathe-apidoc = breathe.apidoc:main", ], }, install_requires=requires, diff --git a/tests/test_renderer.py b/tests/test_renderer.py index cf6ab7ff6..c349867c8 100644 --- a/tests/test_renderer.py +++ b/tests/test_renderer.py @@ -4,43 +4,51 @@ import sphinx.addnodes import sphinx.environment from breathe.parser.compound import ( - compounddefTypeSub, linkedTextTypeSub, memberdefTypeSub, paramTypeSub, - refTypeSub, MixedContainer + compounddefTypeSub, + linkedTextTypeSub, + memberdefTypeSub, + paramTypeSub, + refTypeSub, + MixedContainer, ) from breathe.renderer.sphinxrenderer import SphinxRenderer from breathe.renderer.filter import OpenFilter from docutils import frontend, nodes, parsers, utils from sphinx.testing.fixtures import ( - test_params, app_params, make_app, shared_result, - sphinx_test_tempdir, rootdir + test_params, + app_params, + make_app, + shared_result, + sphinx_test_tempdir, + rootdir, ) from sphinx.testing.path import path -sphinx.locale.init([], '') +sphinx.locale.init([], "") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def app(test_params, app_params, make_app, shared_result): """ Based on sphinx.testing.fixtures.app """ args, kwargs = app_params - assert 'srcdir' in kwargs - kwargs['srcdir'].makedirs(exist_ok=True) - (kwargs['srcdir'] / 'conf.py').write_text('') + assert "srcdir" in kwargs + kwargs["srcdir"].makedirs(exist_ok=True) + (kwargs["srcdir"] / "conf.py").write_text("") app_ = make_app(*args, **kwargs) yield app_ - print('# testroot:', kwargs.get('testroot', 'root')) - print('# builder:', app_.builder.name) - print('# srcdir:', app_.srcdir) - print('# outdir:', app_.outdir) - print('# status:', '\n' + app_._status.getvalue()) - print('# warning:', '\n' + app_._warning.getvalue()) + print("# testroot:", kwargs.get("testroot", "root")) + print("# builder:", app_.builder.name) + print("# srcdir:", app_.srcdir) + print("# outdir:", app_.outdir) + print("# status:", "\n" + app_._status.getvalue()) + print("# warning:", "\n" + app_._warning.getvalue()) - if test_params['shared_result']: - shared_result.store(test_params['shared_result'], app_) + if test_params["shared_result"]: + shared_result.store(test_params["shared_result"], app_) class WrappedDoxygenNode: @@ -48,17 +56,19 @@ class WrappedDoxygenNode: A base class for test wrappers of Doxygen nodes. It allows setting all attributes via keyword arguments in the constructor. """ + def __init__(self, cls, *args, **kwargs): if cls: cls.__init__(self, args) for name, value in kwargs.items(): if not hasattr(self, name): - raise AttributeError('invalid attribute ' + name) + raise AttributeError("invalid attribute " + name) setattr(self, name, value) class WrappedMixedContainer(MixedContainer, WrappedDoxygenNode): """A test wrapper of Doxygen mixed container.""" + def __init__(self, **kwargs): MixedContainer.__init__(self, None, None, None, None) WrappedDoxygenNode.__init__(self, None, **kwargs) @@ -66,30 +76,35 @@ def __init__(self, **kwargs): class WrappedLinkedText(linkedTextTypeSub, WrappedDoxygenNode): """A test wrapper of Doxygen linked text.""" + def __init__(self, **kwargs): WrappedDoxygenNode.__init__(self, linkedTextTypeSub, **kwargs) class WrappedMemberDef(memberdefTypeSub, WrappedDoxygenNode): """A test wrapper of Doxygen class/file/namespace member symbol such as a function declaration.""" + def __init__(self, **kwargs): WrappedDoxygenNode.__init__(self, memberdefTypeSub, **kwargs) class WrappedParam(paramTypeSub, WrappedDoxygenNode): """A test wrapper of Doxygen parameter.""" + def __init__(self, **kwargs): WrappedDoxygenNode.__init__(self, paramTypeSub, **kwargs) class WrappedRef(refTypeSub, WrappedDoxygenNode): """A test wrapper of Doxygen ref.""" + def __init__(self, node_name, **kwargs): WrappedDoxygenNode.__init__(self, refTypeSub, node_name, **kwargs) class WrappedCompoundDef(compounddefTypeSub, WrappedDoxygenNode): """A test wrapper of Doxygen compound definition.""" + def __init__(self, **kwargs): WrappedDoxygenNode.__init__(self, compounddefTypeSub, **kwargs) @@ -98,11 +113,10 @@ class MockState: def __init__(self, app): env = sphinx.environment.BuildEnvironment(app) env.setup(app) - env.temp_data['docname'] = 'mock-doc' - settings = frontend.OptionParser( - components=(parsers.rst.Parser,)).get_default_values() + env.temp_data["docname"] = "mock-doc" + settings = frontend.OptionParser(components=(parsers.rst.Parser,)).get_default_values() settings.env = env - self.document = utils.new_document('', settings) + self.document = utils.new_document("", settings) def nested_parse(self, content, content_offset, contentnode): pass @@ -126,7 +140,7 @@ def __init__(self): def get_source_and_line(self, lineno: int): if lineno is None: lineno = 42 - return 'mock-doc', lineno + return "mock-doc", lineno class MockMaskFactory: @@ -142,14 +156,16 @@ def __init__(self, app, node_stack, domain=None, options=[]): self.domain = domain self.node_stack = node_stack self.directive_args = [ - None, # name - None, # arguments + None, # name + None, # arguments options, # options - None, # content - None, # lineno - None, # content_offset - None, # block_text - MockState(app), MockStateMachine()] + None, # content + None, # lineno + None, # content_offset + None, # block_text + MockState(app), + MockStateMachine(), + ] self.child = None self.mask_factory = MockMaskFactory() @@ -175,6 +191,7 @@ class MockCompoundParser: A compound parser reads a doxygen XML file from disk; this mock implements a mapping of what would be the file name on disk to data using a dict. """ + def __init__(self, compound_dict): self.compound_dict = compound_dict @@ -189,6 +206,7 @@ def parse(self, compoundname): class NodeFinder(nodes.NodeVisitor): """Find node with specified class name.""" + def __init__(self, name, document): nodes.NodeVisitor.__init__(self, document) self.name = name @@ -214,21 +232,21 @@ def find_node(nodes, name): """ found_nodes = find_nodes(nodes, name) if len(found_nodes) != 1: - raise Exception('the number of nodes {0} is {1}'.format(name, len(found_nodes))) + raise Exception("the number of nodes {0} is {1}".format(name, len(found_nodes))) return found_nodes[0] def test_find_nodes(): section = nodes.section() - foo = nodes.Text('foo') + foo = nodes.Text("foo") desc = nodes.description() - bar = nodes.Text('bar') + bar = nodes.Text("bar") section.children = [foo, desc, bar] - assert(find_nodes(section, 'description') == [desc]) - assert(find_nodes([section, desc], 'description') == [desc, desc]) - assert(find_nodes([], 'description') == []) - assert(find_nodes(section, 'unknown') == []) - assert(find_nodes(section, 'Text') == [foo, bar]) + assert find_nodes(section, "description") == [desc] + assert find_nodes([section, desc], "description") == [desc, desc] + assert find_nodes([], "description") == [] + assert find_nodes(section, "unknown") == [] + assert find_nodes(section, "Text") == [foo, bar] def check_exception(func, message): @@ -244,23 +262,22 @@ def check_exception(func, message): def test_find_node(): section = nodes.section() - foo = nodes.Text('foo') + foo = nodes.Text("foo") desc = nodes.description() - bar = nodes.Text('bar') + bar = nodes.Text("bar") section.children = [foo, desc, bar] - assert(find_node(section, 'description') == desc) - check_exception(lambda: find_node([section, desc], 'description'), - 'the number of nodes description is 2') - check_exception(lambda: find_node([], 'description'), - 'the number of nodes description is 0') - check_exception(lambda: find_node([section], 'unknown'), - 'the number of nodes unknown is 0') - check_exception(lambda: find_node([section], 'Text'), - 'the number of nodes Text is 2') - - -def render(app, member_def, domain=None, show_define_initializer=False, - compound_parser=None, options=[]): + assert find_node(section, "description") == desc + check_exception( + lambda: find_node([section, desc], "description"), "the number of nodes description is 2" + ) + check_exception(lambda: find_node([], "description"), "the number of nodes description is 0") + check_exception(lambda: find_node([section], "unknown"), "the number of nodes unknown is 0") + check_exception(lambda: find_node([section], "Text"), "the number of nodes Text is 2") + + +def render( + app, member_def, domain=None, show_define_initializer=False, compound_parser=None, options=[] +): """Render Doxygen *member_def* with *renderer_class*.""" app.config.breathe_separate_member_pages = False @@ -270,60 +287,79 @@ def render(app, member_def, domain=None, show_define_initializer=False, app.config.breathe_debug_trace_directives = False app.config.breathe_debug_trace_doxygen_ids = False app.config.breathe_debug_trace_qualification = False - renderer = SphinxRenderer(app, - None, # project_info - [], # node_stack - None, # state - None, # document - MockTargetHandler(), - compound_parser, - OpenFilter()) + renderer = SphinxRenderer( + app, + None, # project_info + [], # node_stack + None, # state + None, # document + MockTargetHandler(), + compound_parser, + OpenFilter(), + ) renderer.context = MockContext(app, [member_def], domain, options) return renderer.render(member_def) def test_render_func(app): - member_def = WrappedMemberDef(kind='function', definition='void foo', type_='void', name='foo', argsstring='(int)', - virt='non-virtual', - param=[WrappedParam(type_=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'int')]))]) - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext().startswith('void') + member_def = WrappedMemberDef( + kind="function", + definition="void foo", + type_="void", + name="foo", + argsstring="(int)", + virt="non-virtual", + param=[ + WrappedParam(type_=WrappedLinkedText(content_=[WrappedMixedContainer(value=u"int")])) + ], + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext().startswith("void") if sphinx.version_info[0] < 4: - assert find_node(signature, 'desc_name')[0] == 'foo' + assert find_node(signature, "desc_name")[0] == "foo" else: - n = find_node(signature, 'desc_name')[0] + n = find_node(signature, "desc_name")[0] assert isinstance(n, sphinx.addnodes.desc_sig_name) assert len(n) == 1 - assert n[0] == 'foo' - params = find_node(signature, 'desc_parameterlist') + assert n[0] == "foo" + params = find_node(signature, "desc_parameterlist") assert len(params) == 1 param = params[0] if sphinx.version_info[0] < 4: - assert param[0] == 'int' + assert param[0] == "int" else: assert isinstance(param[0], sphinx.addnodes.desc_sig_keyword_type) - assert param[0][0] == 'int' + assert param[0][0] == "int" def test_render_typedef(app): - member_def = WrappedMemberDef(kind='typedef', definition='typedef int foo', type_='int', name='foo') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext() == 'typedef int foo' + member_def = WrappedMemberDef( + kind="typedef", definition="typedef int foo", type_="int", name="foo" + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext() == "typedef int foo" def test_render_c_typedef(app): - member_def = WrappedMemberDef(kind='typedef', definition='typedef unsigned int bar', type_='unsigned int', name='bar') - signature = find_node(render(app, member_def, domain='c'), 'desc_signature') - assert signature.astext() == 'typedef unsigned int bar' + member_def = WrappedMemberDef( + kind="typedef", definition="typedef unsigned int bar", type_="unsigned int", name="bar" + ) + signature = find_node(render(app, member_def, domain="c"), "desc_signature") + assert signature.astext() == "typedef unsigned int bar" def test_render_c_function_typedef(app): - member_def = WrappedMemberDef(kind='typedef', definition='typedef void* (*voidFuncPtr)(float, int)', - type_='void* (*', name='voidFuncPtr', argsstring=')(float, int)') - signature = find_node(render(app, member_def, domain='c'), 'desc_signature') - assert signature.astext().startswith('typedef void *') + member_def = WrappedMemberDef( + kind="typedef", + definition="typedef void* (*voidFuncPtr)(float, int)", + type_="void* (*", + name="voidFuncPtr", + argsstring=")(float, int)", + ) + signature = find_node(render(app, member_def, domain="c"), "desc_signature") + assert signature.astext().startswith("typedef void *") if sphinx.version_info[0] < 4: - params = find_node(signature, 'desc_parameterlist') + params = find_node(signature, "desc_parameterlist") assert len(params) == 2 assert params[0].astext() == "float" assert params[1].astext() == "int" @@ -334,92 +370,149 @@ def test_render_c_function_typedef(app): def test_render_using_alias(app): - member_def = WrappedMemberDef(kind='typedef', definition='using foo = int', type_='int', name='foo') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext() == 'using foo = int' + member_def = WrappedMemberDef( + kind="typedef", definition="using foo = int", type_="int", name="foo" + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext() == "using foo = int" def test_render_const_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() const', - virt='non-virtual', const='yes') - signature = find_node(render(app, member_def), 'desc_signature') - assert '_CPPv2NK1fEv' in signature['ids'] + member_def = WrappedMemberDef( + kind="function", + definition="void f", + type_="void", + name="f", + argsstring="() const", + virt="non-virtual", + const="yes", + ) + signature = find_node(render(app, member_def), "desc_signature") + assert "_CPPv2NK1fEv" in signature["ids"] def test_render_lvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() &', - virt='non-virtual', refqual='lvalue') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext().endswith('&') + member_def = WrappedMemberDef( + kind="function", + definition="void f", + type_="void", + name="f", + argsstring="() &", + virt="non-virtual", + refqual="lvalue", + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext().endswith("&") def test_render_rvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() &&', - virt='non-virtual', refqual='rvalue') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext().endswith('&&') + member_def = WrappedMemberDef( + kind="function", + definition="void f", + type_="void", + name="f", + argsstring="() &&", + virt="non-virtual", + refqual="rvalue", + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext().endswith("&&") def test_render_const_lvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f',argsstring='() const &', - virt='non-virtual', const='yes', refqual='lvalue') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext().endswith('const &') + member_def = WrappedMemberDef( + kind="function", + definition="void f", + type_="void", + name="f", + argsstring="() const &", + virt="non-virtual", + const="yes", + refqual="lvalue", + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext().endswith("const &") def test_render_const_rvalue_func(app): - member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() const &&', - virt='non-virtual', const='yes', refqual='rvalue') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext().endswith('const &&') + member_def = WrappedMemberDef( + kind="function", + definition="void f", + type_="void", + name="f", + argsstring="() const &&", + virt="non-virtual", + const="yes", + refqual="rvalue", + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext().endswith("const &&") def test_render_variable_initializer(app): - member_def = WrappedMemberDef(kind='variable', definition='const int EOF', type_='const int', name='EOF', - initializer=WrappedMixedContainer(value=u'= -1')) - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext() == 'const int EOF = -1' + member_def = WrappedMemberDef( + kind="variable", + definition="const int EOF", + type_="const int", + name="EOF", + initializer=WrappedMixedContainer(value=u"= -1"), + ) + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext() == "const int EOF = -1" def test_render_define_initializer(app): - member_def = WrappedMemberDef(kind='define', name='MAX_LENGTH', - initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'100')])) - signature_w_initializer = find_node(render(app, member_def, show_define_initializer=True), 'desc_signature') - assert signature_w_initializer.astext() == 'MAX_LENGTH 100' - - member_def_no_show = WrappedMemberDef(kind='define', name='MAX_LENGTH_NO_INITIALIZER', - initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'100')])) - - signature_wo_initializer = find_node(render(app, member_def_no_show, show_define_initializer=False), 'desc_signature') - assert signature_wo_initializer.astext() == 'MAX_LENGTH_NO_INITIALIZER' + member_def = WrappedMemberDef( + kind="define", + name="MAX_LENGTH", + initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u"100")]), + ) + signature_w_initializer = find_node( + render(app, member_def, show_define_initializer=True), "desc_signature" + ) + assert signature_w_initializer.astext() == "MAX_LENGTH 100" + + member_def_no_show = WrappedMemberDef( + kind="define", + name="MAX_LENGTH_NO_INITIALIZER", + initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u"100")]), + ) + + signature_wo_initializer = find_node( + render(app, member_def_no_show, show_define_initializer=False), "desc_signature" + ) + assert signature_wo_initializer.astext() == "MAX_LENGTH_NO_INITIALIZER" def test_render_define_no_initializer(app): sphinx.addnodes.setup(app) - member_def = WrappedMemberDef(kind='define', name='USE_MILK') - signature = find_node(render(app, member_def), 'desc_signature') - assert signature.astext() == 'USE_MILK' + member_def = WrappedMemberDef(kind="define", name="USE_MILK") + signature = find_node(render(app, member_def), "desc_signature") + assert signature.astext() == "USE_MILK" def test_render_innergroup(app): - refid = 'group__innergroup' - mock_compound_parser = MockCompoundParser({ - refid: WrappedCompoundDef(kind='group', - compoundname='InnerGroup', - briefdescription='InnerGroup') - }) - ref = WrappedRef('InnerGroup', refid=refid) - compound_def = WrappedCompoundDef(kind='group', - compoundname='OuterGroup', - briefdescription='OuterGroup', - innergroup=[ref]) - assert all(el.astext() != 'InnerGroup' - for el in render(app, compound_def, - compound_parser=mock_compound_parser)) - assert any(el.astext() == 'InnerGroup' - for el in render(app, compound_def, - compound_parser=mock_compound_parser, - options=['inner'])) + refid = "group__innergroup" + mock_compound_parser = MockCompoundParser( + { + refid: WrappedCompoundDef( + kind="group", compoundname="InnerGroup", briefdescription="InnerGroup" + ) + } + ) + ref = WrappedRef("InnerGroup", refid=refid) + compound_def = WrappedCompoundDef( + kind="group", compoundname="OuterGroup", briefdescription="OuterGroup", innergroup=[ref] + ) + assert all( + el.astext() != "InnerGroup" + for el in render(app, compound_def, compound_parser=mock_compound_parser) + ) + assert any( + el.astext() == "InnerGroup" + for el in render(app, compound_def, compound_parser=mock_compound_parser, options=["inner"]) + ) + def get_directive(app): from breathe.directives.function import DoxygenFunctionDirective @@ -427,23 +520,29 @@ def get_directive(app): from breathe.parser import DoxygenParserFactory from breathe.finder.factory import FinderFactory from docutils.statemachine import StringList + app.config.breathe_separate_member_pages = False - app.config.breathe_default_project = 'test_project' + app.config.breathe_default_project = "test_project" app.config.breathe_domain_by_extension = {} app.config.breathe_domain_by_file_pattern = {} app.config.breathe_use_project_refids = False project_info_factory = ProjectInfoFactory(app) parser_factory = DoxygenParserFactory(app) finder_factory = FinderFactory(app, parser_factory) - cls_args = ('doxygenclass', ['at::Tensor'], - {'members': '', 'protected-members': None, 'undoc-members': None}, - StringList([], items=[]), - 20, 24, - ('.. doxygenclass:: at::Tensor\n :members:\n' - ' :protected-members:\n :undoc-members:'), - MockState(app), - MockStateMachine(), - ) + cls_args = ( + "doxygenclass", + ["at::Tensor"], + {"members": "", "protected-members": None, "undoc-members": None}, + StringList([], items=[]), + 20, + 24, + ( + ".. doxygenclass:: at::Tensor\n :members:\n" + " :protected-members:\n :undoc-members:" + ), + MockState(app), + MockStateMachine(), + ) return DoxygenFunctionDirective(finder_factory, project_info_factory, parser_factory, *cls_args) @@ -452,23 +551,23 @@ def get_matches(datafile): from xml.dom import minidom argsstrings = [] - with open(os.path.join(os.path.dirname(__file__), 'data', datafile)) as fid: + with open(os.path.join(os.path.dirname(__file__), "data", datafile)) as fid: xml = fid.read() doc = minidom.parseString(xml) sectiondef = sectiondefType.factory() for child in doc.documentElement.childNodes: - sectiondef.buildChildren(child, 'memberdef') - if getattr(child, 'tagName', None) == 'memberdef': + sectiondef.buildChildren(child, "memberdef") + if getattr(child, "tagName", None) == "memberdef": # Get the argsstring function declaration - argsstrings.append(child.getElementsByTagName('argsstring')[0].childNodes[0].data) + argsstrings.append(child.getElementsByTagName("argsstring")[0].childNodes[0].data) matches = [[m, sectiondef] for m in sectiondef.memberdef] return argsstrings, matches def test_resolve_overrides(app): # Test that multiple function overrides works - argsstrings, matches = get_matches('arange.xml') + argsstrings, matches = get_matches("arange.xml") cls = get_directive(app) # Verify that the exact arguments returns one override @@ -476,11 +575,11 @@ def test_resolve_overrides(app): ast_param = cls._parse_args(args) ret = cls._resolve_function(matches, ast_param, None) + def test_ellipsis(app): - argsstrings, matches = get_matches('ellipsis.xml') + argsstrings, matches = get_matches("ellipsis.xml") cls = get_directive(app) # Verify that parsing an ellipsis works ast_param = cls._parse_args(argsstrings[0]) ret = cls._resolve_function(matches, ast_param, None) - diff --git a/tests/test_utils.py b/tests/test_utils.py index 7fba8e1ce..3096d45f5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,3 @@ - from unittest import TestCase from xml.dom import minidom # type: ignore @@ -6,8 +5,8 @@ from breathe.parser.compoundsuper import memberdefType from breathe import path_handler -class TestUtils(TestCase): +class TestUtils(TestCase): def test_param_decl(self): # From xml from: examples/specific/parameters.h @@ -50,41 +49,38 @@ def test_param_decl(self): memberdef = memberdefType.factory() for child in doc.documentElement.childNodes: - memberdef.buildChildren(child, 'param') + memberdef.buildChildren(child, "param") - self.assertEqual(get_param_decl(memberdef.param[0]), 'int a') - self.assertEqual(get_param_decl(memberdef.param[1]), 'float b') - self.assertEqual(get_param_decl(memberdef.param[2]), 'int * c') - self.assertEqual(get_param_decl(memberdef.param[3]), 'int(**p)[3]') - self.assertEqual(get_param_decl(memberdef.param[4]), 'MyClass a') - self.assertEqual(get_param_decl(memberdef.param[5]), 'MyClass * b') - self.assertEqual(get_param_decl(memberdef.param[6]), 'int(&r)[3]') + self.assertEqual(get_param_decl(memberdef.param[0]), "int a") + self.assertEqual(get_param_decl(memberdef.param[1]), "float b") + self.assertEqual(get_param_decl(memberdef.param[2]), "int * c") + self.assertEqual(get_param_decl(memberdef.param[3]), "int(**p)[3]") + self.assertEqual(get_param_decl(memberdef.param[4]), "MyClass a") + self.assertEqual(get_param_decl(memberdef.param[5]), "MyClass * b") + self.assertEqual(get_param_decl(memberdef.param[6]), "int(&r)[3]") def test_definition_without_template_args(self): - - def get_definition(definition, name, bitfield=''): + def get_definition(definition, name, bitfield=""): class MockDataObject: def __init__(self, definition, name, bitfield): self.definition = definition self.name = name self.bitfield = bitfield - return get_definition_without_template_args( - MockDataObject(definition, name, bitfield) - ) + return get_definition_without_template_args(MockDataObject(definition, name, bitfield)) - self.assertEqual('void A::foo', get_definition('void A::foo', 'foo')) + self.assertEqual("void A::foo", get_definition("void A::foo", "foo")) # Template arguments in the return type should be preserved: - self.assertEqual('Result A::f', get_definition('Result A::f', 'f')) + self.assertEqual("Result A::f", get_definition("Result A::f", "f")) # Nested template arguments: - self.assertEqual('Result A::f', get_definition('Result A< B >::f', 'f')) + self.assertEqual("Result A::f", get_definition("Result A< B >::f", "f")) # Bit fields - self.assertEqual('int f : 3', get_definition('int f', 'f', '3')) + self.assertEqual("int f : 3", get_definition("int f", "f", "3")) class TestPathHandler(TestCase): def test_path_handler(self): - self.assertEqual(path_handler.includes_directory('directory/file.h'), True) - self.assertEqual(path_handler.includes_directory('directory\\file.h'), True) - self.assertEqual(path_handler.includes_directory('file.h'), False) + self.assertEqual(path_handler.includes_directory("directory/file.h"), True) + self.assertEqual(path_handler.includes_directory("directory\\file.h"), True) + self.assertEqual(path_handler.includes_directory("file.h"), False) diff --git a/tests/warnings/source/conf.py b/tests/warnings/source/conf.py index a282b7579..8c356c372 100644 --- a/tests/warnings/source/conf.py +++ b/tests/warnings/source/conf.py @@ -20,62 +20,62 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' -sys.path.append('../../') +sys.path.append("../../") # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['breathe'] +extensions = ["breathe"] # Breathe configuration parameters breathe_projects = { - "class" : "../../../examples/doxygen/class/xml/", - "function" : "../../../examples/specific/functionOverload/xml/", - "group" : "../../../examples/specific/group/xml/", - "invalidproject" : "invalid/path/", - } + "class": "../../../examples/doxygen/class/xml/", + "function": "../../../examples/specific/functionOverload/xml/", + "group": "../../../examples/specific/group/xml/", + "invalidproject": "invalid/path/", +} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Test Breathe Warnings' -copyright = u'2014, Michael Jones' +project = u"Test Breathe Warnings" +copyright = u"2014, Michael Jones" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '1.0' +version = "1.0" # The full version, including alpha/beta/rc tags. -release = '1.0' +release = "1.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -83,154 +83,157 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'TestBreatheWarningsdoc' +htmlhelp_basename = "TestBreatheWarningsdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'TestBreatheWarnings.tex', u'Test Breathe Warnings Documentation', - u'Michael Jones', 'manual'), + ( + "index", + "TestBreatheWarnings.tex", + u"Test Breathe Warnings Documentation", + u"Michael Jones", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -238,12 +241,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'testbreathewarnings', u'Test Breathe Warnings Documentation', - [u'Michael Jones'], 1) + ("index", "testbreathewarnings", u"Test Breathe Warnings Documentation", [u"Michael Jones"], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -252,19 +254,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'TestBreatheWarnings', u'Test Breathe Warnings Documentation', - u'Michael Jones', 'TestBreatheWarnings', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "TestBreatheWarnings", + u"Test Breathe Warnings Documentation", + u"Michael Jones", + "TestBreatheWarnings", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False