anvil-editor: init at 0.4
[NixPkgs.git] / pkgs / pkgs-lib / formats.nix
blob8afccf3e03485a0faccbe4b93a9f9c3d85349acf
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 = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }@args:
128         attrsOf (iniAtom args) // {
129           description = "section of an INI file (attrs of " + (iniAtom args).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         in
149         {
151         type = lib.types.attrsOf (
152           iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }
153         );
155         generate = name: value:
156           lib.pipe value
157           [
158             (lib.mapAttrs (_: maybeToList listToValue))
159             (lib.generators.toINI (removeAttrs args ["listToValue" "atomsCoercedToLists"]))
160             (pkgs.writeText name)
161           ];
162       };
164       iniWithGlobalSection = {
165         # Represents lists as duplicate keys
166         listsAsDuplicateKeys ? false,
167         # Alternative to listsAsDuplicateKeys, converts list to non-list
168         # listToValue :: [IniAtom] -> IniAtom
169         listToValue ? null,
170         # Merge multiple instances of the same key into a list
171         atomsCoercedToLists ? null,
172         ...
173         }@args:
174         assert listsAsDuplicateKeys -> listToValue == null;
175         assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
176         let
177           atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
178         in
179         {
180           type = lib.types.submodule {
181             options = {
182               sections = lib.mkOption rec {
183                 type = lib.types.attrsOf (
184                   iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }
185                 );
186                 default = {};
187                 description = type.description;
188               };
189               globalSection = lib.mkOption rec {
190                 type = iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
191                 default = {};
192                 description = "global " + type.description;
193               };
194             };
195           };
196           generate = name: { sections ? {}, globalSection ? {}, ... }:
197             pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue" "atomsCoercedToLists"])
198             {
199               globalSection = maybeToList listToValue globalSection;
200               sections = lib.mapAttrs (_: maybeToList listToValue) sections;
201             });
202         };
204       gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
205         type = let
206           atom = iniAtom {
207             listsAsDuplicateKeys = listsAsDuplicateKeys;
208             listToValue = null;
209             atomsCoercedToLists = false;
210           };
211         in attrsOf (attrsOf (either atom (attrsOf atom)));
213         generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
214       };
216     }) ini iniWithGlobalSection gitIni;
218   # As defined by systemd.syntax(7)
219   #
220   # null does not set any value, which allows for RFC42 modules to specify
221   # optional config options.
222   systemd = let
223     mkValueString = lib.generators.mkValueStringDefault {};
224     mkKeyValue = k: v:
225       if v == null then "# ${k} is unset"
226       else "${k} = ${mkValueString v}";
227   in ini {
228     listsAsDuplicateKeys = true;
229     inherit mkKeyValue;
230   };
232   keyValue = {
233     # Represents lists as duplicate keys
234     listsAsDuplicateKeys ? false,
235     # Alternative to listsAsDuplicateKeys, converts list to non-list
236     # listToValue :: [Atom] -> Atom
237     listToValue ? null,
238     ...
239     }@args:
240     assert listsAsDuplicateKeys -> listToValue == null;
241     {
243     type = let
245       singleAtom = nullOr (oneOf [
246         bool
247         int
248         float
249         str
250       ]) // {
251         description = "atom (null, bool, int, float or string)";
252       };
254       atom =
255         if listsAsDuplicateKeys then
256           coercedTo singleAtom lib.singleton (listOf singleAtom) // {
257             description = singleAtom.description + " or a list of them for duplicate keys";
258           }
259         else if listToValue != null then
260           coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
261             description = singleAtom.description + " or a non-empty list of them";
262           }
263         else
264           singleAtom;
266     in attrsOf atom;
268     generate = name: value:
269       let
270         transformedValue =
271           if listToValue != null
272           then
273             lib.mapAttrs (key: val:
274               if lib.isList val then listToValue val else val
275             ) value
276           else value;
277       in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
279   };
281   toml = {}: json {} // {
282     type = let
283       valueType = oneOf [
284         bool
285         int
286         float
287         str
288         path
289         (attrsOf valueType)
290         (listOf valueType)
291       ] // {
292         description = "TOML value";
293       };
294     in valueType;
296     generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
297       nativeBuildInputs = [ remarshal ];
298       value = builtins.toJSON value;
299       passAsFile = [ "value" ];
300       preferLocalBuild = true;
301     } ''
302       json2toml "$valuePath" "$out"
303     '') {};
305   };
307   /* For configurations of Elixir project, like config.exs or runtime.exs
309     Most Elixir project are configured using the [Config] Elixir DSL
311     Since Elixir has more types than Nix, we need a way to map Nix types to
312     more than 1 Elixir type. To that end, this format provides its own library,
313     and its own set of types.
315     To be more detailed, a Nix attribute set could correspond in Elixir to a
316     [Keyword list] (the more common type), or it could correspond to a [Map].
318     A Nix string could correspond in Elixir to a [String] (also called
319     "binary"), an [Atom], or a list of chars (usually discouraged).
321     A Nix array could correspond in Elixir to a [List] or a [Tuple].
323     Some more types exists, like records, regexes, but since they are less used,
324     we can leave the `mkRaw` function as an escape hatch.
326     For more information on how to use this format in modules, please refer to
327     the Elixir section of the Nixos documentation.
329     TODO: special Elixir values doesn't show up nicely in the documentation
331     [Config]: <https://hexdocs.pm/elixir/Config.html>
332     [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html>
333     [Map]: <https://hexdocs.pm/elixir/Map.html>
334     [String]: <https://hexdocs.pm/elixir/String.html>
335     [Atom]: <https://hexdocs.pm/elixir/Atom.html>
336     [List]: <https://hexdocs.pm/elixir/List.html>
337     [Tuple]: <https://hexdocs.pm/elixir/Tuple.html>
338   */
339   elixirConf = { elixir ? pkgs.elixir }:
340     let
341       toElixir = value:
342         if value == null then "nil" else
343         if value == true then "true" else
344         if value == false then "false" else
345         if lib.isInt value || lib.isFloat value then toString value else
346         if lib.isString value then string value else
347         if lib.isAttrs value then attrs value else
348         if lib.isList value then list value else
349         abort "formats.elixirConf: should never happen (value = ${value})";
351       escapeElixir = lib.escape [ "\\" "#" "\"" ];
352       string = value: "\"${escapeElixir value}\"";
354       attrs = set:
355         if set ? _elixirType then specialType set
356         else
357           let
358             toKeyword = name: value: "${name}: ${toElixir value}";
359             keywordList = lib.concatStringsSep ", " (lib.mapAttrsToList toKeyword set);
360           in
361           "[" + keywordList + "]";
363       listContent = values: lib.concatStringsSep ", " (map toElixir values);
365       list = values: "[" + (listContent values) + "]";
367       specialType = { value, _elixirType }:
368         if _elixirType == "raw" then value else
369         if _elixirType == "atom" then value else
370         if _elixirType == "map" then elixirMap value else
371         if _elixirType == "tuple" then tuple value else
372         abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})";
374       elixirMap = set:
375         let
376           toEntry = name: value: "${toElixir name} => ${toElixir value}";
377           entries = lib.concatStringsSep ", " (lib.mapAttrsToList toEntry set);
378         in
379         "%{${entries}}";
381       tuple = values: "{${listContent values}}";
383       toConf = values:
384         let
385           keyConfig = rootKey: key: value:
386             "config ${rootKey}, ${key}, ${toElixir value}";
387           keyConfigs = rootKey: values: lib.mapAttrsToList (keyConfig rootKey) values;
388           rootConfigs = lib.flatten (lib.mapAttrsToList keyConfigs values);
389         in
390         ''
391           import Config
393           ${lib.concatStringsSep "\n" rootConfigs}
394         '';
395     in
396     {
397       type = let
398         valueType = nullOr
399           (oneOf [
400             bool
401             int
402             float
403             str
404             (attrsOf valueType)
405             (listOf valueType)
406           ]) // {
407           description = "Elixir value";
408         };
409       in
410       attrsOf (attrsOf (valueType));
412       lib =
413         let
414           mkRaw = value: {
415             inherit value;
416             _elixirType = "raw";
417           };
419         in
420         {
421           inherit mkRaw;
423           /* Fetch an environment variable at runtime, with optional fallback
424           */
425           mkGetEnv = { envVariable, fallback ? null }:
426             mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})";
428           /* Make an Elixir atom.
430             Note: lowercase atoms still need to be prefixed by ':'
431           */
432           mkAtom = value: {
433             inherit value;
434             _elixirType = "atom";
435           };
437           /* Make an Elixir tuple out of a list.
438           */
439           mkTuple = value: {
440             inherit value;
441             _elixirType = "tuple";
442           };
444           /* Make an Elixir map out of an attribute set.
445           */
446           mkMap = value: {
447             inherit value;
448             _elixirType = "map";
449           };
451           /* Contains Elixir types. Every type it exports can also be replaced
452              by raw Elixir code (i.e. every type is `either type rawElixir`).
454              It also reexports standard types, wrapping them so that they can
455              also be raw Elixir.
456           */
457           types = let
458             isElixirType = type: x: (x._elixirType or "") == type;
460             rawElixir = mkOptionType {
461               name = "rawElixir";
462               description = "raw elixir";
463               check = isElixirType "raw";
464             };
466             elixirOr = other: either other rawElixir;
467           in
468           {
469             inherit rawElixir elixirOr;
471             atom = elixirOr (mkOptionType {
472               name = "elixirAtom";
473               description = "elixir atom";
474               check = isElixirType "atom";
475             });
477             tuple = elixirOr (mkOptionType {
478               name = "elixirTuple";
479               description = "elixir tuple";
480               check = isElixirType "tuple";
481             });
483             map = elixirOr (mkOptionType {
484               name = "elixirMap";
485               description = "elixir map";
486               check = isElixirType "map";
487             });
488             # Wrap standard types, since anything in the Elixir configuration
489             # can be raw Elixir
490           } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
491         };
493       generate = name: value: pkgs.runCommand name
494         {
495           value = toConf value;
496           passAsFile = [ "value" ];
497           nativeBuildInputs = [ elixir ];
498           preferLocalBuild = true;
499         } ''
500         cp "$valuePath" "$out"
501         mix format "$out"
502       '';
503     };
505   # Outputs a succession of Python variable assignments
506   # Useful for many Django-based services
507   pythonVars = {}: {
508     type = let
509       valueType = nullOr(oneOf [
510         bool
511         float
512         int
513         path
514         str
515         (attrsOf valueType)
516         (listOf valueType)
517       ]) // {
518         description = "Python value";
519       };
520     in attrsOf valueType;
521     generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
522       nativeBuildInputs = [ python3 black ];
523       value = builtins.toJSON value;
524       pythonGen = ''
525         import json
526         import os
528         with open(os.environ["valuePath"], "r") as f:
529             for key, value in json.load(f).items():
530                 print(f"{key} = {repr(value)}")
531       '';
532       passAsFile = [ "value" "pythonGen" ];
533       preferLocalBuild = true;
534     } ''
535       cat "$valuePath"
536       python3 "$pythonGenPath" > $out
537       black $out
538     '') {};
539   };