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,