• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aas-core-works / aas-core-codegen / 23051946462

13 Mar 2026 12:59PM UTC coverage: 83.657% (+1.2%) from 82.428%
23051946462

push

github

web-flow
Turn on coveralls (#596)

The site coveralls.io was down so we had to turn off the coverage
upload temporarily.

30831 of 36854 relevant lines covered (83.66%)

3.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.62
/aas_core_codegen/cpp/common.py
1
"""Provide common functions shared among different C++ code generation modules."""
2

3
import io
4✔
4
import math
4✔
5
import re
4✔
6
from typing import List, Tuple, Optional
4✔
7

8
from icontract import ensure, require
4✔
9

10
from aas_core_codegen import intermediate
4✔
11
from aas_core_codegen.common import (
4✔
12
    Stripped,
13
    assert_never,
14
    Identifier,
15
    indent_but_first_line,
16
)
17
from aas_core_codegen.cpp import (
4✔
18
    naming as cpp_naming,
19
)
20

21
# See: https://google.github.io/styleguide/cppguide.html#Spaces_vs._Tabs
22
INDENT = "  "
4✔
23
INDENT2 = INDENT * 2
4✔
24
INDENT3 = INDENT * 3
4✔
25
INDENT4 = INDENT * 4
4✔
26
INDENT5 = INDENT * 5
4✔
27
INDENT6 = INDENT * 6
4✔
28
INDENT7 = INDENT * 7
4✔
29
INDENT8 = INDENT * 8
4✔
30

31
WARNING = Stripped(
4✔
32
    """\
33
// This code has been automatically generated by aas-core-codegen.
34
// Do NOT edit or append."""
35
)
36

37

38
@ensure(lambda result: result.startswith('L"'))
4✔
39
@ensure(lambda result: result.endswith('"'))
4✔
40
def wstring_literal(text: str) -> Stripped:
4✔
41
    """Generate a C++ string literal from the ``text``."""
42
    escaped = []  # type: List[str]
4✔
43

44
    for character in text:
4✔
45
        code_point = ord(character)
4✔
46

47
        if character == "\a":
4✔
48
            escaped.append("\\a")
×
49
        elif character == "\b":
4✔
50
            escaped.append("\\b")
×
51
        elif character == "\f":
4✔
52
            escaped.append("\\f")
×
53
        elif character == "\n":
4✔
54
            escaped.append("\\n")
×
55
        elif character == "\r":
4✔
56
            escaped.append("\\r")
×
57
        elif character == "\t":
4✔
58
            escaped.append("\\t")
×
59
        elif character == "\v":
4✔
60
            escaped.append("\\v")
×
61
        elif character == '"':
4✔
62
            escaped.append('\\"')
×
63
        elif character == "\\":
4✔
64
            escaped.append("\\\\")
4✔
65
        elif code_point < 32:
4✔
66
            # Non-printable ASCII characters
67
            escaped.append(f"\\x{ord(character):x}")
×
68
        elif code_point <= 127:
4✔
69
            # ASCII
70
            escaped.append(character)
4✔
71
        elif 127 < code_point < 255:
×
72
            # Above ASCII, but can be encoded as a single byte
73
            escaped.append(f"\\x{ord(character):x}")
×
74
        elif 255 <= code_point < 65536:
×
75
            # Above ASCII
76
            escaped.append(f"\\u{ord(character):04x}")
×
77
        elif code_point >= 65536:
×
78
            # Above Unicode Binary Multilingual Pane
79
            escaped.append(f"\\U{ord(character):08x}")
×
80
        else:
81
            escaped.append(character)
×
82

83
    # NOTE (mristin):
84
    # We use std::wstring, therefore ``L`` prefix.
85
    return Stripped('L"{}"'.format("".join(escaped)))
4✔
86

87

88
@require(lambda character: len(character) == 1)
4✔
89
def wchar_literal(character: str) -> Stripped:
4✔
90
    """Generate a C++ wide character literal from the ``character``."""
91
    code_point = ord(character)
4✔
92

93
    escaped: str
94

95
    if character == "\a":
4✔
96
        escaped = "L'\\a'"
×
97
    elif character == "\b":
4✔
98
        escaped = "L'\\b'"
×
99
    elif character == "\f":
4✔
100
        escaped = "L'\\f'"
×
101
    elif character == "\n":
4✔
102
        escaped = "L'\\n'"
4✔
103
    elif character == "\r":
4✔
104
        escaped = "L'\\r'"
4✔
105
    elif character == "\t":
4✔
106
        escaped = "L'\\t'"
4✔
107
    elif character == "\v":
4✔
108
        escaped = "L'\\v'"
×
109
    elif character == "'":
4✔
110
        escaped = "L'\\''"
4✔
111
    elif character == "\\":
4✔
112
        escaped = "L'\\\\'"
4✔
113
    elif code_point < 32:
4✔
114
        # Non-printable ASCII characters
115
        escaped = f"L'\\x{ord(character):x}'"
×
116
    elif code_point <= 127:
4✔
117
        # ASCII
118
        escaped = f"L'{character}'"
4✔
119
    elif 127 < code_point < 255:
4✔
120
        # Above ASCII, but can be encoded as a single byte
121
        escaped = f"L'\\x{ord(character):x}'"
4✔
122
    elif 0xD800 <= code_point <= 0xDFFF:
4✔
123
        # NOTE (mristin):
124
        # These are the surrogate points and can not be represented as wide character
125
        # literals directly as common compilers such as MSVC++ will complain.
126
        #
127
        # We have to fool the compiler at this point as we deliberately want to model
128
        # the surrogate point.
129
        escaped = f"static_cast<wchar_t>(0x{ord(character):04x})"
4✔
130

131
    # NOTE (mristin):
132
    # Mind the intersecting range for surrogate points just above if you ever convert
133
    # this if-elif-else statement into a mapping or pattern matching.
134
    elif 255 <= code_point < 65536:
4✔
135
        # Above ASCII
136
        escaped = f"L'\\u{ord(character):04x}'"
4✔
137
    elif code_point >= 65536:
4✔
138
        # Above Unicode Basic Multilingual Pane
139
        escaped = f"L'\\U{ord(character):08x}'"
4✔
140
    else:
141
        raise AssertionError(f"Unexpected unhandled character: {character!r}")
×
142

143
    return Stripped(escaped)
4✔
144

145

146
# fmt: off
147
# NOTE (mristin):
148
# We use a pre-condition here to simplify the client code. The client must check
149
# before if the input text is all in ASCII, and report to the user if there are
150
# any non-ASCII characters in the input.
151
@require(
4✔
152
    lambda text:
153
    all(
154
        ord(character) <= 127
155
        for character in text
156
    ),
157
    "Only ASCII text can be converted to a C++ string literal, otherwise encoding "
158
    "must be assumed."
159
)
160
@ensure(lambda result: result.startswith('"'))
4✔
161
@ensure(lambda result: result.endswith('"'))
4✔
162
# fmt: on
163
def string_literal(text: str) -> Stripped:
4✔
164
    """Generate a C++ string literal from the ``text``."""
165
    escaped = []  # type: List[str]
4✔
166

167
    for character in text:
4✔
168
        code_point = ord(character)
4✔
169

170
        if character == "\a":
4✔
171
            escaped.append("\\a")
×
172
        elif character == "\b":
4✔
173
            escaped.append("\\b")
×
174
        elif character == "\f":
4✔
175
            escaped.append("\\f")
×
176
        elif character == "\n":
4✔
177
            escaped.append("\\n")
×
178
        elif character == "\r":
4✔
179
            escaped.append("\\r")
×
180
        elif character == "\t":
4✔
181
            escaped.append("\\t")
×
182
        elif character == "\v":
4✔
183
            escaped.append("\\v")
×
184
        elif character == '"':
4✔
185
            escaped.append('\\"')
×
186
        elif character == "\\":
4✔
187
            escaped.append("\\\\")
×
188
        elif code_point < 32:
4✔
189
            # Non-printable ASCII characters
190
            escaped.append(f"\\x{ord(character):x}")
×
191
        elif code_point <= 127:
4✔
192
            escaped.append(character)
4✔
193
        else:
194
            # Above ASCII
195
            raise ValueError(r"Unexpected non-ascii code point: {character!r}")
×
196

197
    return Stripped('"{}"'.format("".join(escaped)))
4✔
198

199

200
def needs_escaping(text: str) -> bool:
4✔
201
    """Check whether the ``text`` contains a character that needs escaping."""
202
    for character in text:
×
203
        code_point = ord(character)
×
204

205
        if character == "\a":
×
206
            return True
×
207
        elif character == "\b":
×
208
            return True
×
209
        elif character == "\f":
×
210
            return True
×
211
        elif character == "\n":
×
212
            return True
×
213
        elif character == "\r":
×
214
            return True
×
215
        elif character == "\t":
×
216
            return True
×
217
        elif character == "\v":
×
218
            return True
×
219
        elif character == '"':
×
220
            return True
×
221
        elif character == "\\":
×
222
            return True
×
223
        elif code_point < 32:
×
224
            # Non-printable ASCII characters
225
            return True
×
226
        elif code_point <= 127:
×
227
            # ASCII
228
            continue
×
229
        elif 127 < code_point < 255:
×
230
            # Above ASCII, but can be encoded as a single byte
231
            return True
×
232
        elif 255 <= code_point < 65536:
×
233
            # Above ASCII
234
            return True
×
235
        elif code_point >= 65536:
×
236
            # Above Unicode Binary Multilingual Pane
237
            return True
×
238
        else:
239
            raise AssertionError(f"Unexpected unhandled character: {character!r}")
×
240

241
    return False
×
242

243

244
def boolean_literal(value: bool) -> Stripped:
4✔
245
    """Generate the boolean literal corresponding to the ``value``."""
246
    return Stripped("true") if value else Stripped("false")
×
247

248

249
def float_literal(value: float) -> Stripped:
4✔
250
    """Generate the float literal.
251

252
    We assume that the precision of the literal is not critical and rely on
253
    Python's ``str(.)`` function. However, if you want to specify the exact
254
    number, you have to format the number yourself, probably using G17 representation.
255
    """
256
    if math.isnan(value):
4✔
257
        return Stripped("std::numeric_limits<double>::quiet_NaN()")
×
258
    if value == math.inf:
4✔
259
        return Stripped("std::numeric_limits<double>::infinity()")
×
260
    elif value == -math.inf:
4✔
261
        return Stripped("-std::numeric_limits<double>::infinity()")
×
262
    else:
263
        return Stripped(str(value))
4✔
264

265

266
def bytes_literal(value: bytes) -> Tuple[Stripped, bool]:
4✔
267
    """
268
    Generate an expression representing the ``value``.
269

270
    If there are more than 8 bytes, a multi-line expression is returned.
271

272
    :param value: to be represented
273
    :return: (C++ expression, is multi-line)
274
    """
275
    if len(value) == 0:
×
276
        return Stripped("std::vector<std::uint8_t>()"), False
×
277

278
    writer = io.StringIO()
×
279

280
    # noinspection PyUnusedLocal
281
    multi_line = None  # type: Optional[bool]
×
282

283
    if len(value) <= 8:
×
284
        items_joined = ", ".join(f"0x{byte:02x}" for byte in value)
×
285
        return Stripped(f"{{{items_joined}}}"), False
×
286
    else:
287
        writer.write(
×
288
            """\
289
{"""
290
        )
291

292
        for start in range(0, len(value), 8):
×
293
            if start == 0:
×
294
                writer.write(f"\n{INDENT}")
×
295
            else:
296
                writer.write(f",\n{INDENT}")
×
297

298
            end = min(start + 8, len(value))
×
299

300
            assert start < end
×
301

302
            for i, byte in enumerate(value[start:end]):
×
303
                if i > 0:
×
304
                    writer.write(", ")
×
305

306
                writer.write(f"0x{byte:02x}")
×
307

308
        writer.write("\n}")
×
309
        multi_line = True
×
310

311
        return Stripped(writer.getvalue()), multi_line
×
312

313

314
PRIMITIVE_TYPE_MAP = {
4✔
315
    intermediate.PrimitiveType.BOOL: Stripped("bool"),
316
    intermediate.PrimitiveType.INT: Stripped("int64_t"),
317
    intermediate.PrimitiveType.FLOAT: Stripped("double"),
318
    intermediate.PrimitiveType.STR: Stripped("std::wstring"),
319
    intermediate.PrimitiveType.BYTEARRAY: Stripped("std::vector<std::uint8_t>"),
320
}
321

322

323
def _assert_all_primitive_types_are_mapped() -> None:
4✔
324
    """Assert that we have explicitly mapped all the primitive types to C++."""
325
    all_primitive_literals = set(literal.value for literal in PRIMITIVE_TYPE_MAP)
4✔
326

327
    mapped_primitive_literals = set(
4✔
328
        literal.value for literal in intermediate.PrimitiveType
329
    )
330

331
    all_diff = all_primitive_literals.difference(mapped_primitive_literals)
4✔
332
    mapped_diff = mapped_primitive_literals.difference(all_primitive_literals)
4✔
333

334
    messages = []  # type: List[str]
4✔
335
    if len(mapped_diff) > 0:
4✔
336
        messages.append(
×
337
            f"More primitive maps are mapped than there were defined "
338
            f"in the ``intermediate._types``: {sorted(mapped_diff)}"
339
        )
340

341
    if len(all_diff) > 0:
4✔
342
        messages.append(
×
343
            f"One or more primitive types in the ``intermediate._types`` were not "
344
            f"mapped in PRIMITIVE_TYPE_MAP: {sorted(all_diff)}"
345
        )
346

347
    if len(messages) > 0:
4✔
348
        raise AssertionError("\n\n".join(messages))
×
349

350

351
_assert_all_primitive_types_are_mapped()
4✔
352

353
COMMON_NAMESPACE = Identifier("common")
4✔
354
TYPES_NAMESPACE = Identifier("types")
4✔
355
CONSTANTS_NAMESPACE = Identifier("constants")
4✔
356
VERIFICATION_NAMESPACE = Identifier("verification")
4✔
357
JSONIZATION_NAMESPACE = Identifier("jsonization")
4✔
358
XMLIZATION_NAMESPACE = Identifier("xmlization")
4✔
359
REVM_NAMESPACE = Identifier("revm")
4✔
360
PATTERN_NAMESPACE = Identifier("pattern")
4✔
361

362

363
def generate_primitive_type(primitive_type: intermediate.PrimitiveType) -> Stripped:
4✔
364
    """Generate the C++ type for the given primitive type."""
365
    return PRIMITIVE_TYPE_MAP[primitive_type]
4✔
366

367

368
def primitive_type_is_referencable(primitive_type: intermediate.PrimitiveType) -> bool:
4✔
369
    """Return ``True`` if the primitive type denotes a referencable value in C++."""
370
    if primitive_type is intermediate.PrimitiveType.BOOL:
4✔
371
        return False
4✔
372

373
    elif primitive_type is intermediate.PrimitiveType.INT:
4✔
374
        return False
×
375

376
    elif primitive_type is intermediate.PrimitiveType.FLOAT:
4✔
377
        return False
×
378

379
    elif primitive_type is intermediate.PrimitiveType.STR:
4✔
380
        return True
4✔
381

382
    elif primitive_type is intermediate.PrimitiveType.BYTEARRAY:
4✔
383
        return True
4✔
384
    else:
385
        assert_never(primitive_type)
×
386

387

388
def generate_primitive_type_with_const_ref_if_applicable(
4✔
389
    primitive_type: intermediate.PrimitiveType,
390
) -> Stripped:
391
    """Generate the C++ type and wrap it in ``const T&``, if applicable."""
392
    code = generate_primitive_type(primitive_type)
4✔
393

394
    if primitive_type_is_referencable(primitive_type):
4✔
395
        return Stripped(f"const {code}&")
4✔
396

397
    return code
×
398

399

400
def generate_type(
4✔
401
    type_annotation: intermediate.TypeAnnotationUnion,
402
    types_namespace: Optional[Identifier] = None,
403
    common_namespace: Optional[Identifier] = COMMON_NAMESPACE,
404
) -> Stripped:
405
    """
406
    Generate the C++ type for the given type annotation.
407

408
    If ``types_namespace`` is specified, it is prepended to all our types.
409

410
    If `common_namespace` is specified, it is prepended to ``optional``.
411
    """
412
    if isinstance(type_annotation, intermediate.PrimitiveTypeAnnotation):
4✔
413
        return generate_primitive_type(type_annotation.a_type)
4✔
414

415
    elif isinstance(type_annotation, intermediate.OurTypeAnnotation):
4✔
416
        our_type = type_annotation.our_type
4✔
417

418
        if isinstance(our_type, intermediate.Enumeration):
4✔
419
            enum_name = cpp_naming.enum_name(type_annotation.our_type.name)
4✔
420
            if types_namespace is None:
4✔
421
                return enum_name
4✔
422

423
            return Stripped(f"{types_namespace}::{enum_name}")
4✔
424

425
        elif isinstance(our_type, intermediate.ConstrainedPrimitive):
4✔
426
            return generate_primitive_type(our_type.constrainee)
4✔
427

428
        elif isinstance(
4✔
429
            our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
430
        ):
431
            # NOTE (mristin):
432
            # We always refer to interfaces even in cases of concrete classes without
433
            # concrete descendants since we want to allow enhancing.
434
            interface_name = cpp_naming.interface_name(our_type.name)
4✔
435

436
            type_identifier: str
437

438
            if types_namespace is None:
4✔
439
                type_identifier = interface_name
4✔
440
            else:
441
                type_identifier = f"{types_namespace}::{interface_name}"
4✔
442

443
            assert not type_identifier.endswith(">")
4✔
444
            return Stripped(f"std::shared_ptr<{type_identifier}>")
4✔
445

446
    elif isinstance(type_annotation, intermediate.ListTypeAnnotation):
4✔
447
        item_type = generate_type(
4✔
448
            type_annotation=type_annotation.items, types_namespace=types_namespace
449
        )
450

451
        if "<" not in item_type:
4✔
452
            return Stripped(f"std::vector<{item_type}>")
×
453

454
        return Stripped(
4✔
455
            f"""\
456
std::vector<
457
{INDENT}{indent_but_first_line(item_type, INDENT)}
458
>"""
459
        )
460

461
    elif isinstance(type_annotation, intermediate.OptionalTypeAnnotation):
4✔
462
        value_type = generate_type(
4✔
463
            type_annotation=type_annotation.value, types_namespace=types_namespace
464
        )
465

466
        common_namespace_prefix = (
4✔
467
            "" if common_namespace is None else f"{common_namespace}::"
468
        )
469

470
        if "<" not in value_type:
4✔
471
            return Stripped(f"{common_namespace_prefix}optional<{value_type}>")
4✔
472

473
        return Stripped(
4✔
474
            f"""\
475
{common_namespace_prefix}optional<
476
{INDENT}{indent_but_first_line(value_type, INDENT)}
477
>"""
478
        )
479

480
    else:
481
        assert_never(type_annotation)
×
482

483
    raise AssertionError("Should not have gotten here")
×
484

485

486
def is_referencable(type_annotation: intermediate.TypeAnnotationUnion) -> bool:
4✔
487
    """Return ``True`` if the type annotation denotes a referencable value in C++."""
488
    if isinstance(type_annotation, intermediate.OptionalTypeAnnotation):
4✔
489
        return True
4✔
490

491
    primitive_type = intermediate.try_primitive_type(type_annotation)
4✔
492
    if primitive_type is not None:
4✔
493
        return primitive_type_is_referencable(primitive_type)
4✔
494

495
    if isinstance(type_annotation, intermediate.OurTypeAnnotation) and isinstance(
4✔
496
        type_annotation.our_type, intermediate.Enumeration
497
    ):
498
        return False
4✔
499

500
    return True
4✔
501

502

503
def generate_type_with_const_ref_if_applicable(
4✔
504
    type_annotation: intermediate.TypeAnnotationUnion,
505
    types_namespace: Optional[Identifier] = None,
506
) -> Stripped:
507
    """
508
    Generate the C++ type and wrap it in ``const T&``, if applicable.
509

510
    If ``types_namespace`` is specified, it is prepended to all our types.
511
    """
512
    code = generate_type(
4✔
513
        type_annotation=type_annotation, types_namespace=types_namespace
514
    )
515
    if is_referencable(type_annotation):
4✔
516
        return Stripped(f"const {code}&")
4✔
517

518
    return code
4✔
519

520

521
def generate_type_with_ref(
4✔
522
    type_annotation: intermediate.TypeAnnotationUnion,
523
    types_namespace: Optional[Identifier] = None,
524
) -> Stripped:
525
    """
526
    Generate the C++ type and wrap it as a reference in ``T&``.
527

528
    If ``types_namespace`` is specified, it is prepended to all our types.
529
    """
530
    code = generate_type(
4✔
531
        type_annotation=type_annotation, types_namespace=types_namespace
532
    )
533
    return Stripped(f"{code}&")
4✔
534

535

536
_ANGLE_BRACKETS_IN_TYPE_RE = re.compile(r"\s*([<>])\s*")
4✔
537
_INDENT_LIST = [
4✔
538
    INDENT,
539
    INDENT2,
540
    INDENT3,
541
    INDENT4,
542
    INDENT5,
543
    INDENT6,
544
    INDENT7,
545
    INDENT8,
546
]
547

548

549
def break_type_in_lines(text: str) -> str:
4✔
550
    """Break the given C++ type in multiple lines, if applicable."""
551
    if len(text) == 0:
4✔
552
        return ""
4✔
553

554
    buffer = io.StringIO()
4✔
555

556
    level = 0
4✔
557
    last_match = None  # type: Optional[re.Match[str]]
4✔
558
    for match in _ANGLE_BRACKETS_IN_TYPE_RE.finditer(text):
4✔
559
        if last_match is None:
4✔
560
            buffer.write(text[: match.start()])
4✔
561
        else:
562
            buffer.write(text[last_match.end() : match.start()])
4✔
563

564
        assert (
4✔
565
            0 <= level < len(_INDENT_LIST)
566
        ), f"Expected at most {len(_INDENT_LIST)} levels, but got: {level=}"
567

568
        angle_bracket = match.group(1)
4✔
569
        if angle_bracket == "<":
4✔
570
            buffer.write("<\n" + _INDENT_LIST[level])
4✔
571
            level += 1
4✔
572
        elif angle_bracket == ">":
4✔
573
            level -= 1
4✔
574
            buffer.write(" >")
4✔
575
        else:
576
            raise AssertionError(f"Unhandled angle bracket: {angle_bracket=}")
×
577

578
        last_match = match
4✔
579

580
    if last_match is None:
4✔
581
        # NOTE (mristin):
582
        # Not a signle angle bracket has been detected.
583
        return text
4✔
584

585
    buffer.write(text[last_match.end() :])
4✔
586

587
    return buffer.getvalue()
4✔
588

589

590
class GeneratorForLoopVariables:
4✔
591
    """
592
    Generate a unique variable name based on ``item`` stem.
593

594
    >>> generator = GeneratorForLoopVariables()
595

596
    >>> next(generator)
597
    'v'
598

599
    >>> next(generator)
600
    'v1'
601

602
    >>> next(generator)
603
    'v2'
604
    """
605

606
    def __init__(self) -> None:
4✔
607
        """Initialize with the zero counter."""
608
        self.counter = 0
×
609

610
    def __next__(self) -> Identifier:
4✔
611
        """Generate the next variable name."""
612
        if self.counter == 0:
×
613
            result = Identifier("v")
×
614
        else:
615
            result = Identifier(f"v{self.counter}")
×
616

617
        self.counter += 1
×
618

619
        return result
×
620

621

622
def generate_include_prefix_path(library_namespace: Stripped) -> Stripped:
4✔
623
    """
624
    Generate the prefix path for the includes.
625

626
    >>> generate_include_prefix_path(Stripped("some::name::space"))
627
    'some/name/space'
628
    """
629
    return Stripped(library_namespace.replace("::", "/"))
4✔
630

631

632
def generate_namespace_opening(library_namespace: Stripped) -> Stripped:
4✔
633
    """
634
    Generate the code to open the nested library namespace.
635

636
    >>> generate_namespace_opening(Stripped("some::name::space"))
637
    'namespace some {\\nnamespace name {\\nnamespace space {'
638
    """
639
    namespace_parts = library_namespace.split("::")
4✔
640
    return Stripped("\n".join(f"namespace {part} {{" for part in namespace_parts))
4✔
641

642

643
def generate_namespace_closing(library_namespace: Stripped) -> Stripped:
4✔
644
    """
645
    Generate the code to close the nested library namespace.
646

647
    >>> generate_namespace_closing(Stripped("some::name::space"))
648
    '}  // namespace space\\n}  // namespace name\\n}  // namespace some'
649
    """
650
    namespace_parts = library_namespace.split("::")
4✔
651
    return Stripped(
4✔
652
        "\n".join(f"}}  // namespace {part}" for part in reversed(namespace_parts))
653
    )
654

655

656
def include_guard_var(namespace: Stripped) -> Stripped:
4✔
657
    """
658
    Generate the variable name of the include guard.
659

660
    >>> include_guard_var(Stripped("aascore3::types"))
661
    'AASCORE3_TYPES_GUARD_'
662
    """
663
    return Stripped("_".join(namespace.split("::")).upper() + "_GUARD_")
4✔
664

665

666
def non_documentation_comment(text: str) -> str:
4✔
667
    r"""
668
    Generate the non-documentation comment prefixed with ``//``.
669

670
    >>> non_documentation_comment('test\nme')
671
    '// test\n// me'
672
    """
673
    lines = []  # type: List[str]
×
674
    for line in text.splitlines():
×
675
        stripped = line.strip()
×
676
        if len(stripped) > 0:
×
677
            lines.append(f"// {line}")
×
678
        else:
679
            lines.append("//")
×
680

681
    return "\n".join(lines)
×
682

683

684
def assert_module_docstring_and_generate_header_consistent(
4✔
685
    module_doc: str, generate_header_doc: str
686
) -> None:
687
    """Check that the two docstrings are consistent and raise an exception otherwise."""
688
    generate_code_text = "Generate code "
4✔
689
    if not module_doc.startswith(generate_code_text):
4✔
690
        raise ValueError(
×
691
            f"Expected the module docstring to start "
692
            f"with {generate_code_text!r}, but got: {module_doc}"
693
        )
694

695
    generate_header_text = "Generate header "
4✔
696
    if not generate_header_doc.startswith(generate_header_text):
4✔
697
        raise ValueError(
×
698
            f"Expected the header generator docstring to start "
699
            f"with {generate_header_text!r}, "
700
            f"but got: {generate_header_doc}"
701
        )
702

703
    suffix_module_doc = module_doc[len(generate_code_text) :]
4✔
704
    suffix_generate_header_doc = generate_header_doc[len(generate_header_text) :]
4✔
705

706
    if not suffix_generate_header_doc.startswith(suffix_module_doc):
4✔
707
        raise ValueError(
×
708
            f"Expected the header generator docstring "
709
            f"to include the part of the module docstring, "
710
            f"but got: {generate_header_doc!r} and "
711
            f"the module docstring was {module_doc!r}"
712
        )
713

714

715
def assert_module_docstring_and_generate_implementation_consistent(
4✔
716
    module_doc: str, generate_implementation_doc: str
717
) -> None:
718
    """Check that the two docstrings are consistent and raise an exception otherwise."""
719
    generate_code_text = "Generate code "
4✔
720
    if not module_doc.startswith(generate_code_text):
4✔
721
        raise ValueError(
×
722
            f"Expected the module docstring to start "
723
            f"with {generate_code_text!r}, but got: {module_doc}"
724
        )
725

726
    generate_implementation_text = "Generate implementation "
4✔
727
    if not generate_implementation_doc.startswith(generate_implementation_text):
4✔
728
        raise ValueError(
×
729
            f"Expected the implementation generator docstring to start "
730
            f"with {generate_implementation_text!r}, "
731
            f"but got: {generate_implementation_doc}"
732
        )
733

734
    suffix_module_doc = module_doc[len(generate_code_text) :]
4✔
735
    suffix_generate_implementation_doc = generate_implementation_doc[
4✔
736
        len(generate_implementation_text) :
737
    ]
738

739
    if not suffix_generate_implementation_doc.startswith(suffix_module_doc):
4✔
740
        raise ValueError(
×
741
            f"Expected the implementation generator docstring "
742
            f"to include the part of the module docstring, "
743
            f"but got: {generate_implementation_doc!r} and "
744
            f"the module docstring was {module_doc!r}"
745
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc