diff --git a/lix/code-generation/build_builtin_constants.py b/lix/code-generation/build_builtin_constants.py index bdba163c5..5d7044e35 100644 --- a/lix/code-generation/build_builtin_constants.py +++ b/lix/code-generation/build_builtin_constants.py @@ -1,70 +1,102 @@ +import dataclasses +from enum import Enum +from textwrap import dedent, indent from typing import NamedTuple -from frontmatter import Post -from common import cxx_literal, generate_file, load_data -import argparse +from common import cxx_literal, generate_file, load_data, get_argument_parser -KNOWN_KEYS = {"name", "type", "constructorArgs", "implementation", "impure", "renameInGlobalScope"} +IMPURE_NOTE = """ +> **Note** +> +> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval). + +""" -class BuiltinConstant(NamedTuple): - name: str - type: str - implementation: str - impure: bool - rename_in_global_scope: bool - documentation: str +class TypeName(NamedTuple): + human: str + code: str + + +class BuiltinType(TypeName, Enum): + attrs = TypeName("set", "nAttrs") + boolean = TypeName("boolean", "nBool") + integer = TypeName("integer", "nInt") + list = TypeName("list", "nList") + null = TypeName("null", "nNull") + string = TypeName("string", "nString") @classmethod - def parse(cls, datum: Post) -> "BuiltinConstant": - unknown_keys = set(datum.keys()) - KNOWN_KEYS - if 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( - name=datum["name"], # type: ignore - type=datum["type"], # type: ignore - implementation=impl, - impure=datum.get("impure", False), # type: ignore - rename_in_global_scope=datum.get("renameInGlobalScope", True), # type: ignore - documentation=datum.content, - ) + def from_string(cls, t_name: str) -> "BuiltinType": + for t in cls: + if t_name == t.name: + return t + msg = f"Invalid builtin type: {t_name}" + raise ValueError(msg) -VALUE_TYPES = { - "attrs": "nAttrs", - "boolean": "nBool", - "integer": "nInt", - "list": "nList", - "null": "nNull", - "string": "nString", -} +@dataclasses.dataclass +class BuiltinConstant: + name: str + documentation: str -HUMAN_TYPES = { - "attrs": "set", - "boolean": "Boolean", - "integer": "integer", - "list": "list", - "null": "null", - "string": "string", -} + # Fields with different name in the Post than in here + # our fields + type: BuiltinType = dataclasses.field(init=False) + + # Post fields + type_str: dataclasses.InitVar[str] + constructor_args: dataclasses.InitVar[list[str] | None] = None + + implementation: str = "" + impure: bool = False + rename_in_global_scope: bool = True + + def __post_init__(self, type_str: str, constructor_args: list[str] | None): + self.type = BuiltinType.from_string(type_str) + if constructor_args is not None: + args = [f"NewValueAs::{type_str}"] + constructor_args + self.implementation = f"{{{','.join(args)}}}" + + @property + def code(self) -> str: + cond = "if (!evalSettings.pureEval) " if self.impure else "" + return dedent(f""" + {cond} {{ + addConstant( + {cxx_literal(("__" if self.rename_in_global_scope else "") + self.name)}, + {self.implementation}, + {{ + .type = {self.type.code}, + .doc = {cxx_literal(self.documentation)}, + .impureOnly = {cxx_literal(self.impure)}, + }} + ); + }} + """) + + @property + def docs(self) -> str: + indentation = " " * 3 + return dedent(f""" +
+ {self.name} ({self.type.human}) +
+
+ + {indent(self.documentation, indentation)} + {indent(IMPURE_NOTE, indentation) if self.impure else ""} + +
+ + """) def main(): - ap = argparse.ArgumentParser() - 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("defs", help="Builtin definition files", nargs="+") - args = ap.parse_args() + args = get_argument_parser().parse_args() - builtin_constants = load_data(args.defs, BuiltinConstant.parse) + builtin_constants = load_data(args.defs, BuiltinConstant) generate_file( args.header, @@ -72,40 +104,9 @@ def main(): lambda constant: # `builtins` is magic and must come first "" if constant.name == "builtins" else constant.name, - lambda constant: f"""{"if (!evalSettings.pureEval) " if constant.impure else ""}{{ - addConstant({cxx_literal(("__" if constant.rename_in_global_scope else "") + constant.name)}, {constant.implementation}, {{ - .type = {VALUE_TYPES[constant.type]}, - .doc = {cxx_literal(constant.documentation)}, - .impureOnly = {cxx_literal(constant.impure)}, - }}); -}} -""", - ) - generate_file( - args.docs, - builtin_constants, - lambda constant: constant.name, - lambda constant: f"""
- {constant.name} ({HUMAN_TYPES[constant.type]}) -
-
- -{constant.documentation} - -""" - + ( - """> **Note** -> -> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval). - -""" - if constant.impure - else "" - ) - + """
- -""", + lambda b: b.code, ) + generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda b: b.docs) if __name__ == "__main__": diff --git a/lix/code-generation/build_builtins.py b/lix/code-generation/build_builtins.py index ee9c9f3a2..020ad697a 100644 --- a/lix/code-generation/build_builtins.py +++ b/lix/code-generation/build_builtins.py @@ -1,103 +1,89 @@ -from typing import NamedTuple +import dataclasses +from textwrap import dedent, indent -from frontmatter import Post - -from build_experimental_features import ExperimentalFeature -from common import cxx_literal, generate_file, load_data -import argparse - -KNOWN_KEYS = {"name", "implementation", "renameInGlobalScope", "args", "experimentalFeature"} +from common import ( + cxx_literal, + generate_file, + load_data, + get_argument_parser, + get_experimental_features, +) -class Builtin(NamedTuple): +@dataclasses.dataclass +class Builtin: name: str - implementation: str - rename_in_global_scope: bool - args: list[str] - experimental_feature: str | None documentation: str + args: list[str] + experimental_feature: str | None = None + implementation: str = "" + rename_in_global_scope: bool = True - @classmethod - def parse(cls, datum: Post) -> "Builtin": - unknown_keys = set(datum.keys()) - KNOWN_KEYS - if unknown_keys: - msg = f"unknown keys: {unknown_keys!r}" - raise ValueError(msg) - return Builtin( - name=datum["name"], # type: ignore - implementation=datum["implementation"] - if "implementation" in datum - else f"prim_{datum['name']}", - rename_in_global_scope=datum.get("renameInGlobalScope", True), # type: ignore - args=datum["args"], # type: ignore - experimental_feature=datum.get("experimentalFeature", None), # type: ignore - documentation=datum.content, + def __post_init__(self): + self.implementation = self.implementation or f"prim_{self.name}" + + def generate_code(self, experimental_features: dict[str, str]) -> str: + xf = experimental_features[self.experimental_feature] + cond = ( + f"if (experimentalFeatureSettings.isEnabled({xf})) " + if self.experimental_feature + else "" ) + return dedent(f""" + {cond}{{ + addPrimOp({{ + .name = {cxx_literal(("__" if self.rename_in_global_scope else "") + self.name)}, + .args = {cxx_literal(self.args)}, + .arity = {len(self.args)}, + .doc = {cxx_literal(self.documentation)}, + .fun = {self.implementation}, + .experimentalFeature = {xf}, + }}); + }} + """) + + @property + def docs(self) -> str: + return dedent(f""" +
+ {self.name} { + " ".join([f"{arg}" for arg in self.args]) + } +
+
+ + {indent(self.documentation, " " * 3)} + + { + f"This function is only available if the [{self.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{self.experimental_feature}) experimental feature is enabled." + if self.experimental_feature is not None + else "" + } +
+ + """) def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--header", help="Path of the header to generate") - ap.add_argument("--docs", help="Path of the documentation file to generate") + ap = get_argument_parser() ap.add_argument( "--experimental-features", help="Directory containing the experimental feature definitions" ) - ap.add_argument("defs", help="Builtin definition files", nargs="+") args = ap.parse_args() - builtins = load_data(args.defs, Builtin.parse) + builtins = load_data(args.defs, Builtin) - experimental_feature_names = {builtin.experimental_feature for (_, builtin) in builtins} - experimental_feature_names.discard(None) - 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 = { - path_and_feature[1].name: f"Xp::{path_and_feature[1].internal_name}" - for path_and_feature in experimental_features - } - experimental_features[None] = "std::nullopt" + experimental_features = get_experimental_features( + args.experimental_features, [b.experimental_feature for (_, b) in builtins] + ) generate_file( 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({{ - .name = {cxx_literal(("__" if builtin.rename_in_global_scope else "") + builtin.name)}, - .args = {cxx_literal(builtin.args)}, - .arity = {len(builtin.args)}, - .doc = {cxx_literal(builtin.documentation)}, - .fun = {builtin.implementation}, - .experimentalFeature = {experimental_features[builtin.experimental_feature]}, - }}); -}} -""", - ) - generate_file( - args.docs, - builtins, - lambda builtin: builtin.name, - lambda builtin: f"""
- {builtin.name} {" ".join([f"{arg}" for arg in builtin.args])} -
-
- -{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. - -""" - if builtin.experimental_feature is not None - else "" - ) - + """
- -""", + lambda b: b.generate_code(experimental_features), ) + generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda b: b.docs) if __name__ == "__main__": diff --git a/lix/code-generation/build_experimental_features.py b/lix/code-generation/build_experimental_features.py deleted file mode 100644 index 73dedb2fd..000000000 --- a/lix/code-generation/build_experimental_features.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import NamedTuple - -from frontmatter import Post - -from common import cxx_literal, generate_file, load_data -import argparse - -KNOWN_KEYS = {"name", "internalName"} - - -class ExperimentalFeature(NamedTuple): - name: str - internal_name: str - description: str - - @classmethod - def parse(cls, datum: Post) -> "ExperimentalFeature": - unknown_keys = set(datum.keys()) - KNOWN_KEYS - if unknown_keys: - msg = f"unknown keys: {unknown_keys!r}" - raise ValueError(msg) - return ExperimentalFeature( - name=datum["name"], # type: ignore - internal_name=datum["internalName"], # type: ignore - description=datum.content, - ) - - -def main(): - ap = argparse.ArgumentParser() - 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("--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("--shortlist", help="Path of the shortlist file to generate") - ap.add_argument("defs", help="Experimental feature definition files", nargs="+") - args = ap.parse_args() - - features = load_data(args.defs, ExperimentalFeature.parse) - - generate_file( - args.header, - features, - 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}, - .name = {cxx_literal(feature.name)}, - .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}}} - -{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", - ) - - -if __name__ == "__main__": - main() diff --git a/lix/code-generation/build_extra_features.py b/lix/code-generation/build_extra_features.py new file mode 100644 index 000000000..c4999f362 --- /dev/null +++ b/lix/code-generation/build_extra_features.py @@ -0,0 +1,74 @@ +import dataclasses +from enum import Enum +from textwrap import dedent +from typing import ClassVar, NamedTuple + +from common import cxx_literal, generate_file, load_data, get_argument_parser + + +class FeatureTypeNames(NamedTuple): + code_tag: str + doc_tag: str + + +class FeatureType(FeatureTypeNames, Enum): + experimental = FeatureTypeNames("Xp", "xp") + deprecated = FeatureTypeNames("Dep", "dp") + + +@dataclasses.dataclass +class ExtraFeature: + name: str + internal_name: str + documentation: str + + type: ClassVar[FeatureType] + + @property + def code(self) -> str: + return dedent(f""" + {{ + .tag = {ExtraFeature.type.code_tag}::{self.internal_name}, + .name = {cxx_literal(self.name)}, + .description = {cxx_literal(self.documentation)}, + }}, + """) + + @property + def docs(self) -> str: + return dedent(f""" + ## [`{self.name}`]{{#{ExtraFeature.type.doc_tag}-feature-{self.name}}} + + {self.documentation} + + """) + + @property + def short_docs(self) -> str: + return f" - [`{self.name}`](@docroot@/contributing/{ExtraFeature.type.name}-features.md#{ExtraFeature.type.doc_tag}-feature-{self.name})\n" + + +def main(): + ap = get_argument_parser() + ap.add_argument("--deprecated", action="store_true", help="Generate deprecated features") + ap.add_argument("--impl-header", help="Path of the implementation header to generate") + ap.add_argument("--shortlist", help="Path of the shortlist file to generate") + args = ap.parse_args() + + ExtraFeature.type = FeatureType.deprecated if args.deprecated else FeatureType.experimental + + features = load_data(args.defs, ExtraFeature) + + generate_file( + args.header, + features, + lambda feature: feature.name, + lambda feature: f" {feature.internal_name},\n", + ) + generate_file(args.impl_header, features, lambda feature: feature.name, lambda f: f.code) + generate_file(args.docs, features, lambda feature: feature.name, lambda f: f.docs) + generate_file(args.shortlist, features, lambda feature: feature.name, lambda f: f.short_docs) + + +if __name__ == "__main__": + main() diff --git a/lix/code-generation/build_settings.py b/lix/code-generation/build_settings.py index 35e746f3b..0d4d9e0fb 100644 --- a/lix/code-generation/build_settings.py +++ b/lix/code-generation/build_settings.py @@ -1,64 +1,106 @@ -from typing import NamedTuple, Any +import dataclasses +from textwrap import dedent +from typing import Any -from frontmatter import Post - -from build_experimental_features import ExperimentalFeature -from common import cxx_literal, generate_file, load_data -import argparse - -KNOWN_KEYS = { - "name", - "internalName", - "platforms", - "type", - "settingType", - "default", - "defaultExpr", - "defaultText", - "aliases", - "experimentalFeature", - "deprecated", -} +from common import ( + cxx_literal, + generate_file, + load_data, + get_experimental_features, + get_argument_parser, +) -class Setting(NamedTuple): +PLATFORM_WARNING = """ + > **Note** + > This setting is only available on {platforms} systems. + +""" + +XP_WARNING = """ + > **Warning** + > This setting is part of an + > [experimental feature](@docroot@/contributing/experimental-features.md). + + To change this setting, you need to make sure the corresponding experimental feature, + [`{feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{feature}), + is enabled. + For example, include the following in [`nix.conf`](#): + + ``` + extra-experimental-features = {feature} + {name} = ... + ``` + +""" + +DEPR_WARNING = """ + > **Warning** + > This setting is deprecated and will be removed in a future version of Lix. + +""" + + +@dataclasses.dataclass +class Setting: name: str internal_name: str - description: str - platforms: list[str] | None - setting_type: str - default_expr: str - default_text: str - aliases: list[str] - experimental_feature: str | None - deprecated: bool + documentation: str - @classmethod - def parse(cls, datum: Post) -> "Setting": - unknown_keys = set(datum.keys()) - KNOWN_KEYS - if unknown_keys: - msg = f"unknown keys: {unknown_keys!r}" - raise ValueError(msg) - default_text = ( - f"`{nix_conf_literal(datum['default'])}`" - if "default" in datum - else datum["defaultText"] - ) - if default_text == "``": - default_text = "*empty*" - return Setting( - name=datum["name"], # type: ignore - internal_name=datum["internalName"], # type: ignore - description=datum.content, - platforms=datum.get("platforms", None), # type: ignore - 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_text=default_text, - aliases=datum.get("aliases", []), # type: ignore - experimental_feature=datum.get("experimentalFeature", None), # type: ignore - deprecated=datum.get("deprecated", False), # type: ignore + default_text: str = "" + setting_type: str = "" + default_expr: str = "" + platforms: list[str] = dataclasses.field(default_factory=list) + aliases: list[str] = dataclasses.field(default_factory=list) + experimental_feature: str | None = None + deprecated: bool = False + + default: dataclasses.InitVar[str | None] = None + type_str: dataclasses.InitVar[str | None] = None + + def __post_init__(self, default: Any, type_str: str | None): + if default is not None: # is not None nor an empty String + self.default_text = f"`{nix_conf_literal(default)}`" + self.default_expr = self.default_expr or cxx_literal(default) + self.default_text = self.default_text or "*empty*" + + if type_str is not None: + self.setting_type = f"Setting<{type_str}>" + + def generate_code(self, experimental_features: dict[str | None, str]) -> str: + indentation = " " * 4 + expr = (indent(indentation, self.default_expr) + indentation) if "\n" in self.default_expr else self.default_expr + return dedent(f""" + {self.setting_type} {self.internal_name} {{ + this, + {expr}, + {cxx_literal(self.name)}, + {cxx_literal(self.documentation)}, + {cxx_literal(self.aliases)}, + true, + {experimental_features[self.experimental_feature]}, + {cxx_literal(self.deprecated)} + }}; + """) + + @property + def docs(self) -> str: + indentation = " " * 3 + platforms = [p.capitalize() for p in self.platforms] + aliases = [f"`{item}`" for item in self.aliases] + description = dedent(f""" + + {indent(indentation, self.documentation)} + + {indent(indentation, PLATFORM_WARNING.format(platforms=str(platforms)[1:-1])) if self.platforms else ""} + {indent(indentation, XP_WARNING.format(feature=self.experimental_feature, name=self.name)) if self.experimental_feature is not None else ""} + {indent(indentation, DEPR_WARNING) if self.deprecated else ""} + **Default:** {self.default_text} + {f"**Deprecated alias:** {str(aliases)[1:-1]}\n" if self.aliases else ""} + """) + return f'- [`{self.name}`](#conf-{self.name})' + indent( + " ", # indent by two space to make it part of the list point + description, ) @@ -87,103 +129,28 @@ def indent(prefix: str, body: str) -> str: def main(): - ap = argparse.ArgumentParser() + ap = get_argument_parser() 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("--docs", help="Path of the documentation file to generate") ap.add_argument( "--experimental-features", help="Directory containing the experimental feature definitions" ) - ap.add_argument("defs", help="Setting definition files", nargs="+") args = ap.parse_args() - settings = load_data(args.defs, Setting.parse) + settings = load_data(args.defs, Setting) - experimental_feature_names = {setting.experimental_feature for (_, setting) in settings} - experimental_feature_names.discard(None) - 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 = { - path_and_feature[1].name: f"Xp::{path_and_feature[1].internal_name}" - for path_and_feature in experimental_features - } - experimental_features[None] = "std::nullopt" + experimental_features = get_experimental_features( + args.experimental_features, [s.experimental_feature for (_, s) in settings] + ) generate_file( args.header, settings, lambda setting: setting.name, - lambda setting: f"""{setting.setting_type} {setting.internal_name} {{ - this, - {setting.default_expr}, - {cxx_literal(setting.name)}, - {cxx_literal(setting.description)}, - {cxx_literal(setting.aliases)}, - true, - {experimental_features[setting.experimental_feature]}, - {cxx_literal(setting.deprecated)} -}}; - -""" - if setting.platforms is None or args.kernel in setting.platforms + lambda setting: setting.generate_code(experimental_features) + if not setting.platforms or args.kernel in setting.platforms else "", ) - generate_file( - args.docs, - settings, - lambda setting: setting.name, - lambda setting: f"""- [`{setting.name}`](#conf-{setting.name}) - -{indent(" ", setting.description)} -""" - + ( - 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** - > This setting is part of an - > [experimental feature](@docroot@/contributing/experimental-features.md). - - To change this setting, you need to make sure the corresponding experimental feature, - [`{setting.experimental_feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{setting.experimental_feature}), - is enabled. - For example, include the following in [`nix.conf`](#): - - ``` - extra-experimental-features = {setting.experimental_feature} - {setting.name} = ... - ``` - -""" - if setting.experimental_feature is not None - else "" - ) - + ( - """ > **Warning** - > This setting is deprecated and will be removed in a future version of Lix. - -""" - if setting.deprecated - else "" - ) - + f""" **Default:** {setting.default_text} - -""" - + ( - f""" **Deprecated alias:** {", ".join([f"`{item}`" for item in setting.aliases])} - -""" - if setting.aliases != [] - else "" - ), - ) + generate_file(args.docs, settings, lambda setting: setting.name, lambda setting: setting.docs) if __name__ == "__main__": diff --git a/lix/code-generation/common.py b/lix/code-generation/common.py index 3e4d8124a..93e9de7a1 100644 --- a/lix/code-generation/common.py +++ b/lix/code-generation/common.py @@ -1,3 +1,5 @@ +import argparse +import re from collections.abc import Callable from pathlib import Path from typing import Any @@ -42,23 +44,52 @@ def cxx_literal(v: Any) -> str: raise NotImplementedError(msg) -def load_data[T](defs: list[str], parse_function: Callable[[frontmatter.Post], T]) -> list[T]: +def get_experimental_features( + base_path: str, human_names: list[str | None] +) -> dict[str | None, str]: + experimental_feature_files = { + f"{base_path}/{xp_name}.md" for xp_name in human_names if xp_name is not None + } + + from build_extra_features import ExtraFeature # noqa: PLC0415 # Avoid cyclic import + + experimental_features_data = load_data(list(experimental_feature_files), ExtraFeature) + experimental_features: dict[str | None, str] = { + xf.name: f"Xp::{xf.internal_name}" for _, xf in experimental_features_data + } + experimental_features[None] = "std::nullopt" + return experimental_features + + +FIELD_RENAMES = {"type": "type_str", "content": "documentation"} + + +def load_data[T](defs: list[str], parse_function: type[T]) -> list[tuple[str, T]]: data = [] for path in defs: try: - datum = frontmatter.load(path) - data.append((path, parse_function(datum))) + datum = { + # convert camelCase to snake_case + re.sub(r"(?<=.)([A-Z])", lambda m: f"_{m.group(1).lower()}", k): v + for k, v in frontmatter.load(path).to_dict().items() + } + + for post_name, field_name in FIELD_RENAMES.items(): + if post_name in datum: + datum[field_name] = datum.pop(post_name) + + data.append((path, parse_function(**datum))) except Exception as e: e.add_note(f"in {path}") raise return data -def generate_file( +def generate_file[T]( path: str | None, - data: list[Any], - sort_key_function: Callable[[Any], str], - generate_function: Callable[[Any], str], + data: list[T], + sort_key_function: Callable[[T], str], + generate_function: Callable[[T], str], ): if path is not None: with Path(path).open("w") as out: @@ -66,7 +97,17 @@ def generate_file( data, key=lambda path_and_datum: sort_key_function(path_and_datum[1]) ): try: - out.write(generate_function(datum)) + text = generate_function(datum) + out.write(text) except Exception as e: e.add_note(f"in {path}") raise + + +def get_argument_parser() -> argparse.ArgumentParser: + ap = argparse.ArgumentParser() + 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("defs", help="Builtin definition files", nargs="+") + + return ap diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index aa2f47ae3..bcb24e717 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -187,11 +187,11 @@ deprecated_feature_definitions = files( experimental_features_gen = custom_target( command : [ python.full_path(), - '@SOURCE_ROOT@/lix/code-generation/build_experimental_features.py', + '@SOURCE_ROOT@/lix/code-generation/build_extra_features.py', '--header', '@OUTPUT0@', '--impl-header', '@OUTPUT1@', '--shortlist', '@OUTPUT2@', - '--descriptions', '@OUTPUT3@', + '--docs', '@OUTPUT3@', '@INPUT@', ], input : experimental_feature_definitions, @@ -219,12 +219,12 @@ experimental_feature_descriptions_md = experimental_features_gen[3] deprecated_features_gen = custom_target( command : [ python.full_path(), - '@SOURCE_ROOT@/lix/code-generation/build_experimental_features.py', + '@SOURCE_ROOT@/lix/code-generation/build_extra_features.py', '--deprecated', '--header', '@OUTPUT0@', '--impl-header', '@OUTPUT1@', '--shortlist', '@OUTPUT2@', - '--descriptions', '@OUTPUT3@', + '--docs', '@OUTPUT3@', '@INPUT@', ], input : deprecated_feature_definitions,