1 # A basic mkdocstrings handler for {fmt}.
2 # Copyright (c) 2012 - present, Victor Zverovich
5 from pathlib
import Path
6 from typing
import Any
, List
, Mapping
, Optional
7 from subprocess
import CalledProcessError
, PIPE
, Popen
, STDOUT
8 import xml
.etree
.ElementTree
as et
10 from mkdocstrings
.handlers
.base
import BaseHandler
13 '''A definition extracted by Doxygen.'''
14 def __init__(self
, name
: str, kind
: Optional
[str] = None,
15 node
: Optional
[et
.Element
] = None,
16 is_member
: bool = False):
18 self
.kind
= kind
if kind
is not None else node
.get('kind')
19 self
.id = name
if not is_member
else None
23 # A map from Doxygen to HTML tags.
27 'computeroutput': 'code',
29 'programlisting': 'pre',
33 # A map from Doxygen tags to text.
40 def escape_html(s
: str) -> str:
41 return s
.replace("<", "<")
43 def doxyxml2html(nodes
: List
[et
.Element
]):
46 tag
= tag_map
.get(n
.tag
)
48 out
+= tag_text_map
[n
.tag
]
49 out
+= '<' + tag
+ '>' if tag
else ''
50 out
+= '<code class="language-cpp">' if tag
== 'pre' else ''
52 out
+= escape_html(n
.text
)
53 out
+= doxyxml2html(n
)
54 out
+= '</code>' if tag
== 'pre' else ''
55 out
+= '</' + tag
+ '>' if tag
else ''
60 def convert_template_params(node
: et
.Element
) -> Optional
[List
[Definition
]]:
61 templateparamlist
= node
.find('templateparamlist')
62 if templateparamlist
is None:
65 for param_node
in templateparamlist
.findall('param'):
66 name
= param_node
.find('declname')
67 param
= Definition(name
.text
if name
is not None else '', 'param')
68 param
.type = param_node
.find('type').text
72 def get_description(node
: et
.Element
) -> List
[et
.Element
]:
73 return node
.findall('briefdescription/para') + \
74 node
.findall('detaileddescription/para')
76 def normalize_type(type: str) -> str:
77 type = type.replace('< ', '<').replace(' >', '>')
78 return type.replace(' &', '&').replace(' *', '*')
80 def convert_type(type: et
.Element
) -> str:
83 result
= type.text
if type.text
else ''
88 result
+= type.tail
.strip()
89 return normalize_type(result
)
91 def convert_params(func
: et
.Element
) -> Definition
:
93 for p
in func
.findall('param'):
94 d
= Definition(p
.find('declname').text
, 'param')
95 d
.type = convert_type(p
.find('type'))
99 def convert_return_type(d
: Definition
, node
: et
.Element
) -> None:
100 d
.trailing_return_type
= None
101 if d
.type == 'auto' or d
.type == 'constexpr auto':
102 parts
= node
.find('argsstring').text
.split(' -> ')
104 d
.trailing_return_type
= normalize_type(parts
[1])
106 def render_param(param
: Definition
) -> str:
107 return param
.type + (f
' {param.name}' if len(param
.name
) > 0 else '')
109 def render_decl(d
: Definition
) -> None:
112 text
+= f
'<a id="{d.id}">\n'
113 text
+= '<pre><code class="language-cpp decl">'
116 if d
.template_params
is not None:
117 text
+= 'template <'
118 text
+= ', '.join([render_param(p
) for p
in d
.template_params
])
124 if d
.kind
== 'function' or d
.kind
== 'variable':
125 text
+= d
.type + ' ' if len(d
.type) > 0 else ''
126 elif d
.kind
== 'typedef':
128 elif d
.kind
== 'define':
134 if d
.params
is not None:
136 (p
.type + ' ' if p
.type else '') + p
.name
for p
in d
.params
])
137 text
+= '(' + escape_html(params
) + ')'
138 if d
.trailing_return_type
:
139 text
+= ' -⁠> ' + escape_html(d
.trailing_return_type
)
140 elif d
.kind
== 'typedef':
141 text
+= ' = ' + escape_html(d
.type)
145 text
+= '</code></pre>\n'
150 class CxxHandler(BaseHandler
):
151 def __init__(self
, **kwargs
: Any
) -> None:
152 super().__init
__(handler
='cxx', **kwargs
)
155 'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
156 'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
160 cmd
= ['doxygen', '-']
161 support_dir
= Path(__file__
).parents
[3]
162 top_dir
= os
.path
.dirname(support_dir
)
163 include_dir
= os
.path
.join(top_dir
, 'include', 'fmt')
164 self
._ns
2doxyxml
= {}
165 build_dir
= os
.path
.join(top_dir
, 'build')
166 os
.makedirs(build_dir
, exist_ok
=True)
167 self
._doxyxml
_dir
= os
.path
.join(build_dir
, 'doxyxml')
168 p
= Popen(cmd
, stdin
=PIPE
, stdout
=PIPE
, stderr
=STDOUT
)
169 _
, _
= p
.communicate(input=r
'''
177 AUTOLINK_SUPPORT = NO
178 MACRO_EXPANSION = YES
179 PREDEFINED = _WIN32=1 \
181 FMT_ENABLE_IF(...)= \
182 FMT_USE_USER_DEFINED_LITERALS=1 \
183 FMT_USE_ALIAS_TEMPLATES=1 \
184 FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
186 "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
187 "FMT_END_NAMESPACE=}}" \
190 ' '.join([os
.path
.join(include_dir
, h
) for h
in headers
]),
191 self
._doxyxml
_dir
).encode('utf-8'))
192 if p
.returncode
!= 0:
193 raise CalledProcessError(p
.returncode
, cmd
)
195 # Merge all file-level XMLs into one to simplify search.
196 self
._file
_doxyxml
= None
198 filename
= h
.replace(".h", "_8h.xml")
199 with
open(os
.path
.join(self
._doxyxml
_dir
, filename
)) as f
:
200 doxyxml
= et
.parse(f
)
201 if self
._file
_doxyxml
is None:
202 self
._file
_doxyxml
= doxyxml
204 root
= self
._file
_doxyxml
.getroot()
205 for node
in doxyxml
.getroot():
208 def collect_compound(self
, identifier
: str,
209 cls
: List
[et
.Element
]) -> Definition
:
210 '''Collect a compound definition such as a struct.'''
211 path
= os
.path
.join(self
._doxyxml
_dir
, cls
[0].get('refid') + '.xml')
212 with
open(path
) as f
:
214 node
= xml
.find('compounddef')
215 d
= Definition(identifier
, node
=node
)
216 d
.template_params
= convert_template_params(node
)
217 d
.desc
= get_description(node
)
219 for m
in node
.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
220 node
.findall('sectiondef[@kind="public-func"]/memberdef'):
221 name
= m
.find('name').text
222 # Doxygen incorrectly classifies members of private unnamed unions as
223 # public members of the containing class.
224 if name
.endswith('_'):
226 desc
= get_description(m
)
230 member
= Definition(name
if name
else '', kind
=kind
, is_member
=True)
231 type = m
.find('type').text
232 member
.type = type if type else ''
233 if kind
== 'function':
234 member
.params
= convert_params(m
)
235 convert_return_type(member
, m
)
236 member
.template_params
= None
238 d
.members
.append(member
)
241 def collect(self
, identifier
: str, config
: Mapping
[str, Any
]) -> Definition
:
242 qual_name
= 'fmt::' + identifier
245 paren
= qual_name
.find('(')
247 qual_name
, param_str
= qual_name
[:paren
], qual_name
[paren
+ 1:-1]
249 colons
= qual_name
.rfind('::')
250 namespace
, name
= qual_name
[:colons
], qual_name
[colons
+ 2:]
253 doxyxml
= self
._ns
2doxyxml
.get(namespace
)
255 path
= f
'namespace{namespace.replace("::", "_1_1")}.xml'
256 with
open(os
.path
.join(self
._doxyxml
_dir
, path
)) as f
:
257 doxyxml
= et
.parse(f
)
258 self
._ns
2doxyxml
[namespace
] = doxyxml
260 nodes
= doxyxml
.findall(
261 f
"compounddef/sectiondef/memberdef/name[.='{name}']/..")
263 nodes
= self
._file
_doxyxml
.findall(
264 f
"compounddef/sectiondef/memberdef/name[.='{name}']/..")
267 # Process a function or a typedef.
269 d
= Definition(name
, node
=node
)
270 if d
.kind
== 'function':
271 params
= convert_params(node
)
272 node_param_str
= ', '.join([p
.type for p
in params
])
273 if param_str
and param_str
!= node_param_str
:
274 candidates
.append(f
'{name}({node_param_str})')
276 elif d
.kind
== 'define':
278 for p
in node
.findall('param'):
279 param
= Definition(p
.find('defname').text
, kind
='param')
282 d
.type = convert_type(node
.find('type'))
283 d
.template_params
= convert_template_params(node
)
285 convert_return_type(d
, node
)
286 d
.desc
= get_description(node
)
289 cls
= doxyxml
.findall(f
"compounddef/innerclass[.='{qual_name}']")
291 raise Exception(f
'Cannot find {identifier}. Candidates: {candidates}')
292 return self
.collect_compound(identifier
, cls
)
294 def render(self
, d
: Definition
, config
: dict) -> str:
296 self
.do_heading('', 0, id=d
.id)
297 text
= '<div class="docblock">\n'
298 text
+= render_decl(d
)
299 text
+= '<div class="docblock-desc">\n'
300 text
+= doxyxml2html(d
.desc
)
301 if d
.members
is not None:
303 text
+= self
.render(m
, config
)
308 def get_handler(theme
: str, custom_templates
: Optional
[str] = None,
309 **config
: Any
) -> CxxHandler
:
310 '''Return an instance of `CxxHandler`.
313 theme: The theme to use when rendering contents.
314 custom_templates: Directory containing custom templates.
315 **config: Configuration passed to the handler.
317 return CxxHandler(theme
=theme
, custom_templates
=custom_templates
)