code-generation: run ruff

Change-Id: I591247ed56f89feba5daf3cbb94211d72a3ba99c
This commit is contained in:
Commentator2.0
2026-01-10 00:07:33 +00:00
committed by Rutile
parent 0d3fdbdb54
commit 8f9ed687da
8 changed files with 385 additions and 259 deletions
+1
View File
@@ -0,0 +1 @@
# noqa: N999 # consistency with rest of the codebase
+65 -46
View File
@@ -1,15 +1,12 @@
from typing import List, NamedTuple from typing import NamedTuple
from frontmatter import Post
from common import cxx_literal, generate_file, load_data from common import cxx_literal, generate_file, load_data
import argparse
KNOWN_KEYS = {"name", "type", "constructorArgs", "implementation", "impure", "renameInGlobalScope"}
KNOWN_KEYS = set([
'name',
'type',
'constructorArgs',
'implementation',
'impure',
'renameInGlobalScope',
])
class BuiltinConstant(NamedTuple): class BuiltinConstant(NamedTuple):
name: str name: str
@@ -19,75 +16,97 @@ class BuiltinConstant(NamedTuple):
rename_in_global_scope: bool rename_in_global_scope: bool
documentation: str documentation: str
def parse(datum): @classmethod
def parse(cls, datum: Post) -> "BuiltinConstant":
unknown_keys = set(datum.keys()) - KNOWN_KEYS unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys: if unknown_keys:
raise Exception('unknown keys', unknown_keys) msg = f"unknown keys: {unknown_keys!r}"
raise ValueError(msg)
if (constructor_args := datum.get("constructorArgs")) is not None:
args = [f"NewValueAs::{datum['type']}"] + constructor_args # type: ignore
impl = f"{{{','.join(args)}}}"
else:
impl = datum["implementation"]
return BuiltinConstant( return BuiltinConstant(
name = datum['name'], name=datum["name"], # type: ignore
type = datum['type'], type=datum["type"], # type: ignore
implementation = ('{' + ', '.join([f'NewValueAs::{datum["type"]}', *datum['constructorArgs']]) + '}') if 'constructorArgs' in datum else datum['implementation'], implementation=impl,
impure = datum.get('impure', False), impure=datum.get("impure", False), # type: ignore
rename_in_global_scope = datum.get('renameInGlobalScope', True), rename_in_global_scope=datum.get("renameInGlobalScope", True), # type: ignore
documentation = datum.content, documentation=datum.content,
) )
VALUE_TYPES = { VALUE_TYPES = {
'attrs': 'nAttrs', "attrs": "nAttrs",
'boolean': 'nBool', "boolean": "nBool",
'integer': 'nInt', "integer": "nInt",
'list': 'nList', "list": "nList",
'null': 'nNull', "null": "nNull",
'string': 'nString', "string": "nString",
} }
HUMAN_TYPES = { HUMAN_TYPES = {
'attrs': 'set', "attrs": "set",
'boolean': 'Boolean', "boolean": "Boolean",
'integer': 'integer', "integer": "integer",
'list': 'list', "list": "list",
'null': 'null', "null": "null",
'string': 'string', "string": "string",
} }
def main():
import argparse
def main():
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument('--header', help='Path of the header to generate') ap.add_argument("--header", help="Path of the header to generate")
ap.add_argument('--docs', help='Path of the documentation file to generate') ap.add_argument("--docs", help="Path of the documentation file to generate")
ap.add_argument('defs', help='Builtin definition files', nargs='+') ap.add_argument("defs", help="Builtin definition files", nargs="+")
args = ap.parse_args() args = ap.parse_args()
builtin_constants = load_data(args.defs, BuiltinConstant.parse) builtin_constants = load_data(args.defs, BuiltinConstant.parse)
generate_file(args.header, builtin_constants, lambda constant: generate_file(
args.header,
builtin_constants,
lambda constant:
# `builtins` is magic and must come first # `builtins` is magic and must come first
'' if constant.name == 'builtins' else constant.name, "" if constant.name == "builtins" else constant.name,
lambda constant: lambda constant: f"""{"if (!evalSettings.pureEval) " if constant.impure else ""}{{
f'''{'if (!evalSettings.pureEval) ' if constant.impure else ''}{{ addConstant({cxx_literal(("__" if constant.rename_in_global_scope else "") + constant.name)}, {constant.implementation}, {{
addConstant({cxx_literal(('__' if constant.rename_in_global_scope else '') + constant.name)}, {constant.implementation}, {{
.type = {VALUE_TYPES[constant.type]}, .type = {VALUE_TYPES[constant.type]},
.doc = {cxx_literal(constant.documentation)}, .doc = {cxx_literal(constant.documentation)},
.impureOnly = {cxx_literal(constant.impure)}, .impureOnly = {cxx_literal(constant.impure)},
}}); }});
}} }}
''') """,
generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda constant: )
f'''<dt id="builtins-{constant.name}"> generate_file(
args.docs,
builtin_constants,
lambda constant: constant.name,
lambda constant: f"""<dt id="builtins-{constant.name}">
<a href="#builtins-{constant.name}"><code>{constant.name}</code></a> ({HUMAN_TYPES[constant.type]}) <a href="#builtins-{constant.name}"><code>{constant.name}</code></a> ({HUMAN_TYPES[constant.type]})
</dt> </dt>
<dd> <dd>
{constant.documentation} {constant.documentation}
''' + ('''> **Note** """
+ (
"""> **Note**
> >
> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval). > Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
''' if constant.impure else '') + '''</dd> """
if constant.impure
else ""
)
+ """</dd>
''') """,
)
if __name__ == '__main__':
if __name__ == "__main__":
main() main()
+62 -40
View File
@@ -1,61 +1,71 @@
from typing import List, NamedTuple, Optional from typing import NamedTuple
from frontmatter import Post
from build_experimental_features import ExperimentalFeature from build_experimental_features import ExperimentalFeature
from common import cxx_literal, generate_file, load_data from common import cxx_literal, generate_file, load_data
import argparse
KNOWN_KEYS = {"name", "implementation", "renameInGlobalScope", "args", "experimentalFeature"}
KNOWN_KEYS = set([
'name',
'implementation',
'renameInGlobalScope',
'args',
'experimentalFeature',
])
class Builtin(NamedTuple): class Builtin(NamedTuple):
name: str name: str
implementation: str implementation: str
rename_in_global_scope: bool rename_in_global_scope: bool
args: List[str] args: list[str]
experimental_feature: Optional[str] experimental_feature: str | None
documentation: str documentation: str
def parse(datum): @classmethod
def parse(cls, datum: Post) -> "Builtin":
unknown_keys = set(datum.keys()) - KNOWN_KEYS unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys: if unknown_keys:
raise Exception('unknown keys', unknown_keys) msg = f"unknown keys: {unknown_keys!r}"
raise ValueError(msg)
return Builtin( return Builtin(
name = datum['name'], name=datum["name"], # type: ignore
implementation = datum['implementation'] if 'implementation' in datum else f'prim_{datum["name"]}', implementation=datum["implementation"]
rename_in_global_scope = datum.get('renameInGlobalScope', True), if "implementation" in datum
args = datum['args'], else f"prim_{datum['name']}",
experimental_feature = datum.get('experimentalFeature', None), rename_in_global_scope=datum.get("renameInGlobalScope", True), # type: ignore
documentation = datum.content, args=datum["args"], # type: ignore
experimental_feature=datum.get("experimentalFeature", None), # type: ignore
documentation=datum.content,
) )
def main():
import argparse
def main():
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument('--header', help='Path of the header to generate') ap.add_argument("--header", help="Path of the header to generate")
ap.add_argument('--docs', help='Path of the documentation file to generate') ap.add_argument("--docs", help="Path of the documentation file to generate")
ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions') ap.add_argument(
ap.add_argument('defs', help='Builtin definition files', nargs='+') "--experimental-features", help="Directory containing the experimental feature definitions"
)
ap.add_argument("defs", help="Builtin definition files", nargs="+")
args = ap.parse_args() args = ap.parse_args()
builtins = load_data(args.defs, Builtin.parse) builtins = load_data(args.defs, Builtin.parse)
experimental_feature_names = set([builtin.experimental_feature for (_, builtin) in builtins]) experimental_feature_names = {builtin.experimental_feature for (_, builtin) in builtins}
experimental_feature_names.discard(None) experimental_feature_names.discard(None)
experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names] experimental_feature_files = [
f"{args.experimental_features}/{name}.md" for name in experimental_feature_names
]
experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse) experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse)
experimental_features = dict(map(lambda path_and_feature: experimental_features = {
(path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features)) path_and_feature[1].name: f"Xp::{path_and_feature[1].internal_name}"
experimental_features[None] = 'std::nullopt' for path_and_feature in experimental_features
}
experimental_features[None] = "std::nullopt"
generate_file(args.header, builtins, lambda builtin: builtin.name, lambda builtin: generate_file(
f'''{'' if builtin.experimental_feature is None else f'if (experimentalFeatureSettings.isEnabled({experimental_features[builtin.experimental_feature]})) '}{{ args.header,
builtins,
lambda builtin: builtin.name,
lambda builtin: f"""{"" if builtin.experimental_feature is None else f"if (experimentalFeatureSettings.isEnabled({experimental_features[builtin.experimental_feature]})) "}{{
addPrimOp({{ addPrimOp({{
.name = {cxx_literal(('__' if builtin.rename_in_global_scope else '') + builtin.name)}, .name = {cxx_literal(("__" if builtin.rename_in_global_scope else "") + builtin.name)},
.args = {cxx_literal(builtin.args)}, .args = {cxx_literal(builtin.args)},
.arity = {len(builtin.args)}, .arity = {len(builtin.args)},
.doc = {cxx_literal(builtin.documentation)}, .doc = {cxx_literal(builtin.documentation)},
@@ -63,20 +73,32 @@ def main():
.experimentalFeature = {experimental_features[builtin.experimental_feature]}, .experimentalFeature = {experimental_features[builtin.experimental_feature]},
}}); }});
}} }}
''') """,
generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda builtin: )
f'''<dt id="builtins-{builtin.name}"> generate_file(
<a href="#builtins-{builtin.name}"><code>{builtin.name} {' '.join([f'<var>{arg}</var>' for arg in builtin.args])}</code></a> args.docs,
builtins,
lambda builtin: builtin.name,
lambda builtin: f"""<dt id="builtins-{builtin.name}">
<a href="#builtins-{builtin.name}"><code>{builtin.name} {" ".join([f"<var>{arg}</var>" for arg in builtin.args])}</code></a>
</dt> </dt>
<dd> <dd>
{builtin.documentation} {builtin.documentation}
''' + (f'''This function is only available if the [{builtin.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{builtin.experimental_feature}) experimental feature is enabled. """
+ (
f"""This function is only available if the [{builtin.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{builtin.experimental_feature}) experimental feature is enabled.
''' if builtin.experimental_feature is not None else '') + '''</dd> """
if builtin.experimental_feature is not None
else ""
)
+ """</dd>
''') """,
)
if __name__ == '__main__':
if __name__ == "__main__":
main() main()
@@ -1,58 +1,77 @@
from typing import NamedTuple from typing import NamedTuple
from frontmatter import Post
from common import cxx_literal, generate_file, load_data from common import cxx_literal, generate_file, load_data
import argparse
KNOWN_KEYS = {"name", "internalName"}
KNOWN_KEYS = set([
'name',
'internalName',
])
class ExperimentalFeature(NamedTuple): class ExperimentalFeature(NamedTuple):
name: str name: str
internal_name: str internal_name: str
description: str description: str
def parse(datum): @classmethod
def parse(cls, datum: Post) -> "ExperimentalFeature":
unknown_keys = set(datum.keys()) - KNOWN_KEYS unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys: if unknown_keys:
raise ValueError('unknown keys', unknown_keys) msg = f"unknown keys: {unknown_keys!r}"
raise ValueError(msg)
return ExperimentalFeature( return ExperimentalFeature(
name = datum['name'], name=datum["name"], # type: ignore
internal_name = datum['internalName'], internal_name=datum["internalName"], # type: ignore
description = datum.content, description=datum.content,
) )
def main():
import argparse
def main():
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument('--deprecated', action='store_true', help='Generate deprecated features') ap.add_argument("--deprecated", action="store_true", help="Generate deprecated features")
ap.add_argument('--header', help='Path of the declaration header to generate') ap.add_argument("--header", help="Path of the declaration header to generate")
ap.add_argument('--impl-header', help='Path of the implementation header to generate') ap.add_argument("--impl-header", help="Path of the implementation header to generate")
ap.add_argument('--descriptions', help='Path of the description file to generate') ap.add_argument("--descriptions", help="Path of the description file to generate")
ap.add_argument('--shortlist', help='Path of the shortlist file to generate') ap.add_argument("--shortlist", help="Path of the shortlist file to generate")
ap.add_argument('defs', help='Experimental feature definition files', nargs='+') ap.add_argument("defs", help="Experimental feature definition files", nargs="+")
args = ap.parse_args() args = ap.parse_args()
features = load_data(args.defs, ExperimentalFeature.parse) features = load_data(args.defs, ExperimentalFeature.parse)
generate_file(args.header, features, lambda feature: feature.name, lambda feature: generate_file(
f' {feature.internal_name},\n') args.header,
generate_file(args.impl_header, features, lambda feature: feature.name, lambda feature: features,
f''' {{ lambda feature: feature.name,
lambda feature: f" {feature.internal_name},\n",
)
generate_file(
args.impl_header,
features,
lambda feature: feature.name,
lambda feature: f""" {{
.tag = {"Dep" if args.deprecated else "Xp"}::{feature.internal_name}, .tag = {"Dep" if args.deprecated else "Xp"}::{feature.internal_name},
.name = {cxx_literal(feature.name)}, .name = {cxx_literal(feature.name)},
.description = {cxx_literal(feature.description)}, .description = {cxx_literal(feature.description)},
}}, }},
''') """,
generate_file(args.descriptions, features, lambda feature: feature.name, lambda feature: )
f'''## [`{feature.name}`]{{#{"dp" if args.deprecated else "xp"}-feature-{feature.name}}} generate_file(
args.descriptions,
features,
lambda feature: feature.name,
lambda feature: f"""## [`{feature.name}`]{{#{"dp" if args.deprecated else "xp"}-feature-{feature.name}}}
{feature.description} {feature.description}
''') """,
generate_file(args.shortlist, features, lambda feature: feature.name, lambda feature: )
f' - [`{feature.name}`](@docroot@/contributing/{"deprecated" if args.deprecated else "experimental"}-features.md#{"dp" if args.deprecated else "xp"}-feature-{feature.name})\n') generate_file(
args.shortlist,
features,
lambda feature: feature.name,
lambda feature: f" - [`{feature.name}`](@docroot@/contributing/{'deprecated' if args.deprecated else 'experimental'}-features.md#{'dp' if args.deprecated else 'xp'}-feature-{feature.name})\n",
)
if __name__ == '__main__':
if __name__ == "__main__":
main() main()
+124 -75
View File
@@ -1,101 +1,121 @@
from typing import List, NamedTuple, Optional from typing import NamedTuple, Any
from frontmatter import Post
from build_experimental_features import ExperimentalFeature from build_experimental_features import ExperimentalFeature
from common import cxx_literal, generate_file, load_data from common import cxx_literal, generate_file, load_data
import argparse
KNOWN_KEYS = {
"name",
"internalName",
"platforms",
"type",
"settingType",
"default",
"defaultExpr",
"defaultText",
"aliases",
"experimentalFeature",
"deprecated",
}
KNOWN_KEYS = set([
'name',
'internalName',
'platforms',
'type',
'settingType',
'default',
'defaultExpr',
'defaultText',
'aliases',
'experimentalFeature',
'deprecated',
])
class Setting(NamedTuple): class Setting(NamedTuple):
name: str name: str
internal_name: str internal_name: str
description: str description: str
platforms: Optional[List[str]] platforms: list[str] | None
setting_type: str setting_type: str
default_expr: str default_expr: str
default_text: str default_text: str
aliases: List[str] aliases: list[str]
experimental_feature: Optional[str] experimental_feature: str | None
deprecated: bool deprecated: bool
def parse(datum): @classmethod
def parse(cls, datum: Post) -> "Setting":
unknown_keys = set(datum.keys()) - KNOWN_KEYS unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys: if unknown_keys:
raise ValueError('unknown keys', unknown_keys) msg = f"unknown keys: {unknown_keys!r}"
default_text = f'`{nix_conf_literal(datum["default"])}`' if 'default' in datum else datum['defaultText'] raise ValueError(msg)
if default_text == '``': default_text = (
default_text = '*empty*' f"`{nix_conf_literal(datum['default'])}`"
if "default" in datum
else datum["defaultText"]
)
if default_text == "``":
default_text = "*empty*"
return Setting( return Setting(
name = datum['name'], name=datum["name"], # type: ignore
internal_name = datum['internalName'], internal_name=datum["internalName"], # type: ignore
description = datum.content, description=datum.content,
platforms = datum.get('platforms', None), platforms=datum.get("platforms", None), # type: ignore
setting_type = f'Setting<{datum["type"]}>' if 'type' in datum else datum['settingType'], setting_type=f"Setting<{datum['type']}>" if "type" in datum else datum["settingType"],
default_expr = cxx_literal(datum['default']) if 'default' in datum else datum['defaultExpr'], default_expr=cxx_literal(datum["default"])
default_text = default_text, if "default" in datum
aliases = datum.get('aliases', []), else datum["defaultExpr"],
experimental_feature = datum.get('experimentalFeature', None), default_text=default_text,
deprecated = datum.get('deprecated', False), aliases=datum.get("aliases", []), # type: ignore
experimental_feature=datum.get("experimentalFeature", None), # type: ignore
deprecated=datum.get("deprecated", False), # type: ignore
) )
platform_names = {
'darwin': 'Darwin',
'linux': 'Linux',
}
def nix_conf_literal(v): platform_names = {"darwin": "Darwin", "linux": "Linux"}
def nix_conf_literal(v: Any) -> str:
if v is None: if v is None:
return '' return ""
elif isinstance(v, bool) and v == False: # 0 == False if v is False:
return 'false' return "false"
elif isinstance(v, bool) and v == True: # 1 == True if v is True:
return 'true' return "true"
elif isinstance(v, int): if isinstance(v, int):
return str(v) return str(v)
elif isinstance(v, str): if isinstance(v, str):
return v return v
elif isinstance(v, list): if isinstance(v, list):
return ' '.join([nix_conf_literal(item) for item in v]) return " ".join([nix_conf_literal(item) for item in v])
else: msg = f"Cannot represent {v!r} in nix.conf"
raise NotImplementedError(f'Cannot represent {repr(v)} in nix.conf') raise NotImplementedError(msg)
def indent(prefix: str, body: str) -> str:
return "".join(["\n" if not line else f"{prefix}{line}\n" for line in body.split("\n")])
def indent(prefix, body):
return ''.join(['\n' if line == '' else f'{prefix}{line}\n' for line in body.split('\n')])
def main(): def main():
import argparse
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument('--kernel', help='Name of the kernel Lix will run on') ap.add_argument("--kernel", help="Name of the kernel Lix will run on")
ap.add_argument('--header', help='Path of the header to generate') ap.add_argument("--header", help="Path of the header to generate")
ap.add_argument('--docs', help='Path of the documentation file to generate') ap.add_argument("--docs", help="Path of the documentation file to generate")
ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions') ap.add_argument(
ap.add_argument('defs', help='Setting definition files', nargs='+') "--experimental-features", help="Directory containing the experimental feature definitions"
)
ap.add_argument("defs", help="Setting definition files", nargs="+")
args = ap.parse_args() args = ap.parse_args()
settings = load_data(args.defs, Setting.parse) settings = load_data(args.defs, Setting.parse)
experimental_feature_names = set([setting.experimental_feature for (_, setting) in settings]) experimental_feature_names = {setting.experimental_feature for (_, setting) in settings}
experimental_feature_names.discard(None) experimental_feature_names.discard(None)
experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names] experimental_feature_files = [
f"{args.experimental_features}/{name}.md" for name in experimental_feature_names
]
experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse) experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse)
experimental_features = dict(map(lambda path_and_feature: experimental_features = {
(path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features)) path_and_feature[1].name: f"Xp::{path_and_feature[1].internal_name}"
experimental_features[None] = 'std::nullopt' for path_and_feature in experimental_features
}
experimental_features[None] = "std::nullopt"
generate_file(args.header, settings, lambda setting: setting.name, lambda setting: generate_file(
f'''{setting.setting_type} {setting.internal_name} {{ args.header,
settings,
lambda setting: setting.name,
lambda setting: f"""{setting.setting_type} {setting.internal_name} {{
this, this,
{setting.default_expr}, {setting.default_expr},
{cxx_literal(setting.name)}, {cxx_literal(setting.name)},
@@ -106,15 +126,28 @@ def main():
{cxx_literal(setting.deprecated)} {cxx_literal(setting.deprecated)}
}}; }};
''' if setting.platforms is None or args.kernel in setting.platforms else '') """
generate_file(args.docs, settings, lambda setting: setting.name, lambda setting: if setting.platforms is None or args.kernel in setting.platforms
f'''- <span id="conf-{setting.name}">[`{setting.name}`](#conf-{setting.name})</span> else "",
)
generate_file(
args.docs,
settings,
lambda setting: setting.name,
lambda setting: f"""- <span id="conf-{setting.name}">[`{setting.name}`](#conf-{setting.name})</span>
{indent(" ", setting.description)} {indent(" ", setting.description)}
''' + (f''' > **Note** """
> This setting is only available on {', '.join([platform_names[platform] for platform in setting.platforms])} systems. + (
f""" > **Note**
> This setting is only available on {", ".join([platform_names[platform] for platform in setting.platforms])} systems.
''' if setting.platforms is not None else '') + (f''' > **Warning** """
if setting.platforms is not None
else ""
)
+ (
f""" > **Warning**
> This setting is part of an > This setting is part of an
> [experimental feature](@docroot@/contributing/experimental-features.md). > [experimental feature](@docroot@/contributing/experimental-features.md).
@@ -128,14 +161,30 @@ def main():
{setting.name} = ... {setting.name} = ...
``` ```
''' if setting.experimental_feature is not None else '') + (''' > **Warning** """
if setting.experimental_feature is not None
else ""
)
+ (
""" > **Warning**
> This setting is deprecated and will be removed in a future version of Lix. > This setting is deprecated and will be removed in a future version of Lix.
''' if setting.deprecated else '') + f''' **Default:** {setting.default_text} """
if setting.deprecated
else ""
)
+ f""" **Default:** {setting.default_text}
''' + (f''' **Deprecated alias:** {', '.join([f'`{item}`' for item in setting.aliases])} """
+ (
f""" **Deprecated alias:** {", ".join([f"`{item}`" for item in setting.aliases])}
''' if setting.aliases != [] else '')) """
if setting.aliases != []
else ""
),
)
if __name__ == '__main__':
if __name__ == "__main__":
main() main()
+34 -30
View File
@@ -1,4 +1,5 @@
#!@python@ #!@python@
# ruff: noqa: SIM112 # ignore lowercase env variable names for capnpc as we have them in lower case as arguments
import argparse import argparse
import capnp import capnp
@@ -7,58 +8,61 @@ import os
import subprocess import subprocess
import sys import sys
if lang := os.environ.get('lix_capnp_lang'): if lang := os.environ.get("lix_capnp_lang"):
outputs = os.environ['lix_capnp_outputs'].split() outputs = os.environ["lix_capnp_outputs"].split()
old_cwd = os.environ['lix_capnp_old_cwd'] old_cwd = os.environ["lix_capnp_old_cwd"]
schema = capnp.load('@capnp_include@/capnp/schema.capnp', imports=['@capnp_include@']) schema = capnp.load("@capnp_include@/capnp/schema.capnp", imports=["@capnp_include@"])
request = schema.CodeGeneratorRequest.read(sys.stdin) request = schema.CodeGeneratorRequest.read(sys.stdin)
subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode() subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode()
base_dir = os.getcwd() base_dir = Path.cwd()
os.chdir(old_cwd) os.chdir(old_cwd)
include = [ str(Path(p).resolve()) for p in os.environ['lix_capnp_include'].split(':') ] include = [str(Path(p).resolve()) for p in os.environ["lix_capnp_include"].split(":")]
if depfile := os.environ['lix_capnp_depfile']: if depfile := os.environ["lix_capnp_depfile"]:
deps = "" deps = ""
for input in request.requestedFiles: for input_file in request.requestedFiles:
deps += " ".join(f"{input.filename}.{o}" for o in outputs) deps += " ".join(f"{input_file.filename}.{o}" for o in outputs)
deps += ":" deps += ":"
for dep in input.imports: for dep in input_file.imports:
if dep.name.startswith("/"): if dep.name.startswith("/"):
for candidate in (Path(i + dep.name) for i in include): for candidate in (Path(i + dep.name) for i in include):
if candidate.exists(): if candidate.exists():
deps += " " + str(candidate) deps += " " + str(candidate)
break break
else: else:
raise RuntimeError("not handling relative includes") msg = "not handling relative includes"
raise RuntimeError(msg)
deps += "\n\n" deps += "\n\n"
Path(depfile).write_text(deps) Path(depfile).write_text(deps)
else: else:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--language') parser.add_argument("--language")
parser.add_argument('--outdir') parser.add_argument("--outdir")
parser.add_argument('--src-prefix') parser.add_argument("--src-prefix")
parser.add_argument('--depfile', default="") parser.add_argument("--depfile", default="")
parser.add_argument('-I', '--include', action='append', default=['@capnp_include@']) parser.add_argument("-I", "--include", action="append", default=["@capnp_include@"])
parser.add_argument('inputs', nargs='+') parser.add_argument("inputs", nargs="+")
args = parser.parse_args() args = parser.parse_args()
for infile in args.inputs: for infile in args.inputs:
os.environ['lix_capnp_lang'] = f"capnpc-{args.language}" os.environ["lix_capnp_lang"] = f"capnpc-{args.language}"
os.environ['lix_capnp_include'] = ':'.join(args.include) os.environ["lix_capnp_include"] = ":".join(args.include)
os.environ['lix_capnp_depfile'] = args.depfile os.environ["lix_capnp_depfile"] = args.depfile
os.environ['lix_capnp_old_cwd'] = os.getcwd() os.environ["lix_capnp_old_cwd"] = str(Path.cwd())
if args.language == "c++": if args.language == "c++":
os.environ['lix_capnp_outputs'] = "c++ h" os.environ["lix_capnp_outputs"] = "c++ h"
else: else:
raise RuntimeError("unknown language " + args.language) raise RuntimeError("unknown language " + args.language)
subprocess.run([ subprocess.run(
'@capnp@', [
'compile', "@capnp@",
f'-o{sys.argv[0]}:{args.outdir}', "compile",
f'--src-prefix={args.src_prefix}', f"-o{sys.argv[0]}:{args.outdir}",
*(f"-I{i}" for i in args.include), f"--src-prefix={args.src_prefix}",
infile *(f"-I{i}" for i in args.include),
]).check_returncode() infile,
]
).check_returncode()
+51 -39
View File
@@ -1,60 +1,72 @@
from collections.abc import Callable
from pathlib import Path
from typing import Any
import frontmatter import frontmatter
import pathlib
from collections import defaultdict
def cxx_escape_character(c):
if ord(c) >= 0x20 and ord(c) < 0x7f and c != '"' and c != '?' and c != '\\': def cxx_escape_character(c: str) -> str:
if 0x20 <= ord(c) < 0x7F and c != '"' and c != "?" and c != "\\":
return c return c
elif c == '\t': if c == "\t":
return r'\t' return r"\t"
elif c == '\n': if c == "\n":
return r'\n' return r"\n"
elif c == '\r': if c == "\r":
return r'\r' return r"\r"
elif c == '"': if c == '"':
return r'\"' return r"\""
elif c == '?': if c == "?":
return r'\?' return r"\?"
elif c == '\\': if c == "\\":
return r'\\' return r"\\"
elif ord(c) <= 0xffff: if ord(c) <= 0xFFFF:
return str.format(r'\u{:04x}', ord(c)) return str.format(r"\u{:04x}", ord(c))
else: return str.format(r"\U{:08x}", ord(c))
return str.format(r'\U{:08x}', ord(c))
def cxx_literal(v):
def cxx_literal(v: Any) -> str:
if v is None: if v is None:
return 'std::nullopt' return "std::nullopt"
elif isinstance(v, bool) and v == False: # 0 == False if v is False:
return 'false' return "false"
elif isinstance(v, bool) and v == True: # 1 == True if v is True:
return 'true' return "true"
elif isinstance(v, int): if isinstance(v, int):
return str(v) return str(v)
elif isinstance(v, str): if isinstance(v, str):
return ''.join(['"', *(cxx_escape_character(c) for c in v), '"']) return "".join(['"', *(cxx_escape_character(c) for c in v), '"'])
elif isinstance(v, list): if isinstance(v, list):
return f'{{{", ".join([cxx_literal(item) for item in v])}}}' return f"{{{', '.join([cxx_literal(item) for item in v])}}}"
else: msg = f"cannot represent {v!r} in C++"
raise NotImplementedError(f'cannot represent {repr(v)} in C++') raise NotImplementedError(msg)
def load_data(defs, parse_function):
def load_data[T](defs: list[str], parse_function: Callable[[frontmatter.Post], T]) -> list[T]:
data = [] data = []
for path in defs: for path in defs:
try: try:
datum = frontmatter.load(path) datum = frontmatter.load(path)
data.append((path, parse_function(datum))) data.append((path, parse_function(datum)))
except Exception as e: except Exception as e:
e.add_note(f'in {path}') e.add_note(f"in {path}")
raise raise
return data return data
def generate_file(path, data, sort_key_function, generate_function):
def generate_file(
path: str | None,
data: list[Any],
sort_key_function: Callable[[Any], str],
generate_function: Callable[[Any], str],
):
if path is not None: if path is not None:
with open(path, 'w') as out: with Path(path).open("w") as out:
for path, datum in sorted(data, key=lambda pathAndDatum: sort_key_function(pathAndDatum[1])): for path, datum in sorted(
data, key=lambda path_and_datum: sort_key_function(path_and_datum[1])
):
try: try:
out.write(generate_function(datum)) out.write(generate_function(datum))
except Exception as e: except Exception as e:
e.add_note(f'in {path}') e.add_note(f"in {path}")
raise raise
+1 -1
View File
@@ -12,5 +12,5 @@ priority = 0
[formatter.ruff] [formatter.ruff]
command = "ruff" command = "ruff"
options = ["check"] options = ["check"]
includes = ["tests/functional2/**/*.py", "meson/clang-tidy/*.py", "maintainers/*.py"] includes = ["tests/functional2/**/*.py", "meson/clang-tidy/*.py", "maintainers/*.py", "lix/code-generation/*.py"]
priority = 1 priority = 1