Make support/python/mkdocstrings_handlers/cxx/__init__.py PEP 8 compliant (2 of 2) (#4115)

* Change indents to 4 spaces.

* Run isort.

* Remove one extra space on the left hand side of each assignment at p.communicate's input.

* Remove 'from __future__ import annotations'.
This requires changing a 'list[]' into a 'List[]'.

We had previously added this import because the code was making use of operator '|'.
But that is no longer true, so the import shouldn't be needed.
This commit is contained in:
Roberto Turrado Camblor 2024-08-11 16:24:50 +02:00 committed by GitHub
parent 50a8c3e9bf
commit c71d03fcb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,339 +2,337 @@
# Copyright (c) 2012 - present, Victor Zverovich # Copyright (c) 2012 - present, Victor Zverovich
# https://github.com/fmtlib/fmt/blob/master/LICENSE # https://github.com/fmtlib/fmt/blob/master/LICENSE
from __future__ import annotations
import os import os
from pathlib import Path
from typing import Any, List, Mapping, Optional
from subprocess import CalledProcessError, PIPE, Popen, STDOUT
import xml.etree.ElementTree as ElementTree import xml.etree.ElementTree as ElementTree
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
from typing import Any, List, Mapping, Optional
from mkdocstrings.handlers.base import BaseHandler from mkdocstrings.handlers.base import BaseHandler
class Definition: class Definition:
"""A definition extracted by Doxygen.""" """A definition extracted by Doxygen."""
def __init__(self, name: str, kind: Optional[str] = None, def __init__(self, name: str, kind: Optional[str] = None,
node: Optional[ElementTree.Element] = None, node: Optional[ElementTree.Element] = None,
is_member: bool = False): is_member: bool = False):
self.name = name self.name = name
self.kind = kind if kind is not None else node.get('kind') self.kind = kind if kind is not None else node.get('kind')
self.desc = None self.desc = None
self.id = name if not is_member else None self.id = name if not is_member else None
self.members = None self.members = None
self.params = None self.params = None
self.template_params = None self.template_params = None
self.trailing_return_type = None self.trailing_return_type = None
self.type = None self.type = None
# A map from Doxygen to HTML tags. # A map from Doxygen to HTML tags.
tag_map = { tag_map = {
'bold': 'b', 'bold': 'b',
'emphasis': 'em', 'emphasis': 'em',
'computeroutput': 'code', 'computeroutput': 'code',
'para': 'p', 'para': 'p',
'programlisting': 'pre', 'programlisting': 'pre',
'verbatim': 'pre' 'verbatim': 'pre'
} }
# A map from Doxygen tags to text. # A map from Doxygen tags to text.
tag_text_map = { tag_text_map = {
'codeline': '', 'codeline': '',
'highlight': '', 'highlight': '',
'sp': ' ' 'sp': ' '
} }
def escape_html(s: str) -> str: def escape_html(s: str) -> str:
return s.replace("<", "&lt;") return s.replace("<", "&lt;")
def doxyxml2html(nodes: List[ElementTree.Element]): def doxyxml2html(nodes: List[ElementTree.Element]):
out = '' out = ''
for n in nodes: for n in nodes:
tag = tag_map.get(n.tag) tag = tag_map.get(n.tag)
if not tag: if not tag:
out += tag_text_map[n.tag] out += tag_text_map[n.tag]
out += '<' + tag + '>' if tag else '' out += '<' + tag + '>' if tag else ''
out += '<code class="language-cpp">' if tag == 'pre' else '' out += '<code class="language-cpp">' if tag == 'pre' else ''
if n.text: if n.text:
out += escape_html(n.text) out += escape_html(n.text)
out += doxyxml2html(list(n)) out += doxyxml2html(list(n))
out += '</code>' if tag == 'pre' else '' out += '</code>' if tag == 'pre' else ''
out += '</' + tag + '>' if tag else '' out += '</' + tag + '>' if tag else ''
if n.tail: if n.tail:
out += n.tail out += n.tail
return out return out
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]: def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
template_param_list = node.find('templateparamlist') template_param_list = node.find('templateparamlist')
if template_param_list is None: if template_param_list is None:
return None return None
params = [] params = []
for param_node in template_param_list.findall('param'): for param_node in template_param_list.findall('param'):
name = param_node.find('declname') name = param_node.find('declname')
param = Definition(name.text if name is not None else '', 'param') param = Definition(name.text if name is not None else '', 'param')
param.type = param_node.find('type').text param.type = param_node.find('type').text
params.append(param) params.append(param)
return params return params
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
return node.findall('briefdescription/para') + \ return node.findall('briefdescription/para') + \
node.findall('detaileddescription/para') node.findall('detaileddescription/para')
def normalize_type(type_: str) -> str: def normalize_type(type_: str) -> str:
type_ = type_.replace('< ', '<').replace(' >', '>') type_ = type_.replace('< ', '<').replace(' >', '>')
return type_.replace(' &', '&').replace(' *', '*') return type_.replace(' &', '&').replace(' *', '*')
def convert_type(type_: ElementTree.Element) -> Optional[str]: def convert_type(type_: ElementTree.Element) -> Optional[str]:
if type_ is None: if type_ is None:
return None return None
result = type_.text if type_.text else '' result = type_.text if type_.text else ''
for ref in type_: for ref in type_:
result += ref.text result += ref.text
if ref.tail: if ref.tail:
result += ref.tail result += ref.tail
result += type_.tail.strip() result += type_.tail.strip()
return normalize_type(result) return normalize_type(result)
def convert_params(func: ElementTree.Element) -> list[Definition]: def convert_params(func: ElementTree.Element) -> List[Definition]:
params = [] params = []
for p in func.findall('param'): for p in func.findall('param'):
d = Definition(p.find('declname').text, 'param') d = Definition(p.find('declname').text, 'param')
d.type = convert_type(p.find('type')) d.type = convert_type(p.find('type'))
params.append(d) params.append(d)
return params return params
def convert_return_type(d: Definition, node: ElementTree.Element) -> None: def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
d.trailing_return_type = None d.trailing_return_type = None
if d.type == 'auto' or d.type == 'constexpr auto': if d.type == 'auto' or d.type == 'constexpr auto':
parts = node.find('argsstring').text.split(' -> ') parts = node.find('argsstring').text.split(' -> ')
if len(parts) > 1: if len(parts) > 1:
d.trailing_return_type = normalize_type(parts[1]) d.trailing_return_type = normalize_type(parts[1])
def render_param(param: Definition) -> str: def render_param(param: Definition) -> str:
return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '') return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '')
def render_decl(d: Definition) -> str: def render_decl(d: Definition) -> str:
text = '' text = ''
if d.id is not None: if d.id is not None:
text += f'<a id="{d.id}">\n' text += f'<a id="{d.id}">\n'
text += '<pre><code class="language-cpp decl">' text += '<pre><code class="language-cpp decl">'
text += '<div>' text += '<div>'
if d.template_params is not None: if d.template_params is not None:
text += 'template &lt;' text += 'template &lt;'
text += ', '.join([render_param(p) for p in d.template_params]) text += ', '.join([render_param(p) for p in d.template_params])
text += '&gt;\n' text += '&gt;\n'
text += '</div>' text += '</div>'
text += '<div>' text += '<div>'
end = ';' end = ';'
if d.kind == 'function' or d.kind == 'variable': if d.kind == 'function' or d.kind == 'variable':
text += d.type + ' ' if len(d.type) > 0 else '' text += d.type + ' ' if len(d.type) > 0 else ''
elif d.kind == 'typedef': elif d.kind == 'typedef':
text += 'using ' text += 'using '
elif d.kind == 'define': elif d.kind == 'define':
end = '' end = ''
else: else:
text += d.kind + ' ' text += d.kind + ' '
text += d.name text += d.name
if d.params is not None: if d.params is not None:
params = ', '.join([ params = ', '.join([
(p.type + ' ' if p.type else '') + p.name for p in d.params]) (p.type + ' ' if p.type else '') + p.name for p in d.params])
text += '(' + escape_html(params) + ')' text += '(' + escape_html(params) + ')'
if d.trailing_return_type: if d.trailing_return_type:
text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type) text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type)
elif d.kind == 'typedef': elif d.kind == 'typedef':
text += ' = ' + escape_html(d.type) text += ' = ' + escape_html(d.type)
text += end text += end
text += '</div>' text += '</div>'
text += '</code></pre>\n' text += '</code></pre>\n'
if d.id is not None: if d.id is not None:
text += f'</a>\n' text += f'</a>\n'
return text return text
class CxxHandler(BaseHandler): class CxxHandler(BaseHandler):
def __init__(self, **kwargs: Any) -> None: def __init__(self, **kwargs: Any) -> None:
super().__init__(handler='cxx', **kwargs) super().__init__(handler='cxx', **kwargs)
headers = [ headers = [
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h', 'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h' 'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
] ]
# Run doxygen. # Run doxygen.
cmd = ['doxygen', '-'] cmd = ['doxygen', '-']
support_dir = Path(__file__).parents[3] support_dir = Path(__file__).parents[3]
top_dir = os.path.dirname(support_dir) top_dir = os.path.dirname(support_dir)
include_dir = os.path.join(top_dir, 'include', 'fmt') include_dir = os.path.join(top_dir, 'include', 'fmt')
self._ns2doxyxml = {} self._ns2doxyxml = {}
build_dir = os.path.join(top_dir, 'build') build_dir = os.path.join(top_dir, 'build')
os.makedirs(build_dir, exist_ok=True) os.makedirs(build_dir, exist_ok=True)
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
_, _ = p.communicate(input=r''' _, _ = p.communicate(input=r'''
PROJECT_NAME = fmt PROJECT_NAME = fmt
GENERATE_XML = YES GENERATE_XML = YES
GENERATE_LATEX = NO GENERATE_LATEX = NO
GENERATE_HTML = NO GENERATE_HTML = NO
INPUT = {0} INPUT = {0}
XML_OUTPUT = {1} XML_OUTPUT = {1}
QUIET = YES QUIET = YES
AUTOLINK_SUPPORT = NO AUTOLINK_SUPPORT = NO
MACRO_EXPANSION = YES MACRO_EXPANSION = YES
PREDEFINED = _WIN32=1 \ PREDEFINED = _WIN32=1 \
__linux__=1 \ __linux__=1 \
FMT_ENABLE_IF(...)= \ FMT_ENABLE_IF(...)= \
FMT_USE_USER_DEFINED_LITERALS=1 \ FMT_USE_USER_DEFINED_LITERALS=1 \
FMT_USE_ALIAS_TEMPLATES=1 \ FMT_USE_ALIAS_TEMPLATES=1 \
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \ FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
FMT_API= \ FMT_API= \
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \ "FMT_END_NAMESPACE=}}" \
"FMT_DOC=1" "FMT_DOC=1"
'''.format( '''.format(
' '.join([os.path.join(include_dir, h) for h in headers]), ' '.join([os.path.join(include_dir, h) for h in headers]),
self._doxyxml_dir).encode('utf-8')) self._doxyxml_dir).encode('utf-8'))
if p.returncode != 0: if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd) raise CalledProcessError(p.returncode, cmd)
# Merge all file-level XMLs into one to simplify search. # Merge all file-level XMLs into one to simplify search.
self._file_doxyxml = None self._file_doxyxml = None
for h in headers: for h in headers:
filename = h.replace(".h", "_8h.xml") filename = h.replace(".h", "_8h.xml")
with open(os.path.join(self._doxyxml_dir, filename)) as f: with open(os.path.join(self._doxyxml_dir, filename)) as f:
doxyxml = ElementTree.parse(f) doxyxml = ElementTree.parse(f)
if self._file_doxyxml is None: if self._file_doxyxml is None:
self._file_doxyxml = doxyxml self._file_doxyxml = doxyxml
continue continue
root = self._file_doxyxml.getroot() root = self._file_doxyxml.getroot()
for node in doxyxml.getroot(): for node in doxyxml.getroot():
root.append(node) root.append(node)
def collect_compound(self, identifier: str, def collect_compound(self, identifier: str,
cls: List[ElementTree.Element]) -> Definition: cls: List[ElementTree.Element]) -> Definition:
"""Collect a compound definition such as a struct.""" """Collect a compound definition such as a struct."""
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml') path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
with open(path) as f: with open(path) as f:
xml = ElementTree.parse(f) xml = ElementTree.parse(f)
node = xml.find('compounddef') node = xml.find('compounddef')
d = Definition(identifier, node=node) d = Definition(identifier, node=node)
d.template_params = convert_template_params(node) d.template_params = convert_template_params(node)
d.desc = get_description(node) d.desc = get_description(node)
d.members = [] d.members = []
for m in \ for m in \
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
node.findall('sectiondef[@kind="public-func"]/memberdef'): node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find('name').text name = m.find('name').text
# Doxygen incorrectly classifies members of private unnamed unions as # Doxygen incorrectly classifies members of private unnamed unions as
# public members of the containing class. # public members of the containing class.
if name.endswith('_'): if name.endswith('_'):
continue continue
desc = get_description(m) desc = get_description(m)
if len(desc) == 0: if len(desc) == 0:
continue continue
kind = m.get('kind') kind = m.get('kind')
member = Definition(name if name else '', kind=kind, is_member=True) member = Definition(name if name else '', kind=kind, is_member=True)
type_text = m.find('type').text type_text = m.find('type').text
member.type = type_text if type_text else '' member.type = type_text if type_text else ''
if kind == 'function': if kind == 'function':
member.params = convert_params(m) member.params = convert_params(m)
convert_return_type(member, m) convert_return_type(member, m)
member.template_params = None member.template_params = None
member.desc = desc member.desc = desc
d.members.append(member) d.members.append(member)
return d return d
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition: def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
qual_name = 'fmt::' + identifier qual_name = 'fmt::' + identifier
param_str = None param_str = None
paren = qual_name.find('(') paren = qual_name.find('(')
if paren > 0: if paren > 0:
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1] qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
colons = qual_name.rfind('::') colons = qual_name.rfind('::')
namespace, name = qual_name[:colons], qual_name[colons + 2:] namespace, name = qual_name[:colons], qual_name[colons + 2:]
# Load XML. # Load XML.
doxyxml = self._ns2doxyxml.get(namespace) doxyxml = self._ns2doxyxml.get(namespace)
if doxyxml is None: if doxyxml is None:
path = f'namespace{namespace.replace("::", "_1_1")}.xml' path = f'namespace{namespace.replace("::", "_1_1")}.xml'
with open(os.path.join(self._doxyxml_dir, path)) as f: with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = ElementTree.parse(f) doxyxml = ElementTree.parse(f)
self._ns2doxyxml[namespace] = doxyxml self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall( nodes = doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..") f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
if len(nodes) == 0: if len(nodes) == 0:
nodes = self._file_doxyxml.findall( nodes = self._file_doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..") f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
candidates = [] candidates = []
for node in nodes: for node in nodes:
# Process a function or a typedef. # Process a function or a typedef.
params = None params = None
d = Definition(name, node=node) d = Definition(name, node=node)
if d.kind == 'function': if d.kind == 'function':
params = convert_params(node) params = convert_params(node)
node_param_str = ', '.join([p.type for p in params]) node_param_str = ', '.join([p.type for p in params])
if param_str and param_str != node_param_str: if param_str and param_str != node_param_str:
candidates.append(f'{name}({node_param_str})') candidates.append(f'{name}({node_param_str})')
continue continue
elif d.kind == 'define': elif d.kind == 'define':
params = [] params = []
for p in node.findall('param'): for p in node.findall('param'):
param = Definition(p.find('defname').text, kind='param') param = Definition(p.find('defname').text, kind='param')
param.type = None param.type = None
params.append(param) params.append(param)
d.type = convert_type(node.find('type')) d.type = convert_type(node.find('type'))
d.template_params = convert_template_params(node) d.template_params = convert_template_params(node)
d.params = params d.params = params
convert_return_type(d, node) convert_return_type(d, node)
d.desc = get_description(node) d.desc = get_description(node)
return d return d
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']") cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
if not cls: if not cls:
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}') raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
return self.collect_compound(identifier, cls) return self.collect_compound(identifier, cls)
def render(self, d: Definition, config: dict) -> str: def render(self, d: Definition, config: dict) -> str:
if d.id is not None: if d.id is not None:
self.do_heading('', 0, id=d.id) self.do_heading('', 0, id=d.id)
text = '<div class="docblock">\n' text = '<div class="docblock">\n'
text += render_decl(d) text += render_decl(d)
text += '<div class="docblock-desc">\n' text += '<div class="docblock-desc">\n'
text += doxyxml2html(d.desc) text += doxyxml2html(d.desc)
if d.members is not None: if d.members is not None:
for m in d.members: for m in d.members:
text += self.render(m, config) text += self.render(m, config)
text += '</div>\n' text += '</div>\n'
text += '</div>\n' text += '</div>\n'
return text return text
def get_handler(theme: str, custom_templates: Optional[str] = None, def get_handler(theme: str, custom_templates: Optional[str] = None,
**_config: Any) -> CxxHandler: **_config: Any) -> CxxHandler:
"""Return an instance of `CxxHandler`. """Return an instance of `CxxHandler`.
Arguments: Arguments:
theme: The theme to use when rendering contents. theme: The theme to use when rendering contents.
custom_templates: Directory containing custom templates. custom_templates: Directory containing custom templates.
**_config: Configuration passed to the handler. **_config: Configuration passed to the handler.
""" """
return CxxHandler(theme=theme, custom_templates=custom_templates) return CxxHandler(theme=theme, custom_templates=custom_templates)