aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/fmt/support/python
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-11-07 14:49:13 +0100
committerGitHub Enterprise <[email protected]>2025-11-07 14:49:13 +0100
commit24e43a913f29ac3b314354e8ce5175f135bcc64f (patch)
treeca442937ceeb63461012b33a4576e9835099f106 /thirdparty/fmt/support/python
parentget oplog attachments (#622) (diff)
downloadzen-24e43a913f29ac3b314354e8ce5175f135bcc64f.tar.xz
zen-24e43a913f29ac3b314354e8ce5175f135bcc64f.zip
switch to xmake for package management (#611)
This change removes our dependency on vcpkg for package management, in favour of bringing some code in-tree in the `thirdparty` folder as well as using the xmake build-in package management feature. For the latter, all the package definitions are maintained in the zen repo itself, in the `repo` folder. It should now also be easier to build the project as it will no longer depend on having the right version of vcpkg installed, which has been a common problem for new people coming in to the codebase. Now you should only need xmake to build. * Bumps xmake requirement on github runners to 2.9.9 to resolve an issue where xmake on Windows invokes cmake with `v144` toolchain which does not exist * BLAKE3 is now in-tree at `thirdparty/blake3` * cpr is now in-tree at `thirdparty/cpr` * cxxopts is now in-tree at `thirdparty/cxxopts` * fmt is now in-tree at `thirdparty/fmt` * robin-map is now in-tree at `thirdparty/robin-map` * ryml is now in-tree at `thirdparty/ryml` * sol2 is now in-tree at `thirdparty/sol2` * spdlog is now in-tree at `thirdparty/spdlog` * utfcpp is now in-tree at `thirdparty/utfcpp` * xmake package repo definitions is in `repo` * implemented support for sanitizers. ASAN is supported on windows, TSAN, UBSAN, MSAN etc are supported on Linux/MacOS though I have not yet tested it extensively on MacOS * the zencore encryption implementation also now supports using mbedTLS which is used on MacOS, though for now we still use openssl on Linux * crashpad * bumps libcurl to 8.11.0 (from 8.8.0) which should address a rare build upload bug
Diffstat (limited to 'thirdparty/fmt/support/python')
-rw-r--r--thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/__init__.py338
-rw-r--r--thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/templates/README1
2 files changed, 339 insertions, 0 deletions
diff --git a/thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/__init__.py b/thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/__init__.py
new file mode 100644
index 000000000..85d52a580
--- /dev/null
+++ b/thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/__init__.py
@@ -0,0 +1,338 @@
+# A basic mkdocstrings handler for {fmt}.
+# Copyright (c) 2012 - present, Victor Zverovich
+# https://github.com/fmtlib/fmt/blob/master/LICENSE
+
+import os
+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
+
+
+class Definition:
+ """A definition extracted by Doxygen."""
+
+ def __init__(self, name: str, kind: Optional[str] = None,
+ node: Optional[ElementTree.Element] = None,
+ is_member: bool = False):
+ self.name = name
+ self.kind = kind if kind is not None else node.get('kind')
+ self.desc = None
+ self.id = name if not is_member else None
+ self.members = None
+ self.params = None
+ self.template_params = None
+ self.trailing_return_type = None
+ self.type = None
+
+
+# A map from Doxygen to HTML tags.
+tag_map = {
+ 'bold': 'b',
+ 'emphasis': 'em',
+ 'computeroutput': 'code',
+ 'para': 'p',
+ 'programlisting': 'pre',
+ 'verbatim': 'pre'
+}
+
+# A map from Doxygen tags to text.
+tag_text_map = {
+ 'codeline': '',
+ 'highlight': '',
+ 'sp': ' '
+}
+
+
+def escape_html(s: str) -> str:
+ return s.replace("<", "&lt;")
+
+
+def doxyxml2html(nodes: List[ElementTree.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 += '<code class="language-cpp">' if tag == 'pre' else ''
+ if n.text:
+ out += escape_html(n.text)
+ out += doxyxml2html(list(n))
+ out += '</code>' if tag == 'pre' else ''
+ out += '</' + tag + '>' if tag else ''
+ if n.tail:
+ out += n.tail
+ return out
+
+
+def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
+ template_param_list = node.find('templateparamlist')
+ if template_param_list is None:
+ return None
+ params = []
+ for param_node in template_param_list.findall('param'):
+ name = param_node.find('declname')
+ param = Definition(name.text if name is not None else '', 'param')
+ param.type = param_node.find('type').text
+ params.append(param)
+ return params
+
+
+def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
+ return node.findall('briefdescription/para') + \
+ node.findall('detaileddescription/para')
+
+
+def normalize_type(type_: str) -> str:
+ type_ = type_.replace('< ', '<').replace(' >', '>')
+ return type_.replace(' &', '&').replace(' *', '*')
+
+
+def convert_type(type_: ElementTree.Element) -> Optional[str]:
+ if type_ is None:
+ return None
+ result = type_.text if type_.text else ''
+ for ref in type_:
+ result += ref.text
+ if ref.tail:
+ result += ref.tail
+ result += type_.tail.strip()
+ return normalize_type(result)
+
+
+def convert_params(func: ElementTree.Element) -> List[Definition]:
+ params = []
+ for p in func.findall('param'):
+ d = Definition(p.find('declname').text, 'param')
+ d.type = convert_type(p.find('type'))
+ params.append(d)
+ return params
+
+
+def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
+ d.trailing_return_type = None
+ if d.type == 'auto' or d.type == 'constexpr auto':
+ parts = node.find('argsstring').text.split(' -> ')
+ if len(parts) > 1:
+ d.trailing_return_type = normalize_type(parts[1])
+
+
+def render_param(param: Definition) -> str:
+ return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '')
+
+
+def render_decl(d: Definition) -> str:
+ text = ''
+ if d.id is not None:
+ text += f'<a id="{d.id}">\n'
+ text += '<pre><code class="language-cpp decl">'
+
+ text += '<div>'
+ if d.template_params is not None:
+ text += 'template &lt;'
+ text += ', '.join([render_param(p) for p in d.template_params])
+ text += '&gt;\n'
+ text += '</div>'
+
+ text += '<div>'
+ end = ';'
+ if d.kind == 'function' or d.kind == 'variable':
+ text += d.type + ' ' if len(d.type) > 0 else ''
+ elif d.kind == 'typedef':
+ text += 'using '
+ elif d.kind == 'define':
+ end = ''
+ else:
+ text += d.kind + ' '
+ text += d.name
+
+ if d.params is not None:
+ params = ', '.join([
+ (p.type + ' ' if p.type else '') + p.name for p in d.params])
+ text += '(' + escape_html(params) + ')'
+ if d.trailing_return_type:
+ text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type)
+ elif d.kind == 'typedef':
+ text += ' = ' + escape_html(d.type)
+
+ text += end
+ text += '</div>'
+ text += '</code></pre>\n'
+ if d.id is not None:
+ text += f'</a>\n'
+ return text
+
+
+class CxxHandler(BaseHandler):
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(handler='cxx', **kwargs)
+
+ headers = [
+ 'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
+ 'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
+ ]
+
+ # Run doxygen.
+ cmd = ['doxygen', '-']
+ support_dir = Path(__file__).parents[3]
+ top_dir = os.path.dirname(support_dir)
+ include_dir = os.path.join(top_dir, 'include', 'fmt')
+ self._ns2doxyxml = {}
+ build_dir = os.path.join(top_dir, 'build')
+ os.makedirs(build_dir, exist_ok=True)
+ self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
+ p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ _, _ = p.communicate(input=r'''
+ PROJECT_NAME = fmt
+ GENERATE_XML = YES
+ GENERATE_LATEX = NO
+ GENERATE_HTML = NO
+ INPUT = {0}
+ XML_OUTPUT = {1}
+ QUIET = YES
+ AUTOLINK_SUPPORT = NO
+ MACRO_EXPANSION = YES
+ PREDEFINED = _WIN32=1 \
+ __linux__=1 \
+ FMT_ENABLE_IF(...)= \
+ FMT_USE_USER_LITERALS=1 \
+ FMT_USE_ALIAS_TEMPLATES=1 \
+ FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
+ FMT_API= \
+ "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
+ "FMT_END_NAMESPACE=}}" \
+ "FMT_DOC=1"
+ '''.format(
+ ' '.join([os.path.join(include_dir, h) for h in headers]),
+ self._doxyxml_dir).encode('utf-8'))
+ if p.returncode != 0:
+ raise CalledProcessError(p.returncode, cmd)
+
+ # Merge all file-level XMLs into one to simplify search.
+ self._file_doxyxml = None
+ for h in headers:
+ filename = h.replace(".h", "_8h.xml")
+ with open(os.path.join(self._doxyxml_dir, filename)) as f:
+ doxyxml = ElementTree.parse(f)
+ if self._file_doxyxml is None:
+ self._file_doxyxml = doxyxml
+ continue
+ root = self._file_doxyxml.getroot()
+ for node in doxyxml.getroot():
+ root.append(node)
+
+ def collect_compound(self, identifier: str,
+ cls: List[ElementTree.Element]) -> Definition:
+ """Collect a compound definition such as a struct."""
+ path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
+ with open(path) as f:
+ xml = ElementTree.parse(f)
+ node = xml.find('compounddef')
+ d = Definition(identifier, node=node)
+ d.template_params = convert_template_params(node)
+ d.desc = get_description(node)
+ d.members = []
+ for m in \
+ node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
+ node.findall('sectiondef[@kind="public-func"]/memberdef'):
+ name = m.find('name').text
+ # Doxygen incorrectly classifies members of private unnamed unions as
+ # public members of the containing class.
+ if name.endswith('_'):
+ continue
+ desc = get_description(m)
+ if len(desc) == 0:
+ continue
+ kind = m.get('kind')
+ member = Definition(name if name else '', kind=kind, is_member=True)
+ type_text = m.find('type').text
+ member.type = type_text if type_text else ''
+ if kind == 'function':
+ member.params = convert_params(m)
+ convert_return_type(member, m)
+ member.template_params = None
+ member.desc = desc
+ d.members.append(member)
+ return d
+
+ def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
+ qual_name = 'fmt::' + identifier
+
+ param_str = None
+ paren = qual_name.find('(')
+ if paren > 0:
+ qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
+
+ colons = qual_name.rfind('::')
+ namespace, name = qual_name[:colons], qual_name[colons + 2:]
+
+ # Load XML.
+ doxyxml = self._ns2doxyxml.get(namespace)
+ if doxyxml is None:
+ path = f'namespace{namespace.replace("::", "_1_1")}.xml'
+ with open(os.path.join(self._doxyxml_dir, path)) as f:
+ doxyxml = ElementTree.parse(f)
+ self._ns2doxyxml[namespace] = doxyxml
+
+ nodes = doxyxml.findall(
+ f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
+ if len(nodes) == 0:
+ nodes = self._file_doxyxml.findall(
+ f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
+ candidates = []
+ for node in nodes:
+ # Process a function or a typedef.
+ params = None
+ d = Definition(name, node=node)
+ if d.kind == 'function':
+ params = convert_params(node)
+ node_param_str = ', '.join([p.type for p in params])
+ if param_str and param_str != node_param_str:
+ candidates.append(f'{name}({node_param_str})')
+ continue
+ elif d.kind == 'define':
+ params = []
+ for p in node.findall('param'):
+ param = Definition(p.find('defname').text, kind='param')
+ param.type = None
+ params.append(param)
+ d.type = convert_type(node.find('type'))
+ d.template_params = convert_template_params(node)
+ d.params = params
+ convert_return_type(d, node)
+ d.desc = get_description(node)
+ return d
+
+ cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
+ if not cls:
+ raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
+ return self.collect_compound(identifier, cls)
+
+ def render(self, d: Definition, config: dict) -> str:
+ if d.id is not None:
+ self.do_heading('', 0, id=d.id)
+ text = '<div class="docblock">\n'
+ text += render_decl(d)
+ text += '<div class="docblock-desc">\n'
+ text += doxyxml2html(d.desc)
+ if d.members is not None:
+ for m in d.members:
+ text += self.render(m, config)
+ text += '</div>\n'
+ text += '</div>\n'
+ return text
+
+
+def get_handler(theme: str, custom_templates: Optional[str] = None,
+ **_config: Any) -> CxxHandler:
+ """Return an instance of `CxxHandler`.
+
+ Arguments:
+ theme: The theme to use when rendering contents.
+ custom_templates: Directory containing custom templates.
+ **_config: Configuration passed to the handler.
+ """
+ return CxxHandler(theme=theme, custom_templates=custom_templates)
diff --git a/thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/templates/README b/thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/templates/README
new file mode 100644
index 000000000..7da18db54
--- /dev/null
+++ b/thirdparty/fmt/support/python/mkdocstrings_handlers/cxx/templates/README
@@ -0,0 +1 @@
+mkdocsstrings requires a handler to have a templates directory.