pytrainer: unpin python 3.10
[NixPkgs.git] / pkgs / pkgs-lib / formats.nix
blob03663ad96b97869833cc3662e9a7c3277e164e51
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 = {}: {
81     generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
82       nativeBuildInputs = [ remarshal ];
83       value = builtins.toJSON value;
84       passAsFile = [ "value" ];
85       preferLocalBuild = true;
86     } ''
87       json2yaml "$valuePath" "$out"
88     '') {};
90     type = let
91       valueType = nullOr (oneOf [
92         bool
93         int
94         float
95         str
96         path
97         (attrsOf valueType)
98         (listOf valueType)
99       ]) // {
100         description = "YAML value";
101       };
102     in valueType;
104   };
106   # the ini formats share a lot of code
107   inherit (
108     let
109       singleIniAtom = nullOr (oneOf [ bool int float str ]) // {
110         description = "INI atom (null, bool, int, float or string)";
111       };
112       iniAtom = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }:
113         let
114           singleIniAtomOr = if atomsCoercedToLists then coercedTo singleIniAtom lib.singleton else either singleIniAtom;
115         in
116         if listsAsDuplicateKeys then
117           singleIniAtomOr (listOf singleIniAtom) // {
118             description = singleIniAtom.description + " or a list of them for duplicate keys";
119           }
120         else if listToValue != null then
121           singleIniAtomOr (nonEmptyListOf singleIniAtom) // {
122             description = singleIniAtom.description + " or a non-empty list of them";
123           }
124         else
125           singleIniAtom;
126       iniSection = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }@args:
127         attrsOf (iniAtom args) // {
128           description = "section of an INI file (attrs of " + (iniAtom args).description + ")";
129         };
131       maybeToList = listToValue: if listToValue != null then lib.mapAttrs (key: val: if lib.isList val then listToValue val else val) else lib.id;
132     in {
133       ini = {
134         # Represents lists as duplicate keys
135         listsAsDuplicateKeys ? false,
136         # Alternative to listsAsDuplicateKeys, converts list to non-list
137         # listToValue :: [IniAtom] -> IniAtom
138         listToValue ? null,
139         # Merge multiple instances of the same key into a list
140         atomsCoercedToLists ? null,
141         ...
142         }@args:
143         assert listsAsDuplicateKeys -> listToValue == null;
144         assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
145         let
146           atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
147         in
148         {
150         type = lib.types.attrsOf (
151           iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }
152         );
154         generate = name: value:
155           lib.pipe value
156           [
157             (lib.mapAttrs (_: maybeToList listToValue))
158             (lib.generators.toINI (removeAttrs args ["listToValue" "atomsCoercedToLists"]))
159             (pkgs.writeText name)
160           ];
161       };
163       iniWithGlobalSection = {
164         # Represents lists as duplicate keys
165         listsAsDuplicateKeys ? false,
166         # Alternative to listsAsDuplicateKeys, converts list to non-list
167         # listToValue :: [IniAtom] -> IniAtom
168         listToValue ? null,
169         # Merge multiple instances of the same key into a list
170         atomsCoercedToLists ? null,
171         ...
172         }@args:
173         assert listsAsDuplicateKeys -> listToValue == null;
174         assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
175         let
176           atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
177         in
178         {
179           type = lib.types.submodule {
180             options = {
181               sections = lib.mkOption rec {
182                 type = lib.types.attrsOf (
183                   iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }
184                 );
185                 default = {};
186                 description = type.description;
187               };
188               globalSection = lib.mkOption rec {
189                 type = iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
190                 default = {};
191                 description = "global " + type.description;
192               };
193             };
194           };
195           generate = name: { sections ? {}, globalSection ? {}, ... }:
196             pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue" "atomsCoercedToLists"])
197             {
198               globalSection = maybeToList listToValue globalSection;
199               sections = lib.mapAttrs (_: maybeToList listToValue) sections;
200             });
201         };
203       gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
204         type = let
205           atom = iniAtom {
206             listsAsDuplicateKeys = listsAsDuplicateKeys;
207             listToValue = null;
208             atomsCoercedToLists = false;
209           };
210         in attrsOf (attrsOf (either atom (attrsOf atom)));
212         generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
213       };
215     }) ini iniWithGlobalSection gitIni;
217   # As defined by systemd.syntax(7)
218   #
219   # null does not set any value, which allows for RFC42 modules to specify
220   # optional config options.
221   systemd = let
222     mkValueString = lib.generators.mkValueStringDefault {};
223     mkKeyValue = k: v:
224       if v == null then "# ${k} is unset"
225       else "${k} = ${mkValueString v}";
226   in ini {
227     listsAsDuplicateKeys = true;
228     inherit mkKeyValue;
229   };
231   keyValue = {
232     # Represents lists as duplicate keys
233     listsAsDuplicateKeys ? false,
234     # Alternative to listsAsDuplicateKeys, converts list to non-list
235     # listToValue :: [Atom] -> Atom
236     listToValue ? null,
237     ...
238     }@args:
239     assert listsAsDuplicateKeys -> listToValue == null;
240     {
242     type = let
244       singleAtom = nullOr (oneOf [
245         bool
246         int
247         float
248         str
249       ]) // {
250         description = "atom (null, bool, int, float or string)";
251       };
253       atom =
254         if listsAsDuplicateKeys then
255           coercedTo singleAtom lib.singleton (listOf singleAtom) // {
256             description = singleAtom.description + " or a list of them for duplicate keys";
257           }
258         else if listToValue != null then
259           coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
260             description = singleAtom.description + " or a non-empty list of them";
261           }
262         else
263           singleAtom;
265     in attrsOf atom;
267     generate = name: value:
268       let
269         transformedValue =
270           if listToValue != null
271           then
272             lib.mapAttrs (key: val:
273               if lib.isList val then listToValue val else val
274             ) value
275           else value;
276       in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
278   };
280   toml = {}: json {} // {
281     type = let
282       valueType = oneOf [
283         bool
284         int
285         float
286         str
287         path
288         (attrsOf valueType)
289         (listOf valueType)
290       ] // {
291         description = "TOML value";
292       };
293     in valueType;
295     generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
296       nativeBuildInputs = [ remarshal ];
297       value = builtins.toJSON value;
298       passAsFile = [ "value" ];
299       preferLocalBuild = true;
300     } ''
301       json2toml "$valuePath" "$out"
302     '') {};
304   };
306   /* For configurations of Elixir project, like config.exs or runtime.exs
308     Most Elixir project are configured using the [Config] Elixir DSL
310     Since Elixir has more types than Nix, we need a way to map Nix types to
311     more than 1 Elixir type. To that end, this format provides its own library,
312     and its own set of types.
314     To be more detailed, a Nix attribute set could correspond in Elixir to a
315     [Keyword list] (the more common type), or it could correspond to a [Map].
317     A Nix string could correspond in Elixir to a [String] (also called
318     "binary"), an [Atom], or a list of chars (usually discouraged).
320     A Nix array could correspond in Elixir to a [List] or a [Tuple].
322     Some more types exists, like records, regexes, but since they are less used,
323     we can leave the `mkRaw` function as an escape hatch.
325     For more information on how to use this format in modules, please refer to
326     the Elixir section of the Nixos documentation.
328     TODO: special Elixir values doesn't show up nicely in the documentation
330     [Config]: <https://hexdocs.pm/elixir/Config.html>
331     [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html>
332     [Map]: <https://hexdocs.pm/elixir/Map.html>
333     [String]: <https://hexdocs.pm/elixir/String.html>
334     [Atom]: <https://hexdocs.pm/elixir/Atom.html>
335     [List]: <https://hexdocs.pm/elixir/List.html>
336     [Tuple]: <https://hexdocs.pm/elixir/Tuple.html>
337   */
338   elixirConf = { elixir ? pkgs.elixir }:
339     let
340       toElixir = value:
341         if value == null then "nil" else
342         if value == true then "true" else
343         if value == false then "false" else
344         if lib.isInt value || lib.isFloat value then toString value else
345         if lib.isString value then string value else
346         if lib.isAttrs value then attrs value else
347         if lib.isList value then list value else
348         abort "formats.elixirConf: should never happen (value = ${value})";
350       escapeElixir = lib.escape [ "\\" "#" "\"" ];
351       string = value: "\"${escapeElixir value}\"";
353       attrs = set:
354         if set ? _elixirType then specialType set
355         else
356           let
357             toKeyword = name: value: "${name}: ${toElixir value}";
358             keywordList = lib.concatStringsSep ", " (lib.mapAttrsToList toKeyword set);
359           in
360           "[" + keywordList + "]";
362       listContent = values: lib.concatStringsSep ", " (map toElixir values);
364       list = values: "[" + (listContent values) + "]";
366       specialType = { value, _elixirType }:
367         if _elixirType == "raw" then value else
368         if _elixirType == "atom" then value else
369         if _elixirType == "map" then elixirMap value else
370         if _elixirType == "tuple" then tuple value else
371         abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})";
373       elixirMap = set:
374         let
375           toEntry = name: value: "${toElixir name} => ${toElixir value}";
376           entries = lib.concatStringsSep ", " (lib.mapAttrsToList toEntry set);
377         in
378         "%{${entries}}";
380       tuple = values: "{${listContent values}}";
382       toConf = values:
383         let
384           keyConfig = rootKey: key: value:
385             "config ${rootKey}, ${key}, ${toElixir value}";
386           keyConfigs = rootKey: values: lib.mapAttrsToList (keyConfig rootKey) values;
387           rootConfigs = lib.flatten (lib.mapAttrsToList keyConfigs values);
388         in
389         ''
390           import Config
392           ${lib.concatStringsSep "\n" rootConfigs}
393         '';
394     in
395     {
396       type = let
397         valueType = nullOr
398           (oneOf [
399             bool
400             int
401             float
402             str
403             (attrsOf valueType)
404             (listOf valueType)
405           ]) // {
406           description = "Elixir value";
407         };
408       in
409       attrsOf (attrsOf (valueType));
411       lib =
412         let
413           mkRaw = value: {
414             inherit value;
415             _elixirType = "raw";
416           };
418         in
419         {
420           inherit mkRaw;
422           /* Fetch an environment variable at runtime, with optional fallback
423           */
424           mkGetEnv = { envVariable, fallback ? null }:
425             mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})";
427           /* Make an Elixir atom.
429             Note: lowercase atoms still need to be prefixed by ':'
430           */
431           mkAtom = value: {
432             inherit value;
433             _elixirType = "atom";
434           };
436           /* Make an Elixir tuple out of a list.
437           */
438           mkTuple = value: {
439             inherit value;
440             _elixirType = "tuple";
441           };
443           /* Make an Elixir map out of an attribute set.
444           */
445           mkMap = value: {
446             inherit value;
447             _elixirType = "map";
448           };
450           /* Contains Elixir types. Every type it exports can also be replaced
451              by raw Elixir code (i.e. every type is `either type rawElixir`).
453              It also reexports standard types, wrapping them so that they can
454              also be raw Elixir.
455           */
456           types = let
457             isElixirType = type: x: (x._elixirType or "") == type;
459             rawElixir = mkOptionType {
460               name = "rawElixir";
461               description = "raw elixir";
462               check = isElixirType "raw";
463             };
465             elixirOr = other: either other rawElixir;
466           in
467           {
468             inherit rawElixir elixirOr;
470             atom = elixirOr (mkOptionType {
471               name = "elixirAtom";
472               description = "elixir atom";
473               check = isElixirType "atom";
474             });
476             tuple = elixirOr (mkOptionType {
477               name = "elixirTuple";
478               description = "elixir tuple";
479               check = isElixirType "tuple";
480             });
482             map = elixirOr (mkOptionType {
483               name = "elixirMap";
484               description = "elixir map";
485               check = isElixirType "map";
486             });
487             # Wrap standard types, since anything in the Elixir configuration
488             # can be raw Elixir
489           } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
490         };
492       generate = name: value: pkgs.runCommand name
493         {
494           value = toConf value;
495           passAsFile = [ "value" ];
496           nativeBuildInputs = [ elixir ];
497           preferLocalBuild = true;
498         } ''
499         cp "$valuePath" "$out"
500         mix format "$out"
501       '';
502     };
504   # Outputs a succession of Python variable assignments
505   # Useful for many Django-based services
506   pythonVars = {}: {
507     type = let
508       valueType = nullOr(oneOf [
509         bool
510         float
511         int
512         path
513         str
514         (attrsOf valueType)
515         (listOf valueType)
516       ]) // {
517         description = "Python value";
518       };
519     in attrsOf valueType;
520     generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
521       nativeBuildInputs = [ python3 black ];
522       value = builtins.toJSON value;
523       pythonGen = ''
524         import json
525         import os
527         with open(os.environ["valuePath"], "r") as f:
528             for key, value in json.load(f).items():
529                 print(f"{key} = {repr(value)}")
530       '';
531       passAsFile = [ "value" "pythonGen" ];
532       preferLocalBuild = true;
533     } ''
534       cat "$valuePath"
535       python3 "$pythonGenPath" > $out
536       black $out
537     '') {};
538   };