# Released under the MIT License. See LICENSE for details.
#
"""Generate the engine's special-character table from the shared enum.
:class:`bacommon.text.SpecialChar` is the one list of the custom
characters the engine can draw. This emits it as
``base/generated/special_chars.h`` (the C++ enum plus each character's
UTF-8 form), so the engine cannot disagree with Python code and
servers that use the enum directly.
"""
from __future__ import annotations # Docs-generation hack.
import os
from bacommon.text import SpecialChar
[docs]
def cpp_name(char: SpecialChar) -> str:
"""``LEFT_ARROW`` -> ``kLeftArrow``."""
return 'k' + ''.join(part.capitalize() for part in char.name.split('_'))
def _cpp_string(value: str) -> str:
"""A string as a C++ literal of escaped UTF-8 bytes."""
return '"' + ''.join(f'\\x{b:02x}' for b in value.encode()) + '"'
[docs]
def generate_cpp() -> str:
"""Return the header's contents."""
guard = 'BALLISTICA_BASE_GENERATED_SPECIAL_CHARS_H_'
out = [
'// Released under the MIT License. See LICENSE for details.',
'//',
'// AUTO-GENERATED by `tools/pcommand gen_special_chars_cpp`.',
'// DO NOT EDIT BY HAND.',
'//',
'// Generated from SpecialChar in tools/bacommon/text.py.',
'',
f'#ifndef {guard}',
f'#define {guard}',
'',
'#include <cstdint>',
'',
'namespace ballistica::base {',
'',
'/// Special characters the engine can display.',
'enum class SpecialChar : uint8_t {',
]
out += [f' {cpp_name(char)},' for char in SpecialChar]
out += [
' kLast // Sentinel.',
'};',
'',
'/// The UTF-8 form of each special character, indexed by its',
'/// SpecialChar value.',
'inline constexpr const char* kSpecialCharStrings[] = {',
]
out += [
f' {_cpp_string(char.value)}, // {cpp_name(char)}'
for char in SpecialChar
]
out += [
'};',
'',
'} // namespace ballistica::base',
'',
f'#endif // {guard}',
'',
]
return '\n'.join(out)
[docs]
def write_cpp(out_path: str) -> None:
"""Write the header, leaving unchanged bytes alone."""
contents = generate_cpp()
os.makedirs(os.path.dirname(out_path), exist_ok=True)
if os.path.exists(out_path):
with open(out_path, encoding='utf-8') as infile:
if infile.read() == contents:
# Leave the bytes alone (no spurious rebuilds
# downstream) but bump the mtime so make sees the
# target as newer than its sources.
os.utime(out_path, None)
return
with open(out_path, 'w', encoding='utf-8') as outfile:
outfile.write(contents)
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self, TypeIs)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum