Skip to content

Commit 01cb23e

Browse files
authored
feat(lib.toKdl): and update niri to use it (#386)
For the niri module, deprecation warnings have been added. You will want to follow the directions given, and then when you are done fixing them, set `config.v2-settings = true;` in order to remove the check for deprecated values.
1 parent 23b8770 commit 01cb23e

5 files changed

Lines changed: 527 additions & 132 deletions

File tree

ci/checks/toKdl.nix

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
{
2+
pkgs,
3+
self,
4+
}:
5+
let
6+
lib = pkgs.lib;
7+
toKdl = self.lib.toKdl;
8+
9+
assertions = [
10+
{
11+
description = "plain node";
12+
expected = ''"a" '';
13+
actual = toKdl { a = _: { }; };
14+
}
15+
{
16+
description = "primitive as argument";
17+
expected = ''"b" 1'';
18+
actual = toKdl { b = 1; };
19+
}
20+
{
21+
description = "list of primitives as multiple args";
22+
expected = ''"c" "x" 2 true #null'';
23+
actual = toKdl {
24+
c = [
25+
"x"
26+
2
27+
true
28+
null
29+
];
30+
};
31+
}
32+
{
33+
description = "attrset as child block";
34+
expected = "\"d\" {\n \"x\" 1\n}";
35+
actual = toKdl {
36+
d = {
37+
x = 1;
38+
};
39+
};
40+
}
41+
{
42+
description = "list of attrsets as repeated child nodes";
43+
expected = "\"e\" {\n \"x\" 1\n \"x\" 2\n}";
44+
actual = toKdl {
45+
e = [
46+
{ x = 1; }
47+
{ x = 2; }
48+
];
49+
};
50+
}
51+
{
52+
description = "function with props only";
53+
expected = ''"h" "k"="v"'';
54+
actual = toKdl {
55+
h = _: {
56+
props = {
57+
k = "v";
58+
};
59+
};
60+
};
61+
}
62+
{
63+
description = "function with content only";
64+
expected = "\"i\" {\n \"j\" 1\n}";
65+
actual = toKdl {
66+
i = x: {
67+
content = {
68+
j = 1;
69+
};
70+
};
71+
};
72+
}
73+
{
74+
description = "function with props and content";
75+
expected = "\"f\" \"arg1\" \"key\"=\"val\" {\n \"g\" \n}";
76+
actual = toKdl {
77+
f = _: {
78+
props = [
79+
"arg1"
80+
{ key = "val"; }
81+
];
82+
content = {
83+
g = _: { };
84+
};
85+
};
86+
};
87+
}
88+
{
89+
description = "nested structure";
90+
expected = "\"k\" {\n \"l\" {\n \"m\" \"a\"\n \"m\" \"b\"\n }\n}";
91+
actual = toKdl {
92+
k = {
93+
l = [
94+
{ m = "a"; }
95+
{ m = "b"; }
96+
];
97+
};
98+
};
99+
}
100+
{
101+
description = "top-level list of attrsets";
102+
expected = "\"a\" 1\n\"b\" 2";
103+
actual = toKdl [
104+
{ a = 1; }
105+
{ b = 2; }
106+
];
107+
}
108+
{
109+
description = "null value";
110+
expected = ''"n" #null'';
111+
actual = toKdl { n = null; };
112+
}
113+
{
114+
description = "mixed args and block";
115+
expected = "\"mixed\" \"arg1\" {\n \"child\" \"val\"\n}";
116+
actual = toKdl {
117+
mixed = _: {
118+
props = "arg1";
119+
content = {
120+
child = "val";
121+
};
122+
};
123+
};
124+
}
125+
];
126+
127+
failedAssertions = builtins.filter (a: a.expected != a.actual) assertions;
128+
numPassed = builtins.length (builtins.filter (a: a.expected == a.actual) assertions);
129+
numFailed = builtins.length failedAssertions;
130+
reportFailed = lib.concatMapStringsSep "\n" (a: ''
131+
- ${a.description}:
132+
Expected (${toString (builtins.stringLength a.expected)} chars):
133+
${lib.concatMapStringsSep "\n" (l: " ${l}") (lib.splitString "\n" a.expected)}
134+
Actual (${toString (builtins.stringLength a.actual)} chars):
135+
${lib.concatMapStringsSep "\n" (l: " ${l}") (lib.splitString "\n" a.actual)}
136+
'') failedAssertions;
137+
in
138+
pkgs.runCommand "toKdl-test" { } ''
139+
echo "Testing toKdl function..."
140+
echo ""
141+
if [ ${toString numFailed} -gt 0 ]; then
142+
echo "FAILED: ${toString numFailed} test(s) failed, ${toString numPassed} passed"
143+
echo ""
144+
echo "Failed tests:"
145+
echo "${reportFailed}"
146+
exit 1
147+
else
148+
echo "PASSED: All ${toString numPassed} tests passed!"
149+
touch $out
150+
fi
151+
''

lib/lib.nix

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,144 @@ in
544544
mapAttrsToList0 =
545545
f: v: lib.imap0 (i: v: f i v.name v.value) (lib.mapAttrsToList lib.nameValuePair v);
546546

547+
/**
548+
genStr :: string -> int -> string
549+
550+
Generates a string by repeating the input string the specified number of times
551+
*/
552+
genStr = str: num: builtins.concatStringsSep "" (builtins.genList (_: str) num);
553+
554+
/**
555+
Converts a Nix value to a KDL document string.
556+
557+
The top-level argument, and individual nodes can be either an attrset or a list of attrsets:
558+
- Attrset: each pair becomes a node (in a child block if not the top level)
559+
- List of attrsets: each attrset of nodes is processed, and then they are concatenated in sequence.
560+
This is useful for when there are repeated node names
561+
562+
Inside nodes, attrsets and lists of attrsets create child blocks.
563+
564+
For any individual node, instead of providing the content as an attrset or an attrset of lists,
565+
you may instead provide a function.
566+
567+
Functions produce nodes with:
568+
- `props`: (optional) node arguments. May be an attrset, or a list containing mixed values and attrsets.
569+
Plain values are provided as arguments. Attrset values are mapped to properties, i.e. `nodename "key"="value" {}`.
570+
These values may not be sensibly nested further.
571+
- `content`: (optional) child block content (attrs or list of attrs, like top level)
572+
- `type`: (optional) a string to be placed in a type annotation on the node name. (If you provide a function returning a set with this field to props, it will add it to the value instead)
573+
- `custom`: (optional) a function of type `{ indent, lvl, name } -> string` which is to replace the node (including its name) with a custom string.
574+
575+
This means you can make a node with only a name like `toKdl { mynode = _: { }; }`, which will produce a string containing just `mynode`
576+
577+
If you provide a list which contains more than just attrsets as a node's value, it will be assumed to be arguments/properties instead.
578+
579+
If you provide a primitive value, it will likewise be considered to be an argument.
580+
581+
Otherwise, it will be assumed to be a block, and to pass arguments, you should use the function form.
582+
583+
The argument to the function is provided by calling the function with `lib.fix`.
584+
585+
The top level argument to `wlib.toKdl` may also be a function, but it is slightly different
586+
than the function form you can provide to a normal node.
587+
588+
As a top-level argument, you may provide a function like
589+
`_: { lvl = 0; indent = " "; content = set_or_list_of_sets; }`
590+
rather than passing the content directly as the argument.
591+
592+
This allows you to set the indentation level of the generated nodes, and indentation width/character.
593+
594+
Example:
595+
596+
```nix
597+
{
598+
# plain node (no args, no block)
599+
a = _: { };
600+
# primitive → argument
601+
b = 1;
602+
# list of primitives → multiple args
603+
c = [ "x" 2 true null ];
604+
# attrset → child block
605+
d = {
606+
x = 1;
607+
};
608+
# list of attrsets → repeated child nodes
609+
e = [
610+
{ x = 1; }
611+
{ x = 2; }
612+
];
613+
# function form: props (args + properties) + content (block)
614+
f = _: {
615+
props = [
616+
"arg1"
617+
{ key = "val"; }
618+
];
619+
content = {
620+
g = _: { };
621+
};
622+
};
623+
# function with only props (no block)
624+
h = _: {
625+
props = { k = "v"; };
626+
};
627+
# function with only content (block, no props)
628+
i = _: {
629+
content = {
630+
j = 1;
631+
};
632+
};
633+
# nested combination
634+
k = {
635+
l = [
636+
{ m = "a"; }
637+
{ m = "b"; }
638+
];
639+
};
640+
# typed argument in props (list form)
641+
n = [ (_: { type = "string"; content = "o"; }) ];
642+
# typed argument and typed property and block content
643+
p = _: {
644+
props = [ (_: { type = "string"; content = "q"; }) { r = (_: { type = "string"; content = "s"; }); } ];
645+
type = "string";
646+
content = {
647+
t = "u";
648+
};
649+
};
650+
}
651+
```
652+
653+
```kdl
654+
"a"
655+
"b" 1
656+
"c" "x" 2 true #null
657+
"d" {
658+
"x" 1
659+
}
660+
"e" {
661+
"x" 1
662+
"x" 2
663+
}
664+
"f" "arg1" "key"="val" {
665+
"g"
666+
}
667+
"h" "k"="v"
668+
"i" {
669+
"j" 1
670+
}
671+
"k" {
672+
"l" {
673+
"m" "a"
674+
"m" "b"
675+
}
676+
}
677+
"n" (string)"o"
678+
(string)"p" (string)"q" "r"=(string)"s" {
679+
"t" "u"
680+
}
681+
```
682+
*/
683+
toKdl = import ./toKdl.nix { inherit lib wlib; };
684+
547685
/**
548686
Placeholder value used when overriding a non-main field of a spec type.
549687

lib/toKdl.nix

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{ lib, wlib }:
2+
let
3+
inherit (builtins) isList isAttrs toJSON;
4+
listOfNodes = l: isList l && builtins.all isAttrs l;
5+
toKdlNode =
6+
indent_str: i: n: val:
7+
let
8+
mkArgs =
9+
args:
10+
let
11+
toVal =
12+
v:
13+
if isNull v then
14+
"#null"
15+
else if lib.isFunction v then
16+
let
17+
res = lib.fix v;
18+
in
19+
lib.optionalString (res ? type) "(${toString res.type})"
20+
+ lib.optionalString (res ? content) "${toJSON res.content}"
21+
else if isAttrs v || isList v then
22+
toJSON (toJSON v)
23+
else
24+
toJSON v;
25+
mkAttrsOrVal =
26+
attrs:
27+
if isAttrs attrs then
28+
lib.concatMapAttrsStringSep " " (n: v: "${toJSON n}=${toVal v}") attrs
29+
else
30+
toVal attrs;
31+
in
32+
if isList args then lib.concatMapStringsSep " " mkAttrsOrVal args else mkAttrsOrVal args;
33+
indent = wlib.genStr indent_str;
34+
special = lib.isFunction val;
35+
res = if special then lib.fix val else val;
36+
v = if special then res.content or null else res;
37+
nodetype = if res ? type then "(${toString res.type})" else "";
38+
attrs = if special && res ? props then mkArgs res.props else "";
39+
in
40+
if special && res ? custom then
41+
res.custom {
42+
indent = indent_str;
43+
lvl = i;
44+
name = n;
45+
}
46+
else if isAttrs v then
47+
''
48+
${indent i}${nodetype}${toJSON n} ${attrs} {
49+
${lib.concatMapAttrsStringSep "\n" (toKdlNode indent_str (i + 1)) v}
50+
${indent i}}''
51+
else if listOfNodes v then
52+
''
53+
${indent i}${nodetype}${toJSON n} ${attrs} {
54+
${lib.concatMapStringsSep "\n" (lib.concatMapAttrsStringSep "\n" (toKdlNode indent_str (i + 1))) v}
55+
${indent i}}''
56+
else if special then
57+
"${indent i}${nodetype}${toJSON n} ${attrs}"
58+
else
59+
"${indent i}${nodetype}${toJSON n} ${mkArgs v}";
60+
toKdl =
61+
indent: i: value:
62+
if isAttrs value then
63+
lib.concatMapAttrsStringSep "\n" (toKdlNode indent i) value
64+
else if listOfNodes value then
65+
lib.concatMapStringsSep "\n" (lib.concatMapAttrsStringSep "\n" (toKdlNode indent i)) value
66+
else
67+
throw "ERROR wlib.toKdl: argument to wlib.toKdl is expected to be an attrset or a list of attrsets which represent the top level nodes of a kdl file!";
68+
in
69+
value:
70+
if lib.isFunction value then
71+
let
72+
res = lib.fix value;
73+
in
74+
toKdl (res.indent or " ") (res.lvl or 0) res.content
75+
else
76+
toKdl " " 0 value

0 commit comments

Comments
 (0)