dump_syms: Add consumers into passthru.tests
[NixPkgs.git] / lib / generators.nix
blobb77cca75010f983f9723c881766b164e2d781a0d
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/misc.nix
14  * Documentation in the manual, #sec-generators
15  */
16 { lib }:
17 with (lib).trivial;
18 let
19   libStr = lib.strings;
20   libAttr = lib.attrsets;
22   inherit (lib) isFunction;
25 rec {
27   ## -- HELPER FUNCTIONS & DEFAULTS --
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     # convert derivations to store paths
39     else if lib.isDerivation v then toString v
40     # we default to not quoting strings
41     else if isString   v then v
42     # isString returns "1", which is not a good default
43     else if true  ==   v then "true"
44     # here it returns to "", which is even less of a good default
45     else if false ==   v then "false"
46     else if null  ==   v then "null"
47     # if you have lists you probably want to replace this
48     else if isList     v then err "lists" v
49     # same as for lists, might want to replace
50     else if isAttrs    v then err "attrsets" v
51     # functions can’t be printed of course
52     else if isFunction v then err "functions" v
53     # Floats currently can't be converted to precise strings,
54     # condition warning on nix version once this isn't a problem anymore
55     # See https://github.com/NixOS/nix/pull/3480
56     else if isFloat    v then libStr.floatToString v
57     else err "this value is" (toString v);
60   /* Generate a line of key k and value v, separated by
61    * character sep. If sep appears in k, it is escaped.
62    * Helper for synaxes with different separators.
63    *
64    * mkValueString specifies how values should be formatted.
65    *
66    * mkKeyValueDefault {} ":" "f:oo" "bar"
67    * > "f\:oo:bar"
68    */
69   mkKeyValueDefault = {
70     mkValueString ? mkValueStringDefault {}
71   }: sep: k: v:
72     "${libStr.escape [sep] k}${sep}${mkValueString v}";
75   ## -- FILE FORMAT GENERATORS --
78   /* Generate a key-value-style config file from an attrset.
79    *
80    * mkKeyValue is the same as in toINI.
81    */
82   toKeyValue = {
83     mkKeyValue ? mkKeyValueDefault {} "=",
84     listsAsDuplicateKeys ? false
85   }:
86   let mkLine = k: v: mkKeyValue k v + "\n";
87       mkLines = if listsAsDuplicateKeys
88         then k: v: map (mkLine k) (if lib.isList v then v else [v])
89         else k: v: [ (mkLine k v) ];
90   in attrs: libStr.concatStrings (lib.concatLists (libAttr.mapAttrsToList mkLines attrs));
93   /* Generate an INI-style config file from an
94    * attrset of sections to an attrset of key-value pairs.
95    *
96    * generators.toINI {} {
97    *   foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
98    *   baz = { "also, integers" = 42; };
99    * }
100    *
101    *> [baz]
102    *> also, integers=42
103    *>
104    *> [foo]
105    *> ciao=bar
106    *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
107    *
108    * The mk* configuration attributes can generically change
109    * the way sections and key-value strings are generated.
110    *
111    * For more examples see the test cases in ./tests/misc.nix.
112    */
113   toINI = {
114     # apply transformations (e.g. escapes) to section names
115     mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
116     # format a setting line from key and value
117     mkKeyValue    ? mkKeyValueDefault {} "=",
118     # allow lists as values for duplicate keys
119     listsAsDuplicateKeys ? false
120   }: attrsOfAttrs:
121     let
122         # map function to string for each key val
123         mapAttrsToStringsSep = sep: mapFn: attrs:
124           libStr.concatStringsSep sep
125             (libAttr.mapAttrsToList mapFn attrs);
126         mkSection = sectName: sectValues: ''
127           [${mkSectionName sectName}]
128         '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues;
129     in
130       # map input to ini sections
131       mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
133   /* Generate an INI-style config file from an attrset
134    * specifying the global section (no header), and an
135    * attrset of sections to an attrset of key-value pairs.
136    *
137    * generators.toINIWithGlobalSection {} {
138    *   globalSection = {
139    *     someGlobalKey = "hi";
140    *   };
141    *   sections = {
142    *     foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
143    *     baz = { "also, integers" = 42; };
144    * }
145    *
146    *> someGlobalKey=hi
147    *>
148    *> [baz]
149    *> also, integers=42
150    *>
151    *> [foo]
152    *> ciao=bar
153    *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
154    *
155    * The mk* configuration attributes can generically change
156    * the way sections and key-value strings are generated.
157    *
158    * For more examples see the test cases in ./tests/misc.nix.
159    *
160    * If you don’t need a global section, you can also use
161    * `generators.toINI` directly, which only takes
162    * the part in `sections`.
163    */
164   toINIWithGlobalSection = {
165     # apply transformations (e.g. escapes) to section names
166     mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
167     # format a setting line from key and value
168     mkKeyValue    ? mkKeyValueDefault {} "=",
169     # allow lists as values for duplicate keys
170     listsAsDuplicateKeys ? false
171   }: { globalSection, sections }:
172     ( if globalSection == {}
173       then ""
174       else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection)
175            + "\n")
176     + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections);
178   /* Generate a git-config file from an attrset.
179    *
180    * It has two major differences from the regular INI format:
181    *
182    * 1. values are indented with tabs
183    * 2. sections can have sub-sections
184    *
185    * generators.toGitINI {
186    *   url."ssh://git@github.com/".insteadOf = "https://github.com";
187    *   user.name = "edolstra";
188    * }
189    *
190    *> [url "ssh://git@github.com/"]
191    *>   insteadOf = https://github.com/
192    *>
193    *> [user]
194    *>   name = edolstra
195    */
196   toGitINI = attrs:
197     with builtins;
198     let
199       mkSectionName = name:
200         let
201           containsQuote = libStr.hasInfix ''"'' name;
202           sections = libStr.splitString "." name;
203           section = head sections;
204           subsections = tail sections;
205           subsection = concatStringsSep "." subsections;
206         in if containsQuote || subsections == [ ] then
207           name
208         else
209           ''${section} "${subsection}"'';
211       # generation for multiple ini values
212       mkKeyValue = k: v:
213         let mkKeyValue = mkKeyValueDefault { } " = " k;
214         in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (lib.toList v));
216       # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
217       gitFlattenAttrs = let
218         recurse = path: value:
219           if isAttrs value && !lib.isDerivation value then
220             lib.mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
221           else if length path > 1 then {
222             ${concatStringsSep "." (lib.reverseList (tail path))}.${head path} = value;
223           } else {
224             ${head path} = value;
225           };
226       in attrs: lib.foldl lib.recursiveUpdate { } (lib.flatten (recurse [ ] attrs));
228       toINI_ = toINI { inherit mkKeyValue mkSectionName; };
229     in
230       toINI_ (gitFlattenAttrs attrs);
232   /* Generates JSON from an arbitrary (non-function) value.
233     * For more information see the documentation of the builtin.
234     */
235   toJSON = {}: builtins.toJSON;
238   /* YAML has been a strict superset of JSON since 1.2, so we
239     * use toJSON. Before it only had a few differences referring
240     * to implicit typing rules, so it should work with older
241     * parsers as well.
242     */
243   toYAML = toJSON;
245   withRecursion =
246     {
247       /* If this option is not null, the given value will stop evaluating at a certain depth */
248       depthLimit
249       /* If this option is true, an error will be thrown, if a certain given depth is exceeded */
250     , throwOnDepthLimit ? true
251     }:
252       assert builtins.isInt depthLimit;
253       let
254         specialAttrs = [
255           "__functor"
256           "__functionArgs"
257           "__toString"
258           "__pretty"
259         ];
260         stepIntoAttr = evalNext: name:
261           if builtins.elem name specialAttrs
262             then id
263             else evalNext;
264         transform = depth:
265           if depthLimit != null && depth > depthLimit then
266             if throwOnDepthLimit
267               then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!"
268               else const "<unevaluated>"
269           else id;
270         mapAny = with builtins; depth: v:
271           let
272             evalNext = x: mapAny (depth + 1) (transform (depth + 1) x);
273           in
274             if isAttrs v then mapAttrs (stepIntoAttr evalNext) v
275             else if isList v then map evalNext v
276             else transform (depth + 1) v;
277       in
278         mapAny 0;
280   /* Pretty print a value, akin to `builtins.trace`.
281     * Should probably be a builtin as well.
282     */
283   toPretty = {
284     /* If this option is true, attrsets like { __pretty = fn; val = …; }
285        will use fn to convert val to a pretty printed representation.
286        (This means fn is type Val -> String.) */
287     allowPrettyValues ? false,
288     /* If this option is true, the output is indented with newlines for attribute sets and lists */
289     multiline ? true
290   }:
291     let
292     go = indent: v: with builtins;
293     let     isPath   = v: typeOf v == "path";
294             introSpace = if multiline then "\n${indent}  " else " ";
295             outroSpace = if multiline then "\n${indent}" else " ";
296     in if   isInt      v then toString v
297     else if isFloat    v then "~${toString v}"
298     else if isString   v then
299       let
300         # Separate a string into its lines
301         newlineSplits = filter (v: ! isList v) (builtins.split "\n" v);
302         # For a '' string terminated by a \n, which happens when the closing '' is on a new line
303         multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''";
304         # For a '' string not terminated by a \n, which happens when the closing '' is not on a new line
305         multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''";
306         # For single lines, replace all newlines with their escaped representation
307         singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\"";
308       in if multiline && length newlineSplits > 1 then
309         if lib.last newlineSplits == "" then multilineResult else multilineResult'
310       else singlelineResult
311     else if true  ==   v then "true"
312     else if false ==   v then "false"
313     else if null  ==   v then "null"
314     else if isPath     v then toString v
315     else if isList     v then
316       if v == [] then "[ ]"
317       else "[" + introSpace
318         + libStr.concatMapStringsSep introSpace (go (indent + "  ")) v
319         + outroSpace + "]"
320     else if isFunction v then
321       let fna = lib.functionArgs v;
322           showFnas = concatStringsSep ", " (libAttr.mapAttrsToList
323                        (name: hasDefVal: if hasDefVal then name + "?" else name)
324                        fna);
325       in if fna == {}    then "<function>"
326                          else "<function, args: {${showFnas}}>"
327     else if isAttrs    v then
328       # apply pretty values if allowed
329       if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
330          then v.__pretty v.val
331       else if v == {} then "{ }"
332       else if v ? type && v.type == "derivation" then
333         "<derivation ${v.drvPath or "???"}>"
334       else "{" + introSpace
335           + libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
336               (name: value:
337                 "${libStr.escapeNixIdentifier name} = ${go (indent + "  ") value};") v)
338         + outroSpace + "}"
339     else abort "generators.toPretty: should never happen (v = ${v})";
340   in go "";
342   # PLIST handling
343   toPlist = {}: v: let
344     isFloat = builtins.isFloat or (x: false);
345     expr = ind: x:  with builtins;
346       if x == null  then "" else
347       if isBool x   then bool ind x else
348       if isInt x    then int ind x else
349       if isString x then str ind x else
350       if isList x   then list ind x else
351       if isAttrs x  then attrs ind x else
352       if isFloat x  then float ind x else
353       abort "generators.toPlist: should never happen (v = ${v})";
355     literal = ind: x: ind + x;
357     bool = ind: x: literal ind  (if x then "<true/>" else "<false/>");
358     int = ind: x: literal ind "<integer>${toString x}</integer>";
359     str = ind: x: literal ind "<string>${x}</string>";
360     key = ind: x: literal ind "<key>${x}</key>";
361     float = ind: x: literal ind "<real>${toString x}</real>";
363     indent = ind: expr "\t${ind}";
365     item = ind: libStr.concatMapStringsSep "\n" (indent ind);
367     list = ind: x: libStr.concatStringsSep "\n" [
368       (literal ind "<array>")
369       (item ind x)
370       (literal ind "</array>")
371     ];
373     attrs = ind: x: libStr.concatStringsSep "\n" [
374       (literal ind "<dict>")
375       (attr ind x)
376       (literal ind "</dict>")
377     ];
379     attr = let attrFilter = name: value: name != "_module" && value != null;
380     in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList
381       (name: value: lib.optionals (attrFilter name value) [
382       (key "\t${ind}" name)
383       (expr "\t${ind}" value)
384     ]) x));
386   in ''<?xml version="1.0" encoding="UTF-8"?>
387 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
388 <plist version="1.0">
389 ${expr "" v}
390 </plist>'';
392   /* Translate a simple Nix expression to Dhall notation.
393    * Note that integers are translated to Integer and never
394    * the Natural type.
395   */
396   toDhall = { }@args: v:
397     with builtins;
398     let concatItems = lib.strings.concatStringsSep ", ";
399     in if isAttrs v then
400       "{ ${
401         concatItems (lib.attrsets.mapAttrsToList
402           (key: value: "${key} = ${toDhall args value}") v)
403       } }"
404     else if isList v then
405       "[ ${concatItems (map (toDhall args) v)} ]"
406     else if isInt v then
407       "${if v < 0 then "" else "+"}${toString v}"
408     else if isBool v then
409       (if v then "True" else "False")
410     else if isFunction v then
411       abort "generators.toDhall: cannot convert a function to Dhall"
412     else if isNull v then
413       abort "generators.toDhall: cannot convert a null to Dhall"
414     else
415       builtins.toJSON v;