lib: add generator for KDL

Added a generator for the KDL document language.
This is in order for home-manager to natively generate
the new config format for zellij, as described in nix-community#3364.

There is not a one to one mapping between KDL and nix types,
but attrset translation is heavily based on KDLs JSON-IN-KDL microsyntax.
The exception here is the `_args` and `_props` arguments, which lets you
specify arguments and properties as described in the spec.

See more here:
- https://kdl.dev/
- https://github.com/kdl-org/kdl/blob/main/SPEC.md

The generator also conforms to the interface from the nixpkgs manual:
https://nixos.org/manual/nixpkgs/stable/#sec-generators

Co-authored-by: Gaetan Lepage <gaetan@glepage.com>
This commit is contained in:
h7x4 2022-11-21 23:05:02 +01:00 committed by Robert Helgesson
parent 36999b8d19
commit fce9dbfeb4
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
7 changed files with 202 additions and 0 deletions

3
.github/CODEOWNERS vendored
View file

@ -16,6 +16,9 @@ Makefile @thiagokokada
/modules/launchd @midchildan
/modules/lib/generators.nix @h7x4
/test/lib/generators @h7x4
/modules/misc/dconf.nix @rycee
/modules/misc/editorconfig.nix @loicreynier

View file

@ -6,6 +6,7 @@ rec {
assertions = import ./assertions.nix { inherit lib; };
booleans = import ./booleans.nix { inherit lib; };
generators = import ./generators.nix { inherit lib; };
gvariant = import ./gvariant.nix { inherit lib; };
maintainers = import ./maintainers.nix;
strings = import ./strings.nix { inherit lib; };

102
modules/lib/generators.nix Normal file
View file

@ -0,0 +1,102 @@
{ lib }:
{
toKDL = { }:
let
inherit (lib) concatStringsSep splitString mapAttrsToList any;
inherit (builtins) typeOf replaceStrings elem;
# ListOf String -> String
indentStrings = let
# Although the input of this function is a list of strings,
# the strings themselves *will* contain newlines, so you need
# to normalize the list by joining and resplitting them.
unlines = lib.splitString "\n";
lines = lib.concatStringsSep "\n";
indentAll = lines: concatStringsSep "\n" (map (x: " " + x) lines);
in stringsWithNewlines: indentAll (unlines (lines stringsWithNewlines));
# String -> String
sanitizeString = replaceStrings [ "\n" ''"'' ] [ "\\n" ''\"'' ];
# OneOf [Int Float String Bool Null] -> String
literalValueToString = element:
lib.throwIfNot
(elem (typeOf element) [ "int" "float" "string" "bool" "null" ])
"Cannot convert value of type ${typeOf element} to KDL literal."
(if typeOf element == "null" then
"null"
else if element == false then
"false"
else if element == true then
"true"
else if typeOf element == "string" then
''"${sanitizeString element}"''
else
toString element);
# Attrset Conversion
# String -> AttrsOf Anything -> String
convertAttrsToKDL = name: attrs:
let
optArgsString = lib.optionalString (attrs ? "_args")
(lib.pipe attrs._args [
(map literalValueToString)
(lib.concatStringsSep " ")
(s: s + " ")
]);
optPropsString = lib.optionalString (attrs ? "_props")
(lib.pipe attrs._props [
(lib.mapAttrsToList
(name: value: "${name}=${literalValueToString value}"))
(lib.concatStringsSep " ")
(s: s + " ")
]);
children =
lib.filterAttrs (name: _: !(elem name [ "_args" "_props" ])) attrs;
in ''
${name} ${optArgsString}${optPropsString}{
${indentStrings (mapAttrsToList convertAttributeToKDL children)}
}'';
# List Conversion
# String -> ListOf (OneOf [Int Float String Bool Null]) -> String
convertListOfFlatAttrsToKDL = name: list:
let flatElements = map literalValueToString list;
in "${name} ${concatStringsSep " " flatElements}";
# String -> ListOf Anything -> String
convertListOfNonFlatAttrsToKDL = name: list: ''
${name} {
${indentStrings (map (x: convertAttributeToKDL "-" x) list)}
}'';
# String -> ListOf Anything -> String
convertListToKDL = name: list:
let elementsAreFlat = !any (el: elem (typeOf el) [ "list" "set" ]) list;
in if elementsAreFlat then
convertListOfFlatAttrsToKDL name list
else
convertListOfNonFlatAttrsToKDL name list;
# Combined Conversion
# String -> Anything -> String
convertAttributeToKDL = name: value:
let vType = typeOf value;
in if elem vType [ "int" "float" "bool" "null" "string" ] then
"${name} ${literalValueToString value}"
else if vType == "set" then
convertAttrsToKDL name value
else if vType == "list" then
convertListToKDL name value
else
throw ''
Cannot convert type `(${typeOf value})` to KDL:
${name} = ${toString value}
'';
in attrs: ''
${concatStringsSep "\n" (mapAttrsToList convertAttributeToKDL attrs)}
'';
}

View file

@ -47,6 +47,7 @@ import nmt {
inherit lib pkgs modules;
testedAttrPath = [ "home" "activationPackage" ];
tests = builtins.foldl' (a: b: a // (import b)) { } ([
./lib/generators
./lib/types
./modules/files
./modules/home-environment

View file

@ -0,0 +1 @@
{ generators-tokdl = ./tokdl.nix; }

View file

@ -0,0 +1,41 @@
a 1
b "string"
bigFlatItems 23847590283751 1.239000 "multiline \" \" \"\nstring\n" null
c "multiline string\nwith special characters:\n\t \n \\" \"\n"
extraAttrs 2 true arg1=1 arg2=false {
nested {
a 1
b null
}
}
flatItems 1 2 "asdf" true null
listInAttrsInList {
list1 {
- {
a 1
}
- {
b true
}
- {
c null
d {
- {
e "asdfadfasdfasdf"
}
}
}
}
list2 {
- {
a 8
}
}
}
nested {
- 1 2
- true false
-
- null
}
unsafeString " \" \n "

View file

@ -0,0 +1,53 @@
{ config, lib, ... }:
{
home.file."result.txt".text = lib.hm.generators.toKDL { } {
a = 1;
b = "string";
c = ''
multiline string
with special characters:
\t \n \" "
'';
unsafeString = " \" \n ";
flatItems = [ 1 2 "asdf" true null ];
bigFlatItems = [
23847590283751
1.239
''
multiline " " "
string
''
null
];
nested = [ [ 1 2 ] [ true false ] [ ] [ null ] ];
extraAttrs = {
_args = [ 2 true ];
_props = {
arg1 = 1;
arg2 = false;
};
nested = {
a = 1;
b = null;
};
};
listInAttrsInList = {
list1 = [
{ a = 1; }
{ b = true; }
{
c = null;
d = [{ e = "asdfadfasdfasdf"; }];
}
];
list2 = [{ a = 8; }];
};
};
nmt.script = ''
assertFileContent \
home-files/result.txt \
${./tokdl-result.txt}
'';
}