Merge pull request #329823 from ExpidusOS/fix/pkgsllvm/elfutils
[NixPkgs.git] / pkgs / pkgs-lib / formats.nix
blob8fc808b2646f48efffb325ea2e979d285e8945bd
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   json = {}: {
50     type = with lib.types; let
51       valueType = nullOr (oneOf [
52         bool
53         int
54         float
55         str
56         path
57         (attrsOf valueType)
58         (listOf valueType)
59       ]) // {
60         description = "JSON value";
61       };
62     in valueType;
64     generate = name: value: pkgs.callPackage ({ runCommand, jq }: runCommand name {
65       nativeBuildInputs = [ jq ];
66       value = builtins.toJSON value;
67       passAsFile = [ "value" ];
68       preferLocalBuild = true;
69     } ''
70       jq . "$valuePath"> $out
71     '') {};
73   };
75   yaml = {}: {
77     generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
78       nativeBuildInputs = [ remarshal ];
79       value = builtins.toJSON value;
80       passAsFile = [ "value" ];
81       preferLocalBuild = true;
82     } ''
83       json2yaml "$valuePath" "$out"
84     '') {};
86     type = with lib.types; let
87       valueType = nullOr (oneOf [
88         bool
89         int
90         float
91         str
92         path
93         (attrsOf valueType)
94         (listOf valueType)
95       ]) // {
96         description = "YAML value";
97       };
98     in valueType;
100   };
102   # the ini formats share a lot of code
103   inherit (
104     let
105       singleIniAtom = with lib.types; nullOr (oneOf [ bool int float str ]) // {
106         description = "INI atom (null, bool, int, float or string)";
107       };
108       iniAtom = with lib.types; { listsAsDuplicateKeys, listToValue }:
109         if listsAsDuplicateKeys then
110           coercedTo singleIniAtom lib.singleton (listOf singleIniAtom) // {
111             description = singleIniAtom.description + " or a list of them for duplicate keys";
112           }
113         else if listToValue != null then
114           coercedTo singleIniAtom lib.singleton (nonEmptyListOf singleIniAtom) // {
115             description = singleIniAtom.description + " or a non-empty list of them";
116           }
117         else
118           singleIniAtom;
119       iniSection = with lib.types; { listsAsDuplicateKeys, listToValue }@args:
120         attrsOf (iniAtom args) // {
121           description = "section of an INI file (attrs of " + (iniAtom args).description + ")";
122         };
124       maybeToList = listToValue: if listToValue != null then lib.mapAttrs (key: val: if lib.isList val then listToValue val else val) else lib.id;
125     in {
126       ini = {
127         # Represents lists as duplicate keys
128         listsAsDuplicateKeys ? false,
129         # Alternative to listsAsDuplicateKeys, converts list to non-list
130         # listToValue :: [IniAtom] -> IniAtom
131         listToValue ? null,
132         ...
133         }@args:
134         assert listsAsDuplicateKeys -> listToValue == null;
135         {
137         type = lib.types.attrsOf (iniSection { listsAsDuplicateKeys = listsAsDuplicateKeys; listToValue = listToValue; });
139         generate = name: value:
140           lib.pipe value
141           [
142             (lib.mapAttrs (_: maybeToList listToValue))
143             (lib.generators.toINI (removeAttrs args ["listToValue"]))
144             (pkgs.writeText name)
145           ];
146       };
148       iniWithGlobalSection = {
149         # Represents lists as duplicate keys
150         listsAsDuplicateKeys ? false,
151         # Alternative to listsAsDuplicateKeys, converts list to non-list
152         # listToValue :: [IniAtom] -> IniAtom
153         listToValue ? null,
154         ...
155         }@args:
156         assert listsAsDuplicateKeys -> listToValue == null;
157         {
158           type = lib.types.submodule {
159             options = {
160               sections = lib.mkOption rec {
161                 type = lib.types.attrsOf (iniSection { listsAsDuplicateKeys = listsAsDuplicateKeys; listToValue = listToValue; });
162                 default = {};
163                 description = type.description;
164               };
165               globalSection = lib.mkOption rec {
166                 type = iniSection { listsAsDuplicateKeys = listsAsDuplicateKeys; listToValue = listToValue; };
167                 default = {};
168                 description = "global " + type.description;
169               };
170             };
171           };
172           generate = name: { sections ? {}, globalSection ? {}, ... }:
173             pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue"])
174             {
175               globalSection = maybeToList listToValue globalSection;
176               sections = lib.mapAttrs (_: maybeToList listToValue) sections;
177             });
178         };
180       gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
181         type = let
182           atom = iniAtom {
183             listsAsDuplicateKeys = listsAsDuplicateKeys;
184             listToValue = null;
185           };
186         in with lib.types; attrsOf (attrsOf (either atom (attrsOf atom)));
188         generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
189       };
191     }) ini iniWithGlobalSection gitIni;
193   # As defined by systemd.syntax(7)
194   #
195   # null does not set any value, which allows for RFC42 modules to specify
196   # optional config options.
197   systemd = let
198     mkValueString = lib.generators.mkValueStringDefault {};
199     mkKeyValue = k: v:
200       if v == null then "# ${k} is unset"
201       else "${k} = ${mkValueString v}";
202   in ini {
203     listsAsDuplicateKeys = true;
204     inherit mkKeyValue;
205   };
207   keyValue = {
208     # Represents lists as duplicate keys
209     listsAsDuplicateKeys ? false,
210     # Alternative to listsAsDuplicateKeys, converts list to non-list
211     # listToValue :: [Atom] -> Atom
212     listToValue ? null,
213     ...
214     }@args:
215     assert listsAsDuplicateKeys -> listToValue == null;
216     {
218     type = with lib.types; let
220       singleAtom = nullOr (oneOf [
221         bool
222         int
223         float
224         str
225       ]) // {
226         description = "atom (null, bool, int, float or string)";
227       };
229       atom =
230         if listsAsDuplicateKeys then
231           coercedTo singleAtom lib.singleton (listOf singleAtom) // {
232             description = singleAtom.description + " or a list of them for duplicate keys";
233           }
234         else if listToValue != null then
235           coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
236             description = singleAtom.description + " or a non-empty list of them";
237           }
238         else
239           singleAtom;
241     in attrsOf atom;
243     generate = name: value:
244       let
245         transformedValue =
246           if listToValue != null
247           then
248             lib.mapAttrs (key: val:
249               if lib.isList val then listToValue val else val
250             ) value
251           else value;
252       in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
254   };
256   toml = {}: json {} // {
257     type = with lib.types; let
258       valueType = oneOf [
259         bool
260         int
261         float
262         str
263         path
264         (attrsOf valueType)
265         (listOf valueType)
266       ] // {
267         description = "TOML value";
268       };
269     in valueType;
271     generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
272       nativeBuildInputs = [ remarshal ];
273       value = builtins.toJSON value;
274       passAsFile = [ "value" ];
275       preferLocalBuild = true;
276     } ''
277       json2toml "$valuePath" "$out"
278     '') {};
280   };
282   /* For configurations of Elixir project, like config.exs or runtime.exs
284     Most Elixir project are configured using the [Config] Elixir DSL
286     Since Elixir has more types than Nix, we need a way to map Nix types to
287     more than 1 Elixir type. To that end, this format provides its own library,
288     and its own set of types.
290     To be more detailed, a Nix attribute set could correspond in Elixir to a
291     [Keyword list] (the more common type), or it could correspond to a [Map].
293     A Nix string could correspond in Elixir to a [String] (also called
294     "binary"), an [Atom], or a list of chars (usually discouraged).
296     A Nix array could correspond in Elixir to a [List] or a [Tuple].
298     Some more types exists, like records, regexes, but since they are less used,
299     we can leave the `mkRaw` function as an escape hatch.
301     For more information on how to use this format in modules, please refer to
302     the Elixir section of the Nixos documentation.
304     TODO: special Elixir values doesn't show up nicely in the documentation
306     [Config]: <https://hexdocs.pm/elixir/Config.html>
307     [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html>
308     [Map]: <https://hexdocs.pm/elixir/Map.html>
309     [String]: <https://hexdocs.pm/elixir/String.html>
310     [Atom]: <https://hexdocs.pm/elixir/Atom.html>
311     [List]: <https://hexdocs.pm/elixir/List.html>
312     [Tuple]: <https://hexdocs.pm/elixir/Tuple.html>
313   */
314   elixirConf = { elixir ? pkgs.elixir }:
315     with lib; let
316       toElixir = value: with builtins;
317         if value == null then "nil" else
318         if value == true then "true" else
319         if value == false then "false" else
320         if isInt value || isFloat value then toString value else
321         if isString value then string value else
322         if isAttrs value then attrs value else
323         if isList value then list value else
324         abort "formats.elixirConf: should never happen (value = ${value})";
326       escapeElixir = escape [ "\\" "#" "\"" ];
327       string = value: "\"${escapeElixir value}\"";
329       attrs = set:
330         if set ? _elixirType then specialType set
331         else
332           let
333             toKeyword = name: value: "${name}: ${toElixir value}";
334             keywordList = concatStringsSep ", " (mapAttrsToList toKeyword set);
335           in
336           "[" + keywordList + "]";
338       listContent = values: concatStringsSep ", " (map toElixir values);
340       list = values: "[" + (listContent values) + "]";
342       specialType = { value, _elixirType }:
343         if _elixirType == "raw" then value else
344         if _elixirType == "atom" then value else
345         if _elixirType == "map" then elixirMap value else
346         if _elixirType == "tuple" then tuple value else
347         abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})";
349       elixirMap = set:
350         let
351           toEntry = name: value: "${toElixir name} => ${toElixir value}";
352           entries = concatStringsSep ", " (mapAttrsToList toEntry set);
353         in
354         "%{${entries}}";
356       tuple = values: "{${listContent values}}";
358       toConf = values:
359         let
360           keyConfig = rootKey: key: value:
361             "config ${rootKey}, ${key}, ${toElixir value}";
362           keyConfigs = rootKey: values: mapAttrsToList (keyConfig rootKey) values;
363           rootConfigs = flatten (mapAttrsToList keyConfigs values);
364         in
365         ''
366           import Config
368           ${concatStringsSep "\n" rootConfigs}
369         '';
370     in
371     {
372       type = with lib.types; let
373         valueType = nullOr
374           (oneOf [
375             bool
376             int
377             float
378             str
379             (attrsOf valueType)
380             (listOf valueType)
381           ]) // {
382           description = "Elixir value";
383         };
384       in
385       attrsOf (attrsOf (valueType));
387       lib =
388         let
389           mkRaw = value: {
390             inherit value;
391             _elixirType = "raw";
392           };
394         in
395         {
396           inherit mkRaw;
398           /* Fetch an environment variable at runtime, with optional fallback
399           */
400           mkGetEnv = { envVariable, fallback ? null }:
401             mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})";
403           /* Make an Elixir atom.
405             Note: lowercase atoms still need to be prefixed by ':'
406           */
407           mkAtom = value: {
408             inherit value;
409             _elixirType = "atom";
410           };
412           /* Make an Elixir tuple out of a list.
413           */
414           mkTuple = value: {
415             inherit value;
416             _elixirType = "tuple";
417           };
419           /* Make an Elixir map out of an attribute set.
420           */
421           mkMap = value: {
422             inherit value;
423             _elixirType = "map";
424           };
426           /* Contains Elixir types. Every type it exports can also be replaced
427              by raw Elixir code (i.e. every type is `either type rawElixir`).
429              It also reexports standard types, wrapping them so that they can
430              also be raw Elixir.
431           */
432           types = with lib.types; let
433             isElixirType = type: x: (x._elixirType or "") == type;
435             rawElixir = mkOptionType {
436               name = "rawElixir";
437               description = "raw elixir";
438               check = isElixirType "raw";
439             };
441             elixirOr = other: either other rawElixir;
442           in
443           {
444             inherit rawElixir elixirOr;
446             atom = elixirOr (mkOptionType {
447               name = "elixirAtom";
448               description = "elixir atom";
449               check = isElixirType "atom";
450             });
452             tuple = elixirOr (mkOptionType {
453               name = "elixirTuple";
454               description = "elixir tuple";
455               check = isElixirType "tuple";
456             });
458             map = elixirOr (mkOptionType {
459               name = "elixirMap";
460               description = "elixir map";
461               check = isElixirType "map";
462             });
463             # Wrap standard types, since anything in the Elixir configuration
464             # can be raw Elixir
465           } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
466         };
468       generate = name: value: pkgs.runCommand name
469         {
470           value = toConf value;
471           passAsFile = [ "value" ];
472           nativeBuildInputs = [ elixir ];
473           preferLocalBuild = true;
474         } ''
475         cp "$valuePath" "$out"
476         mix format "$out"
477       '';
478     };
480   # Outputs a succession of Python variable assignments
481   # Useful for many Django-based services
482   pythonVars = {}: {
483     type = with lib.types; let
484       valueType = nullOr(oneOf [
485         bool
486         float
487         int
488         path
489         str
490         (attrsOf valueType)
491         (listOf valueType)
492       ]) // {
493         description = "Python value";
494       };
495     in attrsOf valueType;
496     generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
497       nativeBuildInputs = [ python3 black ];
498       value = builtins.toJSON value;
499       pythonGen = ''
500         import json
501         import os
503         with open(os.environ["valuePath"], "r") as f:
504             for key, value in json.load(f).items():
505                 print(f"{key} = {repr(value)}")
506       '';
507       passAsFile = [ "value" "pythonGen" ];
508       preferLocalBuild = true;
509     } ''
510       cat "$valuePath"
511       python3 "$pythonGenPath" > $out
512       black $out
513     '') {};
514   };