-
Notifications
You must be signed in to change notification settings - Fork 15
/
kdl.nix
185 lines (167 loc) · 4.73 KB
/
kdl.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
{lib, ...}:
with lib; let
node = name: args: children:
foldl (
self: this:
if isAttrs this
then self // {props = self.props // this;}
else self // {args = self.args ++ [this];}
) {
inherit name;
children = toList children;
args = [];
props = {};
} (toList args);
plain = name: node name [];
leaf = name: args: node name args [];
magic-leaf = node-name: {
${node-name} = [];
__functor = self: arg: {
inherit (self) __functor;
${node-name} = self.${node-name} ++ toList arg;
};
};
flag = name: node name [] [];
serialize.string = flip pipe [
(escape ["\\" "\""])
# including newlines will cause the serialized output to contain additional indentation
# so we escape them
(replaceStrings ["\n"] ["\\n"])
(v: "\"${v}\"")
];
serialize.path = serialize.string;
serialize.int = toString;
serialize.float = toString;
serialize.bool = v:
if v
then "true"
else "false";
serialize.null = const "null";
serialize.value = v: serialize.${builtins.typeOf v} v;
# this is not a complete list of valid identifiers
# but it is good enough for niri
# if this rejects a valid ident, literally nothing bad happens
# essentially, this regex boils down to any sequence of letters, numbers or +/-
# but not something that looks like a number (e.g. 0, -4, +12)
bare-ident = "[A-Za-z][A-Za-z0-9+-]*|[+-]|[+-][A-Za-z+-][A-Za-z0-9+-]*";
serialize.ident = v:
if strings.match bare-ident v != null
then v
else serialize.string v;
serialize.prop = {
name,
value,
}: "${serialize.ident name}=${serialize.value value}";
prefix-lines = prefix:
flip pipe [
(splitString "\n")
(map (s: "${prefix}${s}"))
(concatStringsSep "\n")
];
indent = prefix-lines " ";
count-lines = flip pipe [
(splitString "\n")
length
];
# a common pattern when declaring a config is to have "optional" nodes
# that only exist if a certain condition is met.
# without special handling, this would be done with list concatenation
# and lib.optional, which is ugly and hard to read.
# it's also not unthinkable that a user might want to declare many nodes
# in a separate function, and include in the current list.
# this function makes it easier to declare optional nodes
# or adding an infix list of nodes by ignoring null nodes, and flattening the result
# this is completely fine because in this context,
# nested lists are not meaningful and neither are null nodes.
transform-nodes = flip pipe [
flatten
(remove null)
];
internal-serialize-nodes = flip pipe [
(map serialize.node)
(concatStringsSep "\n")
];
serialize.node = {
name,
args,
props,
children,
}:
concatStringsSep " " (flatten [
(serialize.ident name)
(map serialize.value args)
(map serialize.prop (attrsToList props))
(
let
children' = transform-nodes children;
serialized = internal-serialize-nodes children';
in
if length children' == 0
then []
else if count-lines serialized == 1
then "{ ${serialized}; }"
else "{\n${indent serialized}\n}"
)
]);
serialize.nodes = flip pipe [
transform-nodes
internal-serialize-nodes
];
kdl-value = types.nullOr (
types.oneOf [
types.str
types.int
types.float
types.bool
]
);
kdl-node = types.submodule {
options.name = mkOption {
type = types.str;
};
options.args = mkOption {
type = types.listOf kdl-value;
default = [];
};
options.props = mkOption {
type = types.attrsOf kdl-value;
default = {};
};
options.children = mkOption {
type = kdl-nodes;
default = [];
};
};
kdl-leaf = mkOptionType {
name = "kdl-leaf";
description = "kdl leaf";
descriptionClass = "noun";
check = v: let
leaves = mapAttrsToList leaf (removeAttrs v ["__functor"]);
in
isAttrs v && length leaves == 1 && all kdl-node.check leaves;
merge = loc: defs: removeAttrs (mergeOneOption loc defs) ["__functor"];
};
kdl-args = mkOptionType {
name = "kdl-args";
description = "kdl arguments";
descriptionClass = "noun";
check = v: kdl-leaf.check {inherit v;};
};
kdl-nodes =
(types.oneOf [(types.listOf (types.nullOr kdl-nodes)) kdl-node])
// {
name = "kdl-nodes";
description = "kdl nodes";
descriptionClass = "noun";
};
kdl-document =
kdl-nodes
// {
name = "kdl-document";
description = "kdl document";
};
in {
inherit node plain leaf magic-leaf flag serialize;
types = {inherit kdl-value kdl-node kdl-nodes kdl-leaf kdl-args kdl-document;};
}