diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md index bae2fb72fdf39..354556fe5f581 100644 --- a/nixos/doc/manual/development/settings-options.section.md +++ b/nixos/doc/manual/development/settings-options.section.md @@ -364,6 +364,67 @@ have a predefined type and string generator already declared under and returning a set with [CDN](https://github.com/dzikoysk/cdn)-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/by-name/js/json2kdl/package.nix b/pkgs/by-name/js/json2kdl/package.nix new file mode 100644 index 0000000000000..a799cc4d735f2 --- /dev/null +++ b/pkgs/by-name/js/json2kdl/package.nix @@ -0,0 +1,26 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, +}: + +rustPlatform.buildRustPackage rec { + pname = "json2kdl"; + version = "0.2.0"; + + src = fetchFromGitHub { + owner = "AgathaSorceress"; + repo = pname; + rev = "v${version}"; + hash = "sha256-NVpIHbv7vbppe+g7YK9OY2oL7axmqG8Kmuv4kO8Jyjs="; + }; + + cargoHash = "sha256-xlG8p25VBLwUWnyr9JNzSrI0KmwdRpAgL5eckbC/3nk="; + + meta = { + description = "Program that converts JSON files to KDL"; + homepage = "https://github.com/AgathaSorceress/json2kdl"; + platforms = lib.platforms.all; + maintainers = (with lib.maintainers; [ feathecutie ]); + }; +} diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix index f01ea4fe44fa2..3fc9877fd5907 100644 --- a/pkgs/pkgs-lib/formats.nix +++ b/pkgs/pkgs-lib/formats.nix @@ -637,4 +637,138 @@ rec { else throw "pkgs.formats.xml: Unknown format: ${format}"; + # 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" + '') {}; + + }; + }