1/* Functions that generate widespread file
2 * formats from nix data structures.
3 *
4 * They all follow a similar interface:
5 * generator { config-attrs } data
6 *
7 * `config-attrs` are “holes” in the generators
8 * with sensible default implementations that
9 * can be overwritten. The default implementations
10 * are mostly generators themselves, called with
11 * their respective default values; they can be reused.
12 *
13 * Tests can be found in ./tests.nix
14 * Documentation in the manual, #sec-generators
15 */
16{ lib }:
17with (lib).trivial;
18let
19 libStr = lib.strings;
20 libAttr = lib.attrsets;
21
22 inherit (lib) isFunction;
23in
24
25rec {
26
27 ## -- HELPER FUNCTIONS & DEFAULTS --
28
29 /* Convert a value to a sensible default string representation.
30 * The builtin `toString` function has some strange defaults,
31 * suitable for bash scripts but not much else.
32 */
33 mkValueStringDefault = {}: v: with builtins;
34 let err = t: v: abort
35 ("generators.mkValueStringDefault: " +
36 "${t} not supported: ${toPretty {} v}");
37 in if isInt v then toString v
38 # we default to not quoting strings
39 else if isString v then v
40 # isString returns "1", which is not a good default
41 else if true == v then "true"
42 # here it returns to "", which is even less of a good default
43 else if false == v then "false"
44 else if null == v then "null"
45 # if you have lists you probably want to replace this
46 else if isList v then err "lists" v
47 # same as for lists, might want to replace
48 else if isAttrs v then err "attrsets" v
49 else if isFunction v then err "functions" v
50 else err "this value is" (toString v);
51
52
53 /* Generate a line of key k and value v, separated by
54 * character sep. If sep appears in k, it is escaped.
55 * Helper for synaxes with different separators.
56 *
57 * mkValueString specifies how values should be formatted.
58 *
59 * mkKeyValueDefault {} ":" "f:oo" "bar"
60 * > "f\:oo:bar"
61 */
62 mkKeyValueDefault = {
63 mkValueString ? mkValueStringDefault {}
64 }: sep: k: v:
65 "${libStr.escape [sep] k}${sep}${mkValueString v}";
66
67
68 ## -- FILE FORMAT GENERATORS --
69
70
71 /* Generate a key-value-style config file from an attrset.
72 *
73 * mkKeyValue is the same as in toINI.
74 */
75 toKeyValue = {
76 mkKeyValue ? mkKeyValueDefault {} "="
77 }: attrs:
78 let mkLine = k: v: mkKeyValue k v + "\n";
79 in libStr.concatStrings (libAttr.mapAttrsToList mkLine attrs);
80
81
82 /* Generate an INI-style config file from an
83 * attrset of sections to an attrset of key-value pairs.
84 *
85 * generators.toINI {} {
86 * foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
87 * baz = { "also, integers" = 42; };
88 * }
89 *
90 *> [baz]
91 *> also, integers=42
92 *>
93 *> [foo]
94 *> ciao=bar
95 *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
96 *
97 * The mk* configuration attributes can generically change
98 * the way sections and key-value strings are generated.
99 *
100 * For more examples see the test cases in ./tests.nix.
101 */
102 toINI = {
103 # apply transformations (e.g. escapes) to section names
104 mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
105 # format a setting line from key and value
106 mkKeyValue ? mkKeyValueDefault {} "="
107 }: attrsOfAttrs:
108 let
109 # map function to string for each key val
110 mapAttrsToStringsSep = sep: mapFn: attrs:
111 libStr.concatStringsSep sep
112 (libAttr.mapAttrsToList mapFn attrs);
113 mkSection = sectName: sectValues: ''
114 [${mkSectionName sectName}]
115 '' + toKeyValue { inherit mkKeyValue; } sectValues;
116 in
117 # map input to ini sections
118 mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
119
120
121 /* Generates JSON from an arbitrary (non-function) value.
122 * For more information see the documentation of the builtin.
123 */
124 toJSON = {}: builtins.toJSON;
125
126
127 /* YAML has been a strict superset of JSON since 1.2, so we
128 * use toJSON. Before it only had a few differences referring
129 * to implicit typing rules, so it should work with older
130 * parsers as well.
131 */
132 toYAML = {}@args: toJSON args;
133
134
135 /* Pretty print a value, akin to `builtins.trace`.
136 * Should probably be a builtin as well.
137 */
138 toPretty = {
139 /* If this option is true, attrsets like { __pretty = fn; val = …; }
140 will use fn to convert val to a pretty printed representation.
141 (This means fn is type Val -> String.) */
142 allowPrettyValues ? false
143 }@args: v: with builtins;
144 let isPath = v: typeOf v == "path";
145 in if isInt v then toString v
146 else if isString v then ''"${libStr.escape [''"''] v}"''
147 else if true == v then "true"
148 else if false == v then "false"
149 else if null == v then "null"
150 else if isPath v then toString v
151 else if isList v then "[ "
152 + libStr.concatMapStringsSep " " (toPretty args) v
153 + " ]"
154 else if isAttrs v then
155 # apply pretty values if allowed
156 if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
157 then v.__pretty v.val
158 # TODO: there is probably a better representation?
159 else if v ? type && v.type == "derivation" then
160 "<δ:${v.name}>"
161 # "<δ:${concatStringsSep "," (builtins.attrNames v)}>"
162 else "{ "
163 + libStr.concatStringsSep " " (libAttr.mapAttrsToList
164 (name: value:
165 "${toPretty args name} = ${toPretty args value};") v)
166 + " }"
167 else if isFunction v then
168 let fna = lib.functionArgs v;
169 showFnas = concatStringsSep "," (libAttr.mapAttrsToList
170 (name: hasDefVal: if hasDefVal then "(${name})" else name)
171 fna);
172 in if fna == {} then "<λ>"
173 else "<λ:{${showFnas}}>"
174 else abort "generators.toPretty: should never happen (v = ${v})";
175
176 # PLIST handling
177 toPlist = {}: v: let
178 isFloat = builtins.isFloat or (x: false);
179 expr = ind: x: with builtins;
180 if isNull x then "" else
181 if isBool x then bool ind x else
182 if isInt x then int ind x else
183 if isString x then str ind x else
184 if isList x then list ind x else
185 if isAttrs x then attrs ind x else
186 if isFloat x then float ind x else
187 abort "generators.toPlist: should never happen (v = ${v})";
188
189 literal = ind: x: ind + x;
190
191 bool = ind: x: literal ind (if x then "<true/>" else "<false/>");
192 int = ind: x: literal ind "<integer>${toString x}</integer>";
193 str = ind: x: literal ind "<string>${x}</string>";
194 key = ind: x: literal ind "<key>${x}</key>";
195 float = ind: x: literal ind "<real>${toString x}</real>";
196
197 indent = ind: expr "\t${ind}";
198
199 item = ind: libStr.concatMapStringsSep "\n" (indent ind);
200
201 list = ind: x: libStr.concatStringsSep "\n" [
202 (literal ind "<array>")
203 (item ind x)
204 (literal ind "</array>")
205 ];
206
207 attrs = ind: x: libStr.concatStringsSep "\n" [
208 (literal ind "<dict>")
209 (attr ind x)
210 (literal ind "</dict>")
211 ];
212
213 attr = let attrFilter = name: value: name != "_module" && value != null;
214 in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList
215 (name: value: lib.optional (attrFilter name value) [
216 (key "\t${ind}" name)
217 (expr "\t${ind}" value)
218 ]) x));
219
220 in ''<?xml version="1.0" encoding="UTF-8"?>
221<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
222<plist version="1.0">
223${expr "" v}
224</plist>'';
225
226}