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
17 # Utility functions for convenience, or special interactions with the
21 # Types specific to the format (optional)
26 # generate :: Name -> Value -> Path
27 # A function for generating a file with a value of such a type
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.
39 inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; })
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;
55 valueType = nullOr (oneOf [
64 description = "JSON value";
68 generate = name: value: pkgs.callPackage ({ runCommand, jq }: runCommand name {
69 nativeBuildInputs = [ jq ];
70 value = builtins.toJSON value;
71 passAsFile = [ "value" ];
72 preferLocalBuild = true;
74 jq . "$valuePath"> $out
81 generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
82 nativeBuildInputs = [ remarshal ];
83 value = builtins.toJSON value;
84 passAsFile = [ "value" ];
85 preferLocalBuild = true;
87 json2yaml "$valuePath" "$out"
91 valueType = nullOr (oneOf [
100 description = "YAML value";
106 # the ini formats share a lot of code
109 singleIniAtom = nullOr (oneOf [ bool int float str ]) // {
110 description = "INI atom (null, bool, int, float or string)";
112 iniAtom = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }:
114 singleIniAtomOr = if atomsCoercedToLists then coercedTo singleIniAtom lib.singleton else either singleIniAtom;
116 if listsAsDuplicateKeys then
117 singleIniAtomOr (listOf singleIniAtom) // {
118 description = singleIniAtom.description + " or a list of them for duplicate keys";
120 else if listToValue != null then
121 singleIniAtomOr (nonEmptyListOf singleIniAtom) // {
122 description = singleIniAtom.description + " or a non-empty list of them";
126 iniSection = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }@args:
127 attrsOf (iniAtom args) // {
128 description = "section of an INI file (attrs of " + (iniAtom args).description + ")";
131 maybeToList = listToValue: if listToValue != null then lib.mapAttrs (key: val: if lib.isList val then listToValue val else val) else lib.id;
134 # Represents lists as duplicate keys
135 listsAsDuplicateKeys ? false,
136 # Alternative to listsAsDuplicateKeys, converts list to non-list
137 # listToValue :: [IniAtom] -> IniAtom
139 # Merge multiple instances of the same key into a list
140 atomsCoercedToLists ? null,
143 assert listsAsDuplicateKeys -> listToValue == null;
144 assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
146 atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
150 type = lib.types.attrsOf (
151 iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }
154 generate = name: value:
157 (lib.mapAttrs (_: maybeToList listToValue))
158 (lib.generators.toINI (removeAttrs args ["listToValue" "atomsCoercedToLists"]))
159 (pkgs.writeText name)
163 iniWithGlobalSection = {
164 # Represents lists as duplicate keys
165 listsAsDuplicateKeys ? false,
166 # Alternative to listsAsDuplicateKeys, converts list to non-list
167 # listToValue :: [IniAtom] -> IniAtom
169 # Merge multiple instances of the same key into a list
170 atomsCoercedToLists ? null,
173 assert listsAsDuplicateKeys -> listToValue == null;
174 assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
176 atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
179 type = lib.types.submodule {
181 sections = lib.mkOption rec {
182 type = lib.types.attrsOf (
183 iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; }
186 description = type.description;
188 globalSection = lib.mkOption rec {
189 type = iniSection { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
191 description = "global " + type.description;
195 generate = name: { sections ? {}, globalSection ? {}, ... }:
196 pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue" "atomsCoercedToLists"])
198 globalSection = maybeToList listToValue globalSection;
199 sections = lib.mapAttrs (_: maybeToList listToValue) sections;
203 gitIni = { listsAsDuplicateKeys ? false, ... }@args: {
206 listsAsDuplicateKeys = listsAsDuplicateKeys;
208 atomsCoercedToLists = false;
210 in attrsOf (attrsOf (either atom (attrsOf atom)));
212 generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
215 }) ini iniWithGlobalSection gitIni;
217 # As defined by systemd.syntax(7)
219 # null does not set any value, which allows for RFC42 modules to specify
220 # optional config options.
222 mkValueString = lib.generators.mkValueStringDefault {};
224 if v == null then "# ${k} is unset"
225 else "${k} = ${mkValueString v}";
227 listsAsDuplicateKeys = true;
232 # Represents lists as duplicate keys
233 listsAsDuplicateKeys ? false,
234 # Alternative to listsAsDuplicateKeys, converts list to non-list
235 # listToValue :: [Atom] -> Atom
239 assert listsAsDuplicateKeys -> listToValue == null;
244 singleAtom = nullOr (oneOf [
250 description = "atom (null, bool, int, float or string)";
254 if listsAsDuplicateKeys then
255 coercedTo singleAtom lib.singleton (listOf singleAtom) // {
256 description = singleAtom.description + " or a list of them for duplicate keys";
258 else if listToValue != null then
259 coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
260 description = singleAtom.description + " or a non-empty list of them";
267 generate = name: value:
270 if listToValue != null
272 lib.mapAttrs (key: val:
273 if lib.isList val then listToValue val else val
276 in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
280 toml = {}: json {} // {
291 description = "TOML value";
295 generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
296 nativeBuildInputs = [ remarshal ];
297 value = builtins.toJSON value;
298 passAsFile = [ "value" ];
299 preferLocalBuild = true;
301 json2toml "$valuePath" "$out"
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>
338 elixirConf = { elixir ? pkgs.elixir }:
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}\"";
354 if set ? _elixirType then specialType set
357 toKeyword = name: value: "${name}: ${toElixir value}";
358 keywordList = lib.concatStringsSep ", " (lib.mapAttrsToList toKeyword set);
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})";
375 toEntry = name: value: "${toElixir name} => ${toElixir value}";
376 entries = lib.concatStringsSep ", " (lib.mapAttrsToList toEntry set);
380 tuple = values: "{${listContent values}}";
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);
392 ${lib.concatStringsSep "\n" rootConfigs}
406 description = "Elixir value";
409 attrsOf (attrsOf (valueType));
422 /* Fetch an environment variable at runtime, with optional fallback
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 ':'
433 _elixirType = "atom";
436 /* Make an Elixir tuple out of a list.
440 _elixirType = "tuple";
443 /* Make an Elixir map out of an attribute set.
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
457 isElixirType = type: x: (x._elixirType or "") == type;
459 rawElixir = mkOptionType {
461 description = "raw elixir";
462 check = isElixirType "raw";
465 elixirOr = other: either other rawElixir;
468 inherit rawElixir elixirOr;
470 atom = elixirOr (mkOptionType {
472 description = "elixir atom";
473 check = isElixirType "atom";
476 tuple = elixirOr (mkOptionType {
477 name = "elixirTuple";
478 description = "elixir tuple";
479 check = isElixirType "tuple";
482 map = elixirOr (mkOptionType {
484 description = "elixir map";
485 check = isElixirType "map";
487 # Wrap standard types, since anything in the Elixir configuration
489 } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
492 generate = name: value: pkgs.runCommand name
494 value = toConf value;
495 passAsFile = [ "value" ];
496 nativeBuildInputs = [ elixir ];
497 preferLocalBuild = true;
499 cp "$valuePath" "$out"
504 # Outputs a succession of Python variable assignments
505 # Useful for many Django-based services
508 valueType = nullOr(oneOf [
517 description = "Python value";
519 in attrsOf valueType;
520 generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
521 nativeBuildInputs = [ python3 black ];
522 value = builtins.toJSON value;
527 with open(os.environ["valuePath"], "r") as f:
528 for key, value in json.load(f).items():
529 print(f"{key} = {repr(value)}")
531 passAsFile = [ "value" "pythonGen" ];
532 preferLocalBuild = true;
535 python3 "$pythonGenPath" > $out