From c882b47e3b9cdcd36a87f14ca75bff00678d94ee Mon Sep 17 00:00:00 2001 From: Fea Date: Tue, 12 Mar 2024 03:12:16 +0100 Subject: [PATCH] formats.kdl: init --- .../development/settings-options.section.md | 61 ++++++++ pkgs/pkgs-lib/formats.nix | 134 ++++++++++++++++++ 2 files changed, 195 insertions(+) diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md index cedc82d32f89a..e29960d78f2cd 100644 --- a/nixos/doc/manual/development/settings-options.section.md +++ b/nixos/doc/manual/development/settings-options.section.md @@ -108,6 +108,67 @@ have a predefined type and string generator already declared under and returning a set with TOML-specific attributes `type` and `generate` as specified [below](#pkgs-formats-result). +`pkgs.formats.kdl` { } + +: A function taking an empty attribute set (for future extensibility) + and returning a set with [KDL](https://kdl.dev/)-specific attributes `type`, + `lib` and `generate` as specified [below](#pkgs-formats-result). + + `lib` is a set containing the functions `node` and `typed`, which are helper + functions indended to facilitate generating the required structure for pkgs.formats.kdl + in an ergonomic way. + + Their signatures are as follows: + + `node`: + + ```nix + name: type: arguments: properties: children: { inherit name type arguments properties children; }; + ``` + + `typed`: + + ```nix + type: value: { inherit type value; }; + ``` + + This allows writing the KDL node + + ```kdl + name "arg1" (special-type)"arg2" prop=1 { + child + } + ``` + + as + + ```nix + (node "name" null [ "arg1" (typed "special-type" "arg2") ] { prop = 1; } [ + (node "child" null [ ] { } [ ]) + ]) + ``` + + instead of + + ```nix + { + name = "name"; + arguments = [ + "arg1" + { + type = "special-type"; + value = "arg2"; + } + ]; + properties = { prop = 1; }; + children = [ + { + name = "child"; + } + ]; + } + ``` + `pkgs.formats.elixirConf { elixir ? pkgs.elixir }` : A function taking an attribute set with values diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix index 8fc808b2646f4..72da2ec35df66 100644 --- a/pkgs/pkgs-lib/formats.nix +++ b/pkgs/pkgs-lib/formats.nix @@ -513,4 +513,138 @@ rec { '') {}; }; + # The KDL document language (https://kdl.dev/) + kdl = {}: { + + type = (with lib.types; let + # https://github.com/kdl-org/kdl/blob/main/SPEC.md#value + untypedKdlValue = (nullOr (oneOf [ str bool number ])) // { description = "KDL value"; }; + kdlValue = either untypedKdlValue ((submodule { + options = { + type = lib.mkOption { + type = nullOr str; + default = null; + description = '' + [Type Annotation](https://github.com/kdl-org/kdl/blob/main/SPEC.md#type-annotation) of the value. + Set to `null` to prevent generating a type annotation. + ''; + }; + value = lib.mkOption { + type = untypedKdlValue; + description = '' + The actual KDL value. + ''; + }; + }; + }) // { description = "submodule: { type = /* type annotation */; value = /* KDL value */; }"; }); + node = submoduleWith { + modules = lib.toList { + options = { + name = lib.mkOption { + type = str; + description = '' + Name of [KDL node](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node). + ''; + }; + type = lib.mkOption { + type = nullOr str; + default = null; + description = '' + [Type Annotation](https://github.com/kdl-org/kdl/blob/main/SPEC.md#type-annotation) of [KDL node](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node). + Set to `null` to prevent generating a type annotation. + ''; + }; + arguments = lib.mkOption { + type = listOf kdlValue; + default = [ ]; + description = '' + [Arguments](https://github.com/kdl-org/kdl/blob/main/SPEC.md#argument) of [KDL node](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node). + ''; + }; + properties = lib.mkOption { + type = attrsOf kdlValue; + default = { }; + description = '' + [Properties](https://github.com/kdl-org/kdl/blob/main/SPEC.md#property) of [KDL node](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node). + ''; + }; + children = lib.mkOption { + type = listOf (node // { + # Prevent Nix from trying to recurse into suboptions or submodules, as this leads to a stack overflow + getSubOptions = prefix: {}; + getSubModules = null; + }); + default = [ ]; + description = '' + [Children](https://github.com/kdl-org/kdl/blob/main/SPEC.md#children-block) of [KDL node](https://github.com/kdl-org/kdl/blob/main/SPEC.md#node). + ''; + }; + }; + }; + description = "KDL node"; + }; + valueType = listOf node; + in + valueType); + + lib = { + /** + Helper function for generating attrsets expect by pkgs.formats.kdl + + # Example + + ```nix + let + settingsFormat = pkgs.formats.kdl { }; + inherit (settingsFormat.lib) node; + in + settingsFormat.generate "sample.kdl" [ + (node "foo" null [ ] { } [ + (node "bar" null [ "baz" ] { a = 1; } [ ]) + ]) + ] + ``` + + # Arguments + + name + : The name of the node, represented by a string + + type + : The type annotation of the node, represented by a string, or null to avoid generating a type annotation + + arguments + : The arguments of the node, represented as a list of KDL values + + properties + : The properties of the node, represented as an attrset of KDL values + + children + : The children of the node, represented as a list of nodes + + */ + node = name: type: arguments: properties: children: { inherit name type arguments properties children; }; + + /** + Helper function for generting the format of a typed value as expected by pkgs.formats.kdl + + type + : The type of the value, represented by a string + + value + : The value itself + */ + typed = type: value: { inherit type value; }; + }; + + generate = name: value: pkgs.callPackage ({ runCommand, json2kdl }: runCommand name { + nativeBuildInputs = [ json2kdl ]; + value = builtins.toJSON value; + passAsFile = [ "value" ]; + } '' + json2kdl "$valuePath" "$out" + '') {}; + + }; + }