incus: fix container tests from image rename (#360305)
[NixPkgs.git] / pkgs / pkgs-lib / formats.nix
blob5bf51a7e6fe69e8b56b218aa67da38e02b96293a
1 { lib, pkgs }:
2 rec {
4   /*
6   Every following entry represents a format for program configuration files
7   used for `settings`-style options (see https://github.com/NixOS/rfcs/pull/42).
8   Each entry should look as follows:
10     <format> = <parameters>: {
11       #        ^^ Parameters for controlling the format
13       # The module system type most suitable for representing such a format
14       # The description needs to be overwritten for recursive types
15       type = ...;
17       # Utility functions for convenience, or special interactions with the
18       # format (optional)
19       lib = {
20         exampleFunction = ...
21         # Types specific to the format (optional)
22         types = { ... };
23         ...
24       };
26       # generate :: Name -> Value -> Path
27       # A function for generating a file with a value of such a type
28       generate = ...;
30     });
32   Please note that `pkgs` may not always be available for use due to the split
33   options doc build introduced in fc614c37c653, so lazy evaluation of only the
34   'type' field is required.
36   */
39   inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; })
40     javaProperties;
42   libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format;
44   hocon = (import ./formats/hocon/default.nix { inherit lib pkgs; }).format;
46   php = (import ./formats/php/default.nix { inherit lib pkgs; }).format;
48   inherit (lib) mkOptionType;
49   inherit (lib.types) nullOr oneOf coercedTo listOf nonEmptyListOf attrsOf either;
50   inherit (lib.types) bool int float str path;
52   json = {}: {
54     type = let
55       valueType = nullOr (oneOf [
56         bool
57         int
58         float
59         str
60         path
61         (attrsOf valueType)
62         (listOf valueType)
63       ]) // {
64         description = "JSON value";
65       };
66     in valueType;
68     generate = name: value: pkgs.callPackage ({ runCommand, jq }: runCommand name {
69       nativeBuildInputs = [ jq ];
70       value = builtins.toJSON value;
71       passAsFile = [ "value" ];
72       preferLocalBuild = true;
73     } ''
74       jq . "$valuePath"> $out
75     '') {};
77   };
79   yaml = yaml_1_1;
81   yaml_1_1 = {}: {
82     generate = name: value: pkgs.callPackage ({ runCommand, remarshal_0_17 }: runCommand name {
83       nativeBuildInputs = [ remarshal_0_17 ];
84       value = builtins.toJSON value;
85       passAsFile = [ "value" ];
86       preferLocalBuild = true;
87     } ''
88       json2yaml "$valuePath" "$out"
89     '') {};
91     type = let
92       valueType = nullOr (oneOf [
93         bool
94         int
95         float
96         str
97         path
98         (attrsOf valueType)
99         (listOf valueType)
100       ]) // {
101         description = "YAML value";
102       };
103     in valueType;
105   };
107   # the ini formats share a lot of code
108   inherit (
109     let
110       singleIniAtom = nullOr (oneOf [ bool int float str ]) // {
111         description = "INI atom (null, bool, int, float or string)";
112       };
113       iniAtom = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }:
114         let
115           singleIniAtomOr = if atomsCoercedToLists then coercedTo singleIniAtom lib.singleton else either singleIniAtom;
116         in
117         if listsAsDuplicateKeys then
118           singleIniAtomOr (listOf singleIniAtom) // {
119             description = singleIniAtom.description + " or a list of them for duplicate keys";
120           }
121         else if listToValue != null then
122           singleIniAtomOr (nonEmptyListOf singleIniAtom) // {
123             description = singleIniAtom.description + " or a non-empty list of them";
124           }
125         else
126           singleIniAtom;
127       iniSection = atom:
128         attrsOf atom // {
129           description = "section of an INI file (attrs of " + atom.description + ")";
130         };
132       maybeToList = listToValue: if listToValue != null then lib.mapAttrs (key: val: if lib.isList val then listToValue val else val) else lib.id;
133     in {
134       ini = {
135         # Represents lists as duplicate keys
136         listsAsDuplicateKeys ? false,
137         # Alternative to listsAsDuplicateKeys, converts list to non-list
138         # listToValue :: [IniAtom] -> IniAtom
139         listToValue ? null,
140         # Merge multiple instances of the same key into a list
141         atomsCoercedToLists ? null,
142         ...
143         }@args:
144         assert listsAsDuplicateKeys -> listToValue == null;
145         assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
146         let
147           atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
148           atom = iniAtom { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
149         in
150         {
152         type = lib.types.attrsOf (
153           iniSection atom
154         );
155         lib.types.atom = atom;
157         generate = name: value:
158           lib.pipe value
159           [
160             (lib.mapAttrs (_: maybeToList listToValue))
161             (lib.generators.toINI (removeAttrs args ["listToValue" "atomsCoercedToLists"]))
162             (pkgs.writeText name)
163           ];
164       };
166       iniWithGlobalSection = {
167         # Represents lists as duplicate keys
168         listsAsDuplicateKeys ? false,
169         # Alternative to listsAsDuplicateKeys, converts list to non-list
170         # listToValue :: [IniAtom] -> IniAtom
171         listToValue ? null,
172         # Merge multiple instances of the same key into a list
173         atomsCoercedToLists ? null,
174         ...
175         }@args:
176         assert listsAsDuplicateKeys -> listToValue == null;
177         assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
178         let
179           atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
180           atom = iniAtom { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
181         in
182         {
183           type = lib.types.submodule {
184             options = {
185               sections = lib.mkOption rec {
186                 type = lib.types.attrsOf (
187                   iniSection atom
188                 );
189                 default = {};
190                 description = type.description;
191               };
192               globalSection = lib.mkOption rec {
193                 type = iniSection atom;
194                 default = {};
195                 description = "global " + type.description;
196               };
197             };
198           };
199           lib.types.atom = atom;
200           generate = name: { sections ? {}, globalSection ? {}, ... }:
201             pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue" "atomsCoercedToLists"])
202             {
203               globalSection = maybeToList listToValue globalSection;
204               sections = lib.mapAttrs (_: maybeToList listToValue) sections;
205             });
206         };
208       gitIni = { listsAsDuplicateKeys ? false, ... }@args:
209         let
210           atom = iniAtom {
211             inherit listsAsDuplicateKeys;
212             listToValue = null;
213             atomsCoercedToLists = false;
214           };
215         in
216         {
217           type = attrsOf (attrsOf (either atom (attrsOf atom)));
218           lib.types.atom = atom;
219           generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
220         };
222     }) ini iniWithGlobalSection gitIni;
224   # As defined by systemd.syntax(7)
225   #
226   # null does not set any value, which allows for RFC42 modules to specify
227   # optional config options.
228   systemd = let
229     mkValueString = lib.generators.mkValueStringDefault {};
230     mkKeyValue = k: v:
231       if v == null then "# ${k} is unset"
232       else "${k} = ${mkValueString v}";
233   in ini {
234     listsAsDuplicateKeys = true;
235     inherit mkKeyValue;
236   };
238   keyValue = {
239     # Represents lists as duplicate keys
240     listsAsDuplicateKeys ? false,
241     # Alternative to listsAsDuplicateKeys, converts list to non-list
242     # listToValue :: [Atom] -> Atom
243     listToValue ? null,
244     ...
245     }@args:
246     assert listsAsDuplicateKeys -> listToValue == null;
247     {
249     type = let
251       singleAtom = nullOr (oneOf [
252         bool
253         int
254         float
255         str
256       ]) // {
257         description = "atom (null, bool, int, float or string)";
258       };
260       atom =
261         if listsAsDuplicateKeys then
262           coercedTo singleAtom lib.singleton (listOf singleAtom) // {
263             description = singleAtom.description + " or a list of them for duplicate keys";
264           }
265         else if listToValue != null then
266           coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
267             description = singleAtom.description + " or a non-empty list of them";
268           }
269         else
270           singleAtom;
272     in attrsOf atom;
274     generate = name: value:
275       let
276         transformedValue =
277           if listToValue != null
278           then
279             lib.mapAttrs (key: val:
280               if lib.isList val then listToValue val else val
281             ) value
282           else value;
283       in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
285   };
287   toml = {}: json {} // {
288     type = let
289       valueType = oneOf [
290         bool
291         int
292         float
293         str
294         path
295         (attrsOf valueType)
296         (listOf valueType)
297       ] // {
298         description = "TOML value";
299       };
300     in valueType;
302     generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
303       nativeBuildInputs = [ remarshal ];
304       value = builtins.toJSON value;
305       passAsFile = [ "value" ];
306       preferLocalBuild = true;
307     } ''
308       json2toml "$valuePath" "$out"
309     '') {};
311   };
313   /* For configurations of Elixir project, like config.exs or runtime.exs
315     Most Elixir project are configured using the [Config] Elixir DSL
317     Since Elixir has more types than Nix, we need a way to map Nix types to
318     more than 1 Elixir type. To that end, this format provides its own library,
319     and its own set of types.
321     To be more detailed, a Nix attribute set could correspond in Elixir to a
322     [Keyword list] (the more common type), or it could correspond to a [Map].
324     A Nix string could correspond in Elixir to a [String] (also called
325     "binary"), an [Atom], or a list of chars (usually discouraged).
327     A Nix array could correspond in Elixir to a [List] or a [Tuple].
329     Some more types exists, like records, regexes, but since they are less used,
330     we can leave the `mkRaw` function as an escape hatch.
332     For more information on how to use this format in modules, please refer to
333     the Elixir section of the Nixos documentation.
335     TODO: special Elixir values doesn't show up nicely in the documentation
337     [Config]: <https://hexdocs.pm/elixir/Config.html>
338     [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html>
339     [Map]: <https://hexdocs.pm/elixir/Map.html>
340     [String]: <https://hexdocs.pm/elixir/String.html>
341     [Atom]: <https://hexdocs.pm/elixir/Atom.html>
342     [List]: <https://hexdocs.pm/elixir/List.html>
343     [Tuple]: <https://hexdocs.pm/elixir/Tuple.html>
344   */
345   elixirConf = { elixir ? pkgs.elixir }:
346     let
347       toElixir = value:
348         if value == null then "nil" else
349         if value == true then "true" else
350         if value == false then "false" else
351         if lib.isInt value || lib.isFloat value then toString value else
352         if lib.isString value then string value else
353         if lib.isAttrs value then attrs value else
354         if lib.isList value then list value else
355         abort "formats.elixirConf: should never happen (value = ${value})";
357       escapeElixir = lib.escape [ "\\" "#" "\"" ];
358       string = value: "\"${escapeElixir value}\"";
360       attrs = set:
361         if set ? _elixirType then specialType set
362         else
363           let
364             toKeyword = name: value: "${name}: ${toElixir value}";
365             keywordList = lib.concatStringsSep ", " (lib.mapAttrsToList toKeyword set);
366           in
367           "[" + keywordList + "]";
369       listContent = values: lib.concatStringsSep ", " (map toElixir values);
371       list = values: "[" + (listContent values) + "]";
373       specialType = { value, _elixirType }:
374         if _elixirType == "raw" then value else
375         if _elixirType == "atom" then value else
376         if _elixirType == "map" then elixirMap value else
377         if _elixirType == "tuple" then tuple value else
378         abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})";
380       elixirMap = set:
381         let
382           toEntry = name: value: "${toElixir name} => ${toElixir value}";
383           entries = lib.concatStringsSep ", " (lib.mapAttrsToList toEntry set);
384         in
385         "%{${entries}}";
387       tuple = values: "{${listContent values}}";
389       toConf = values:
390         let
391           keyConfig = rootKey: key: value:
392             "config ${rootKey}, ${key}, ${toElixir value}";
393           keyConfigs = rootKey: values: lib.mapAttrsToList (keyConfig rootKey) values;
394           rootConfigs = lib.flatten (lib.mapAttrsToList keyConfigs values);
395         in
396         ''
397           import Config
399           ${lib.concatStringsSep "\n" rootConfigs}
400         '';
401     in
402     {
403       type = let
404         valueType = nullOr
405           (oneOf [
406             bool
407             int
408             float
409             str
410             (attrsOf valueType)
411             (listOf valueType)
412           ]) // {
413           description = "Elixir value";
414         };
415       in
416       attrsOf (attrsOf (valueType));
418       lib =
419         let
420           mkRaw = value: {
421             inherit value;
422             _elixirType = "raw";
423           };
425         in
426         {
427           inherit mkRaw;
429           /* Fetch an environment variable at runtime, with optional fallback
430           */
431           mkGetEnv = { envVariable, fallback ? null }:
432             mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})";
434           /* Make an Elixir atom.
436             Note: lowercase atoms still need to be prefixed by ':'
437           */
438           mkAtom = value: {
439             inherit value;
440             _elixirType = "atom";
441           };
443           /* Make an Elixir tuple out of a list.
444           */
445           mkTuple = value: {
446             inherit value;
447             _elixirType = "tuple";
448           };
450           /* Make an Elixir map out of an attribute set.
451           */
452           mkMap = value: {
453             inherit value;
454             _elixirType = "map";
455           };
457           /* Contains Elixir types. Every type it exports can also be replaced
458              by raw Elixir code (i.e. every type is `either type rawElixir`).
460              It also reexports standard types, wrapping them so that they can
461              also be raw Elixir.
462           */
463           types = let
464             isElixirType = type: x: (x._elixirType or "") == type;
466             rawElixir = mkOptionType {
467               name = "rawElixir";
468               description = "raw elixir";
469               check = isElixirType "raw";
470             };
472             elixirOr = other: either other rawElixir;
473           in
474           {
475             inherit rawElixir elixirOr;
477             atom = elixirOr (mkOptionType {
478               name = "elixirAtom";
479               description = "elixir atom";
480               check = isElixirType "atom";
481             });
483             tuple = elixirOr (mkOptionType {
484               name = "elixirTuple";
485               description = "elixir tuple";
486               check = isElixirType "tuple";
487             });
489             map = elixirOr (mkOptionType {
490               name = "elixirMap";
491               description = "elixir map";
492               check = isElixirType "map";
493             });
494             # Wrap standard types, since anything in the Elixir configuration
495             # can be raw Elixir
496           } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
497         };
499       generate = name: value: pkgs.runCommand name
500         {
501           value = toConf value;
502           passAsFile = [ "value" ];
503           nativeBuildInputs = [ elixir ];
504           preferLocalBuild = true;
505         } ''
506         cp "$valuePath" "$out"
507         mix format "$out"
508       '';
509     };
511   # Outputs a succession of Python variable assignments
512   # Useful for many Django-based services
513   pythonVars = {}: {
514     type = let
515       valueType = nullOr(oneOf [
516         bool
517         float
518         int
519         path
520         str
521         (attrsOf valueType)
522         (listOf valueType)
523       ]) // {
524         description = "Python value";
525       };
526     in attrsOf valueType;
527     generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
528       nativeBuildInputs = [ python3 black ];
529       value = builtins.toJSON value;
530       pythonGen = ''
531         import json
532         import os
534         with open(os.environ["valuePath"], "r") as f:
535             for key, value in json.load(f).items():
536                 print(f"{key} = {repr(value)}")
537       '';
538       passAsFile = [ "value" "pythonGen" ];
539       preferLocalBuild = true;
540     } ''
541       cat "$valuePath"
542       python3 "$pythonGenPath" > $out
543       black $out
544     '') {};
545   };