diff --git a/doc/mkdocstrings_handlers/cxx/__init__.py b/doc/mkdocstrings_handlers/cxx/__init__.py index 36d84a2e..b5858d94 100644 --- a/doc/mkdocstrings_handlers/cxx/__init__.py +++ b/doc/mkdocstrings_handlers/cxx/__init__.py @@ -1,4 +1,4 @@ -# A basic mkdocstrings handler for C++. +# A basic mkdocstrings handler for {fmt}. # Copyright (c) 2012 - present, Victor Zverovich import os @@ -7,8 +7,56 @@ from mkdocstrings.handlers.base import BaseHandler from typing import Any, Mapping, Optional from subprocess import CalledProcessError, PIPE, Popen, STDOUT -class Definition: - pass +class Decl: + def __init__(self, name: str): + self.name = name + +# A map from Doxygen to HTML tags. +tag_map = { + 'bold': 'b', + 'computeroutput': 'code', + 'para': 'p', + 'programlisting': 'pre', + 'verbatim': 'pre' +} + +# A map from Doxygen tags to text. +tag_text_map = { + 'codeline': '', + 'highlight': '', + 'sp': ' ' +} + +def doxyxml2html(nodes: list[et.Element]): + out = '' + for n in nodes: + tag = tag_map.get(n.tag) + if not tag: + out += tag_text_map[n.tag] + out += '<' + tag + '>' if tag else '' + out += '' if tag == 'pre' else '' + if n.text: + out += n.text + out += doxyxml2html(n) + out += '' if tag == 'pre' else '' + out += '' if tag else '' + if n.tail: + out += n.tail + return out + +def convert_param(param: et.Element) -> Decl: + d = Decl(param.find('declname').text) + type = param.find('type') + type_str = type.text if type.text else '' + for ref in type: + type_str += ref.text + if ref.tail: + type_str += ref.tail + type_str += type.tail.strip() + type_str = type_str.replace('< ', '<').replace(' >', '>') + type_str = type_str.replace(' &', '&').replace(' *', '*') + d.type = type_str + return d class CxxHandler(BaseHandler): def __init__(self, **kwargs: Any) -> None: @@ -18,7 +66,7 @@ class CxxHandler(BaseHandler): cmd = ['doxygen', '-'] doc_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) include_dir = os.path.join(os.path.dirname(doc_dir), 'include', 'fmt') - doxyxml_dir = 'doxyxml' + self._doxyxml_dir = 'doxyxml' p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) out, _ = p.communicate(input=r''' PROJECT_NAME = fmt @@ -30,7 +78,7 @@ class CxxHandler(BaseHandler): {0}/core.h {0}/compile.h {0}/format.h {0}/os.h \ {0}/ostream.h {0}/printf.h {0}/ranges.h {0}/xchar.h QUIET = YES - JAVADOC_AUTOBRIEF = YES + JAVADOC_AUTOBRIEF = NO AUTOLINK_SUPPORT = NO GENERATE_HTML = NO GENERATE_XML = YES @@ -55,44 +103,57 @@ class CxxHandler(BaseHandler): "FMT_DOC=1" EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \ fmt::basic_format_arg::handle - '''.format(include_dir, doxyxml_dir).encode('utf-8')) + '''.format(include_dir, self._doxyxml_dir).encode('utf-8')) if p.returncode != 0: raise CalledProcessError(p.returncode, cmd) # Load XML. - with open(os.path.join(doxyxml_dir, 'namespacefmt.xml')) as f: + with open(os.path.join(self._doxyxml_dir, 'namespacefmt.xml')) as f: self._doxyxml = et.parse(f) - def collect(self, identifier: str, - config: Mapping[str, Any]) -> list[Definition]: - paren = identifier.find('(') - args = None + def collect(self, identifier: str, config: Mapping[str, Any]) -> Decl: + name = identifier + paren = name.find('(') + param_str = None if paren > 0: - identifier, args = identifier[:paren], identifier[paren:] + name, param_str = name[:paren], name[paren:] nodes = self._doxyxml.findall( - f"./compounddef/sectiondef/memberdef/name[.='{identifier}']/..") - defs = [] - for n in nodes: - node_args = n.find('argsstring').text - if args != None and args != node_args: + f"compounddef/sectiondef/memberdef/name[.='{name}']/..") + candidates = [] + for node in nodes: + params = [convert_param(p) for p in node.findall('param')] + node_param_str = '(' + ', '.join([p.type for p in params]) + ')' + if param_str and param_str != node_param_str: + candidates.append(name + node_param_str) continue - d = Definition() - d.type = n.find('type').text - d.name = identifier - d.args = node_args - desc = n.find('detaileddescription/para/verbatim') - d.desc = desc.text if desc != None else '' - defs.append(d) - return defs + d = Decl(name) + d.type = node.find('type').text + d.params = params + d.desc = node.findall('detaileddescription/para') + return d + cls = self._doxyxml.findall(f"compounddef/innerclass[.='fmt::{name}']") + if not cls: + raise Exception(f'Cannot find {identifier}. Candidates: {candidates}') + with open(os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')) as f: + xml = et.parse(f) + node = xml.find('compounddef') + d = Decl(name) + d.type = node.get('kind') + d.params = None + d.desc = node.findall('detaileddescription/para') + return d - def render(self, defs: list[Definition], config: dict) -> str: - text = '' - for d in defs: - text += '
'
-      text += d.type + ' ' + d.name + d.args
-      text += '
\n' - text += d.desc + def render(self, d: Decl, config: dict) -> str: + text = '
'
+    text += d.type + ' ' + d.name
+    if d.params:
+      params = ', '.join([p.type for p in d.params])
+      text += '(' + params + ')'
+    text += ';'
+    text += '
\n' + desc = doxyxml2html(d.desc) + text += desc return text def get_handler(theme: str, custom_templates: Optional[str] = None,