fprime-cpp-codegen 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fprime_cpp_codegen/__init__.py +162 -0
- fprime_cpp_codegen/body.py +448 -0
- fprime_cpp_codegen/builder/__init__.py +75 -0
- fprime_cpp_codegen/builder/base.py +55 -0
- fprime_cpp_codegen/builder/coercion.py +97 -0
- fprime_cpp_codegen/builder/decoration.py +201 -0
- fprime_cpp_codegen/builder/definitions.py +381 -0
- fprime_cpp_codegen/builder/document.py +155 -0
- fprime_cpp_codegen/builder/scopes.py +466 -0
- fprime_cpp_codegen/comments.py +144 -0
- fprime_cpp_codegen/doc.py +371 -0
- fprime_cpp_codegen/errors.py +25 -0
- fprime_cpp_codegen/formatting.py +89 -0
- fprime_cpp_codegen/fprime.py +149 -0
- fprime_cpp_codegen/lines.py +323 -0
- fprime_cpp_codegen/output.py +121 -0
- fprime_cpp_codegen/py.typed +0 -0
- fprime_cpp_codegen/writer.py +787 -0
- fprime_cpp_codegen-0.1.0.dist-info/METADATA +347 -0
- fprime_cpp_codegen-0.1.0.dist-info/RECORD +23 -0
- fprime_cpp_codegen-0.1.0.dist-info/WHEEL +5 -0
- fprime_cpp_codegen-0.1.0.dist-info/licenses/LICENSE +201 -0
- fprime_cpp_codegen-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""Builders for the things a scope holds: functions, constructors, enums."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Sequence
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
from ..body import Body, Code
|
|
9
|
+
from ..comments import (
|
|
10
|
+
add_param_comment,
|
|
11
|
+
write_doxygen_comment,
|
|
12
|
+
write_doxygen_comment_opt,
|
|
13
|
+
)
|
|
14
|
+
from ..doc import Constructor, Destructor, Function, Lines, Output, Param, Type, as_type
|
|
15
|
+
from ..errors import ValidationError
|
|
16
|
+
from ..lines import Line, wrap_in_scope
|
|
17
|
+
from ..lines import line as _line
|
|
18
|
+
from .base import _Builder
|
|
19
|
+
from .coercion import _as_body_lines, _as_params, _sv_qualifier
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Radix(Enum):
|
|
23
|
+
"""How to spell an integer literal."""
|
|
24
|
+
|
|
25
|
+
DECIMAL = "decimal"
|
|
26
|
+
HEX = "hex"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _wrap_in_enum(
|
|
30
|
+
body: list[Line],
|
|
31
|
+
*,
|
|
32
|
+
name: str | None = None,
|
|
33
|
+
scoped: bool = False,
|
|
34
|
+
underlying: str | None = None,
|
|
35
|
+
) -> list[Line]:
|
|
36
|
+
"""Wrap enumerators in an ``enum``, ``enum <name>`` or ``enum class <name>``."""
|
|
37
|
+
if scoped:
|
|
38
|
+
suffix = f" : {underlying}" if underlying is not None else ""
|
|
39
|
+
opening = f"enum class {name}{suffix} {{"
|
|
40
|
+
elif name is not None:
|
|
41
|
+
opening = f"enum {name} {{"
|
|
42
|
+
else:
|
|
43
|
+
opening = "enum {"
|
|
44
|
+
return wrap_in_scope(opening, body, "};", keep_empty=True)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FunctionBuilder(_Builder[Function]):
|
|
48
|
+
"""A function or member function under construction."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
name: str,
|
|
53
|
+
*,
|
|
54
|
+
ret: Type | str = "void",
|
|
55
|
+
params: Iterable[Param | Sequence[str]] = (),
|
|
56
|
+
comment: str | None = None,
|
|
57
|
+
body: Code = None,
|
|
58
|
+
const: bool = False,
|
|
59
|
+
static: bool = False,
|
|
60
|
+
virtual: bool = False,
|
|
61
|
+
pure_virtual: bool = False,
|
|
62
|
+
override: bool = False,
|
|
63
|
+
final: bool = False,
|
|
64
|
+
constexpr: bool = False,
|
|
65
|
+
inline: bool = False,
|
|
66
|
+
noexcept: bool = False,
|
|
67
|
+
deleted: bool = False,
|
|
68
|
+
defaulted: bool = False,
|
|
69
|
+
template: str | None = None,
|
|
70
|
+
inline_body: bool = False,
|
|
71
|
+
cpp_file: str | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
if not name:
|
|
74
|
+
raise ValidationError("a function needs a name")
|
|
75
|
+
self.name = name
|
|
76
|
+
self.ret = as_type(ret)
|
|
77
|
+
self.comment = comment
|
|
78
|
+
self.const = const
|
|
79
|
+
self.constexpr = constexpr
|
|
80
|
+
self.inline = inline
|
|
81
|
+
self.noexcept = noexcept
|
|
82
|
+
self.deleted = deleted
|
|
83
|
+
self.defaulted = defaulted
|
|
84
|
+
self.template = template
|
|
85
|
+
self.inline_body = inline_body
|
|
86
|
+
self.cpp_file = cpp_file
|
|
87
|
+
self._sv = _sv_qualifier(
|
|
88
|
+
static=static,
|
|
89
|
+
virtual=virtual,
|
|
90
|
+
pure_virtual=pure_virtual,
|
|
91
|
+
override=override,
|
|
92
|
+
final=final,
|
|
93
|
+
)
|
|
94
|
+
self._params = _as_params(params)
|
|
95
|
+
self.body = Body(_as_body_lines(body))
|
|
96
|
+
|
|
97
|
+
def param(
|
|
98
|
+
self,
|
|
99
|
+
type_name: Type | str,
|
|
100
|
+
name: str,
|
|
101
|
+
*,
|
|
102
|
+
comment: str | None = None,
|
|
103
|
+
default: str | None = None,
|
|
104
|
+
) -> FunctionBuilder:
|
|
105
|
+
"""Append one formal parameter. Returns self, so calls can be chained."""
|
|
106
|
+
self._params.append(Param(as_type(type_name), name, comment, default))
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
def params(self, *params: Param | Sequence[str]) -> FunctionBuilder:
|
|
110
|
+
"""Append several formal parameters at once."""
|
|
111
|
+
self._params.extend(_as_params(params))
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def build(self) -> Function:
|
|
115
|
+
return Function(
|
|
116
|
+
self.name,
|
|
117
|
+
params=list(self._params),
|
|
118
|
+
ret_type=self.ret,
|
|
119
|
+
body=self.body.build(),
|
|
120
|
+
comment=self.comment,
|
|
121
|
+
sv=self._sv,
|
|
122
|
+
const=self.const,
|
|
123
|
+
constexpr=self.constexpr,
|
|
124
|
+
inline=self.inline,
|
|
125
|
+
noexcept=self.noexcept,
|
|
126
|
+
deleted=self.deleted,
|
|
127
|
+
defaulted=self.defaulted,
|
|
128
|
+
template=self.template,
|
|
129
|
+
inline_body=self.inline_body,
|
|
130
|
+
cpp_file=self.cpp_file,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def __enter__(self) -> FunctionBuilder:
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def __exit__(self, *exc: object) -> None:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ConstructorBuilder(_Builder[Constructor]):
|
|
141
|
+
"""A constructor under construction."""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
*,
|
|
146
|
+
params: Iterable[Param | Sequence[str]] = (),
|
|
147
|
+
initializers: Iterable[str] = (),
|
|
148
|
+
comment: str | None = None,
|
|
149
|
+
body: Code = None,
|
|
150
|
+
explicit: bool = False,
|
|
151
|
+
constexpr: bool = False,
|
|
152
|
+
noexcept: bool = False,
|
|
153
|
+
deleted: bool = False,
|
|
154
|
+
defaulted: bool = False,
|
|
155
|
+
template: str | None = None,
|
|
156
|
+
inline_body: bool = False,
|
|
157
|
+
cpp_file: str | None = None,
|
|
158
|
+
) -> None:
|
|
159
|
+
self.comment = comment
|
|
160
|
+
self.explicit = explicit
|
|
161
|
+
self.constexpr = constexpr
|
|
162
|
+
self.noexcept = noexcept
|
|
163
|
+
self.deleted = deleted
|
|
164
|
+
self.defaulted = defaulted
|
|
165
|
+
self.template = template
|
|
166
|
+
self.inline_body = inline_body
|
|
167
|
+
self.cpp_file = cpp_file
|
|
168
|
+
self._params = _as_params(params)
|
|
169
|
+
self._initializers = list(initializers)
|
|
170
|
+
self.body = Body(_as_body_lines(body))
|
|
171
|
+
|
|
172
|
+
def param(
|
|
173
|
+
self,
|
|
174
|
+
type_name: Type | str,
|
|
175
|
+
name: str,
|
|
176
|
+
*,
|
|
177
|
+
comment: str | None = None,
|
|
178
|
+
default: str | None = None,
|
|
179
|
+
) -> ConstructorBuilder:
|
|
180
|
+
"""Append one formal parameter."""
|
|
181
|
+
self._params.append(Param(as_type(type_name), name, comment, default))
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def params(self, *params: Param | Sequence[str]) -> ConstructorBuilder:
|
|
185
|
+
"""Append several formal parameters at once."""
|
|
186
|
+
self._params.extend(_as_params(params))
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def init(self, *entries: str) -> ConstructorBuilder:
|
|
190
|
+
"""Append member-initializer entries, e.g. ``init("m_size(size)")``."""
|
|
191
|
+
self._initializers.extend(entries)
|
|
192
|
+
return self
|
|
193
|
+
|
|
194
|
+
def build(self) -> Constructor:
|
|
195
|
+
return Constructor(
|
|
196
|
+
params=list(self._params),
|
|
197
|
+
initializers=list(self._initializers),
|
|
198
|
+
body=self.body.build(),
|
|
199
|
+
comment=self.comment,
|
|
200
|
+
explicit=self.explicit,
|
|
201
|
+
constexpr=self.constexpr,
|
|
202
|
+
noexcept=self.noexcept,
|
|
203
|
+
deleted=self.deleted,
|
|
204
|
+
defaulted=self.defaulted,
|
|
205
|
+
template=self.template,
|
|
206
|
+
inline_body=self.inline_body,
|
|
207
|
+
cpp_file=self.cpp_file,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def __enter__(self) -> ConstructorBuilder:
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
def __exit__(self, *exc: object) -> None:
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class DestructorBuilder(_Builder[Destructor]):
|
|
218
|
+
"""A destructor under construction."""
|
|
219
|
+
|
|
220
|
+
def __init__(
|
|
221
|
+
self,
|
|
222
|
+
*,
|
|
223
|
+
comment: str | None = None,
|
|
224
|
+
body: Code = None,
|
|
225
|
+
virtual: bool = False,
|
|
226
|
+
override: bool = False,
|
|
227
|
+
noexcept: bool = False,
|
|
228
|
+
deleted: bool = False,
|
|
229
|
+
defaulted: bool = False,
|
|
230
|
+
inline_body: bool = False,
|
|
231
|
+
cpp_file: str | None = None,
|
|
232
|
+
) -> None:
|
|
233
|
+
self.comment = comment
|
|
234
|
+
self.virtual = virtual
|
|
235
|
+
self.override = override
|
|
236
|
+
self.noexcept = noexcept
|
|
237
|
+
self.deleted = deleted
|
|
238
|
+
self.defaulted = defaulted
|
|
239
|
+
self.inline_body = inline_body
|
|
240
|
+
self.cpp_file = cpp_file
|
|
241
|
+
self.body = Body(_as_body_lines(body))
|
|
242
|
+
|
|
243
|
+
def build(self) -> Destructor:
|
|
244
|
+
return Destructor(
|
|
245
|
+
body=self.body.build(),
|
|
246
|
+
comment=self.comment,
|
|
247
|
+
virtual=self.virtual,
|
|
248
|
+
override=self.override,
|
|
249
|
+
noexcept=self.noexcept,
|
|
250
|
+
deleted=self.deleted,
|
|
251
|
+
defaulted=self.defaulted,
|
|
252
|
+
inline_body=self.inline_body,
|
|
253
|
+
cpp_file=self.cpp_file,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def __enter__(self) -> DestructorBuilder:
|
|
257
|
+
return self
|
|
258
|
+
|
|
259
|
+
def __exit__(self, *exc: object) -> None:
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class EnumBuilder(_Builder[Lines]):
|
|
264
|
+
"""An enum or enum class under construction. Renders as raw lines."""
|
|
265
|
+
|
|
266
|
+
def __init__(
|
|
267
|
+
self,
|
|
268
|
+
name: str | None = None,
|
|
269
|
+
*,
|
|
270
|
+
underlying: str | None = None,
|
|
271
|
+
scoped: bool = False,
|
|
272
|
+
comment: str | None = None,
|
|
273
|
+
output: Output = Output.HPP,
|
|
274
|
+
cpp_file: str | None = None,
|
|
275
|
+
radix: Radix = Radix.DECIMAL,
|
|
276
|
+
trailing_comma: bool = True,
|
|
277
|
+
comment_above: bool = False,
|
|
278
|
+
qualifier: str = "",
|
|
279
|
+
) -> None:
|
|
280
|
+
if underlying is not None and not scoped:
|
|
281
|
+
raise ValidationError(
|
|
282
|
+
"an underlying type needs a scoped enum; pass scoped=True (or use "
|
|
283
|
+
"enum_class())"
|
|
284
|
+
)
|
|
285
|
+
if scoped and not name:
|
|
286
|
+
raise ValidationError("a scoped enum needs a name")
|
|
287
|
+
self.name = name
|
|
288
|
+
self.underlying = underlying
|
|
289
|
+
self.scoped = scoped
|
|
290
|
+
self.comment = comment
|
|
291
|
+
self.output = output
|
|
292
|
+
self.cpp_file = cpp_file
|
|
293
|
+
self.radix = radix
|
|
294
|
+
self.trailing_comma = trailing_comma
|
|
295
|
+
"""Whether the last enumerator carries a comma. Legal either way."""
|
|
296
|
+
|
|
297
|
+
self.comment_above = comment_above
|
|
298
|
+
"""Put each enumerator's comment on a ``//!`` line above it, instead of a
|
|
299
|
+
``//!<`` post-comment after it."""
|
|
300
|
+
|
|
301
|
+
self.qualifier = qualifier
|
|
302
|
+
self._constants: list[list[Line]] = []
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def type(self) -> Type:
|
|
306
|
+
"""This enum as a :class:`Type`, qualified for use in a source file.
|
|
307
|
+
|
|
308
|
+
A nested enum is spelled bare inside its class, but a source-file return type
|
|
309
|
+
precedes ``Class::`` and so is not yet in the class's scope. Pass this to
|
|
310
|
+
``ret=`` for both spellings::
|
|
311
|
+
|
|
312
|
+
status = cls.enum_class("Status", underlying="U8")
|
|
313
|
+
cls.function("check", ret=status.type)
|
|
314
|
+
"""
|
|
315
|
+
if self.name is None:
|
|
316
|
+
raise ValidationError("an anonymous enum has no type name")
|
|
317
|
+
return Type(
|
|
318
|
+
self.name, f"{self.qualifier}::{self.name}" if self.qualifier else None
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def constant(
|
|
322
|
+
self,
|
|
323
|
+
name: str,
|
|
324
|
+
value: int | str | None = None,
|
|
325
|
+
*,
|
|
326
|
+
comment: str | None = None,
|
|
327
|
+
radix: Radix | None = None,
|
|
328
|
+
) -> EnumBuilder:
|
|
329
|
+
"""Append one enumerator.
|
|
330
|
+
|
|
331
|
+
``value`` may be an integer, an arbitrary C++ expression, or ``None`` to let
|
|
332
|
+
the compiler assign the next value.
|
|
333
|
+
"""
|
|
334
|
+
if value is None:
|
|
335
|
+
text = f"{name},"
|
|
336
|
+
elif isinstance(value, int):
|
|
337
|
+
spelled = (
|
|
338
|
+
f"0x{value:x}" if (radix or self.radix) is Radix.HEX else str(value)
|
|
339
|
+
)
|
|
340
|
+
text = f"{name} = {spelled},"
|
|
341
|
+
else:
|
|
342
|
+
text = f"{name} = {value},"
|
|
343
|
+
if comment is not None and self.comment_above:
|
|
344
|
+
entry = [*write_doxygen_comment(comment)[1:], _line(text)]
|
|
345
|
+
else:
|
|
346
|
+
entry = add_param_comment(text, comment)
|
|
347
|
+
self._constants.append(entry)
|
|
348
|
+
return self
|
|
349
|
+
|
|
350
|
+
def constants(self, *names: str) -> EnumBuilder:
|
|
351
|
+
"""Append several auto-numbered enumerators at once."""
|
|
352
|
+
for name in names:
|
|
353
|
+
self.constant(name)
|
|
354
|
+
return self
|
|
355
|
+
|
|
356
|
+
def _body(self) -> list[Line]:
|
|
357
|
+
"""The enumerators, with the last one's comma removed if asked."""
|
|
358
|
+
entries = [list(e) for e in self._constants]
|
|
359
|
+
if entries and not self.trailing_comma:
|
|
360
|
+
last = entries[-1]
|
|
361
|
+
last[-1] = Line(last[-1].string.rstrip(","), last[-1].indent)
|
|
362
|
+
return [l for entry in entries for l in entry]
|
|
363
|
+
|
|
364
|
+
def build(self) -> Lines:
|
|
365
|
+
inner = _wrap_in_enum(
|
|
366
|
+
self._body(),
|
|
367
|
+
name=self.name,
|
|
368
|
+
scoped=self.scoped,
|
|
369
|
+
underlying=self.underlying,
|
|
370
|
+
)
|
|
371
|
+
return Lines(
|
|
372
|
+
[*write_doxygen_comment_opt(self.comment), *inner],
|
|
373
|
+
self.output,
|
|
374
|
+
self.cpp_file,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def __enter__(self) -> EnumBuilder:
|
|
378
|
+
return self
|
|
379
|
+
|
|
380
|
+
def __exit__(self, *exc: object) -> None:
|
|
381
|
+
return None
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""The document scope: one header plus one or more source files, and their output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..doc import CppDoc, FileBanner, HppFile
|
|
10
|
+
from ..errors import ValidationError
|
|
11
|
+
from ..formatting import Formatter
|
|
12
|
+
from ..output import WriteResult, doc_files, write_doc
|
|
13
|
+
from ..writer import render_cpp, render_hpp
|
|
14
|
+
from .base import _DocContext
|
|
15
|
+
from .scopes import _MemberScope
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CppDocBuilder(_MemberScope[CppDoc]):
|
|
19
|
+
"""A whole C++ document: one header and one or more source files."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
file_base: str,
|
|
24
|
+
*,
|
|
25
|
+
description: str | None = None,
|
|
26
|
+
include_guard: str | None = None,
|
|
27
|
+
namespaces: Sequence[str] = (),
|
|
28
|
+
tool_name: str | None = None,
|
|
29
|
+
file_banner: FileBanner | None = None,
|
|
30
|
+
formatter: Formatter | None = None,
|
|
31
|
+
hpp_extension: str = "hpp",
|
|
32
|
+
cpp_extension: str = "cpp",
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Start a document whose files are named after ``file_base``.
|
|
35
|
+
|
|
36
|
+
``include_guard`` defaults to one derived from ``file_base`` and
|
|
37
|
+
``namespaces``; ``namespaces`` is used for nothing else, so pass it when you
|
|
38
|
+
want ``Fw_Cfg_MyClass_HPP`` without spelling the macro out.
|
|
39
|
+
|
|
40
|
+
``formatter`` post-processes every file this document renders; see
|
|
41
|
+
:mod:`fprime_cpp_codegen.formatting`. Any render or write call can override
|
|
42
|
+
it, but cannot switch it off.
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(_DocContext())
|
|
45
|
+
if not file_base:
|
|
46
|
+
raise ValidationError("a document needs a file name base")
|
|
47
|
+
self.file_base = file_base
|
|
48
|
+
self.description = description if description is not None else file_base
|
|
49
|
+
self.hpp_extension = hpp_extension
|
|
50
|
+
self.cpp_extension = cpp_extension
|
|
51
|
+
self.tool_name = tool_name
|
|
52
|
+
self.formatter = formatter
|
|
53
|
+
"""Applied to every file this document renders, unless a call overrides it."""
|
|
54
|
+
|
|
55
|
+
self.file_banner = file_banner
|
|
56
|
+
"""Overrides the ``\\title``/``\\author``/``\\brief`` block atop each file.
|
|
57
|
+
Distinct from :meth:`banner`, which emits a section comment."""
|
|
58
|
+
|
|
59
|
+
self.include_guard = (
|
|
60
|
+
include_guard
|
|
61
|
+
if include_guard is not None
|
|
62
|
+
else _default_guard(file_base, namespaces, hpp_extension)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def hpp_name(self) -> str:
|
|
67
|
+
"""The header file name, e.g. ``"MyClass.hpp"``."""
|
|
68
|
+
return f"{self.file_base}.{self.hpp_extension}"
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def cpp_name(self) -> str:
|
|
72
|
+
"""The default source file name, e.g. ``"MyClass.cpp"``."""
|
|
73
|
+
return f"{self.file_base}.{self.cpp_extension}"
|
|
74
|
+
|
|
75
|
+
def build(self) -> CppDoc:
|
|
76
|
+
return CppDoc(
|
|
77
|
+
description=self.description,
|
|
78
|
+
hpp_file=HppFile(self.hpp_name, self.include_guard),
|
|
79
|
+
cpp_file_name=self.cpp_name,
|
|
80
|
+
members=self._built_members(),
|
|
81
|
+
tool_name=self.tool_name,
|
|
82
|
+
banner=self.file_banner,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# -- output -------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def _formatter(self, override: Formatter | None) -> Formatter | None:
|
|
88
|
+
return override if override is not None else self.formatter
|
|
89
|
+
|
|
90
|
+
def render_hpp(self, *, formatter: Formatter | None = None) -> str:
|
|
91
|
+
"""Render the header as text."""
|
|
92
|
+
text = render_hpp(self.build())
|
|
93
|
+
chosen = self._formatter(formatter)
|
|
94
|
+
return chosen(text, self.hpp_name) if chosen else text
|
|
95
|
+
|
|
96
|
+
def render_cpp(
|
|
97
|
+
self, cpp_file: str | None = None, *, formatter: Formatter | None = None
|
|
98
|
+
) -> str:
|
|
99
|
+
"""Render one source file as text. ``None`` selects the default one."""
|
|
100
|
+
text = render_cpp(self.build(), cpp_file)
|
|
101
|
+
chosen = self._formatter(formatter)
|
|
102
|
+
if not chosen:
|
|
103
|
+
return text
|
|
104
|
+
name = f"{cpp_file}.{self.cpp_extension}" if cpp_file else self.cpp_name
|
|
105
|
+
return chosen(text, name)
|
|
106
|
+
|
|
107
|
+
def files(
|
|
108
|
+
self,
|
|
109
|
+
cpp_files: Sequence[str] | None = None,
|
|
110
|
+
*,
|
|
111
|
+
formatter: Formatter | None = None,
|
|
112
|
+
) -> dict[str, str]:
|
|
113
|
+
"""Render every file this document owns, as a name-to-text mapping."""
|
|
114
|
+
return doc_files(self.build(), cpp_files, formatter=self._formatter(formatter))
|
|
115
|
+
|
|
116
|
+
def write(
|
|
117
|
+
self,
|
|
118
|
+
directory: str | Path = ".",
|
|
119
|
+
cpp_files: Sequence[str] | None = None,
|
|
120
|
+
*,
|
|
121
|
+
formatter: Formatter | None = None,
|
|
122
|
+
skip_unchanged: bool = True,
|
|
123
|
+
encoding: str = "utf-8",
|
|
124
|
+
) -> WriteResult:
|
|
125
|
+
"""Write every file this document owns into ``directory``.
|
|
126
|
+
|
|
127
|
+
Supplemental source files are discovered automatically.
|
|
128
|
+
"""
|
|
129
|
+
return write_doc(
|
|
130
|
+
self.build(),
|
|
131
|
+
directory,
|
|
132
|
+
cpp_files,
|
|
133
|
+
formatter=self._formatter(formatter),
|
|
134
|
+
skip_unchanged=skip_unchanged,
|
|
135
|
+
encoding=encoding,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def __enter__(self) -> CppDocBuilder:
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def __exit__(self, *exc: object) -> None:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _default_guard(
|
|
146
|
+
file_base: str, namespaces: Sequence[str], hpp_extension: str
|
|
147
|
+
) -> str:
|
|
148
|
+
"""Derive an include-guard macro from the file base, namespaces and extension.
|
|
149
|
+
|
|
150
|
+
``_default_guard("MyClass", ["Fw", "Cfg"], "hpp")`` gives ``"Fw_Cfg_MyClass_HPP"``.
|
|
151
|
+
Namespace arguments may themselves be qualified with ``::`` or ``.``.
|
|
152
|
+
"""
|
|
153
|
+
parts = [part for ns in namespaces for part in re.split(r"::|\.", ns) if part]
|
|
154
|
+
ident = re.sub(r"[^A-Za-z0-9_]+", "_", "_".join([*parts, file_base])).strip("_")
|
|
155
|
+
return f"{ident}_{hpp_extension.upper()}"
|