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
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;
88 json2yaml "$valuePath" "$out"
92 valueType = nullOr (oneOf [
101 description = "YAML value";
107 # the ini formats share a lot of code
110 singleIniAtom = nullOr (oneOf [ bool int float str ]) // {
111 description = "INI atom (null, bool, int, float or string)";
113 iniAtom = { listsAsDuplicateKeys, listToValue, atomsCoercedToLists }:
115 singleIniAtomOr = if atomsCoercedToLists then coercedTo singleIniAtom lib.singleton else either singleIniAtom;
117 if listsAsDuplicateKeys then
118 singleIniAtomOr (listOf singleIniAtom) // {
119 description = singleIniAtom.description + " or a list of them for duplicate keys";
121 else if listToValue != null then
122 singleIniAtomOr (nonEmptyListOf singleIniAtom) // {
123 description = singleIniAtom.description + " or a non-empty list of them";
129 description = "section of an INI file (attrs of " + atom.description + ")";
132 maybeToList = listToValue: if listToValue != null then lib.mapAttrs (key: val: if lib.isList val then listToValue val else val) else lib.id;
135 # Represents lists as duplicate keys
136 listsAsDuplicateKeys ? false,
137 # Alternative to listsAsDuplicateKeys, converts list to non-list
138 # listToValue :: [IniAtom] -> IniAtom
140 # Merge multiple instances of the same key into a list
141 atomsCoercedToLists ? null,
144 assert listsAsDuplicateKeys -> listToValue == null;
145 assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
147 atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
148 atom = iniAtom { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
152 type = lib.types.attrsOf (
155 lib.types.atom = atom;
157 generate = name: value:
160 (lib.mapAttrs (_: maybeToList listToValue))
161 (lib.generators.toINI (removeAttrs args ["listToValue" "atomsCoercedToLists"]))
162 (pkgs.writeText name)
166 iniWithGlobalSection = {
167 # Represents lists as duplicate keys
168 listsAsDuplicateKeys ? false,
169 # Alternative to listsAsDuplicateKeys, converts list to non-list
170 # listToValue :: [IniAtom] -> IniAtom
172 # Merge multiple instances of the same key into a list
173 atomsCoercedToLists ? null,
176 assert listsAsDuplicateKeys -> listToValue == null;
177 assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
179 atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
180 atom = iniAtom { inherit listsAsDuplicateKeys listToValue; atomsCoercedToLists = atomsCoercedToLists'; };
183 type = lib.types.submodule {
185 sections = lib.mkOption rec {
186 type = lib.types.attrsOf (
190 description = type.description;
192 globalSection = lib.mkOption rec {
193 type = iniSection atom;
195 description = "global " + type.description;
199 lib.types.atom = atom;
200 generate = name: { sections ? {}, globalSection ? {}, ... }:
201 pkgs.writeText name (lib.generators.toINIWithGlobalSection (removeAttrs args ["listToValue" "atomsCoercedToLists"])
203 globalSection = maybeToList listToValue globalSection;
204 sections = lib.mapAttrs (_: maybeToList listToValue) sections;
208 gitIni = { listsAsDuplicateKeys ? false, ... }@args:
211 inherit listsAsDuplicateKeys;
213 atomsCoercedToLists = false;
217 type = attrsOf (attrsOf (either atom (attrsOf atom)));
218 lib.types.atom = atom;
219 generate = name: value: pkgs.writeText name (lib.generators.toGitINI value);
222 }) ini iniWithGlobalSection gitIni;
224 # As defined by systemd.syntax(7)
226 # null does not set any value, which allows for RFC42 modules to specify
227 # optional config options.
229 mkValueString = lib.generators.mkValueStringDefault {};
231 if v == null then "# ${k} is unset"
232 else "${k} = ${mkValueString v}";
234 listsAsDuplicateKeys = true;
239 # Represents lists as duplicate keys
240 listsAsDuplicateKeys ? false,
241 # Alternative to listsAsDuplicateKeys, converts list to non-list
242 # listToValue :: [Atom] -> Atom
246 assert listsAsDuplicateKeys -> listToValue == null;
251 singleAtom = nullOr (oneOf [
257 description = "atom (null, bool, int, float or string)";
261 if listsAsDuplicateKeys then
262 coercedTo singleAtom lib.singleton (listOf singleAtom) // {
263 description = singleAtom.description + " or a list of them for duplicate keys";
265 else if listToValue != null then
266 coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // {
267 description = singleAtom.description + " or a non-empty list of them";
274 generate = name: value:
277 if listToValue != null
279 lib.mapAttrs (key: val:
280 if lib.isList val then listToValue val else val
283 in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue);
287 toml = {}: json {} // {
298 description = "TOML value";
302 generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name {
303 nativeBuildInputs = [ remarshal ];
304 value = builtins.toJSON value;
305 passAsFile = [ "value" ];
306 preferLocalBuild = true;
308 json2toml "$valuePath" "$out"
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>
345 elixirConf = { elixir ? pkgs.elixir }:
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}\"";
361 if set ? _elixirType then specialType set
364 toKeyword = name: value: "${name}: ${toElixir value}";
365 keywordList = lib.concatStringsSep ", " (lib.mapAttrsToList toKeyword set);
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})";
382 toEntry = name: value: "${toElixir name} => ${toElixir value}";
383 entries = lib.concatStringsSep ", " (lib.mapAttrsToList toEntry set);
387 tuple = values: "{${listContent values}}";
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);
399 ${lib.concatStringsSep "\n" rootConfigs}
413 description = "Elixir value";
416 attrsOf (attrsOf (valueType));
429 /* Fetch an environment variable at runtime, with optional fallback
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 ':'
440 _elixirType = "atom";
443 /* Make an Elixir tuple out of a list.
447 _elixirType = "tuple";
450 /* Make an Elixir map out of an attribute set.
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
464 isElixirType = type: x: (x._elixirType or "") == type;
466 rawElixir = mkOptionType {
468 description = "raw elixir";
469 check = isElixirType "raw";
472 elixirOr = other: either other rawElixir;
475 inherit rawElixir elixirOr;
477 atom = elixirOr (mkOptionType {
479 description = "elixir atom";
480 check = isElixirType "atom";
483 tuple = elixirOr (mkOptionType {
484 name = "elixirTuple";
485 description = "elixir tuple";
486 check = isElixirType "tuple";
489 map = elixirOr (mkOptionType {
491 description = "elixir map";
492 check = isElixirType "map";
494 # Wrap standard types, since anything in the Elixir configuration
496 } // lib.mapAttrs (_name: type: elixirOr type) lib.types;
499 generate = name: value: pkgs.runCommand name
501 value = toConf value;
502 passAsFile = [ "value" ];
503 nativeBuildInputs = [ elixir ];
504 preferLocalBuild = true;
506 cp "$valuePath" "$out"
511 # Outputs a succession of Python variable assignments
512 # Useful for many Django-based services
515 valueType = nullOr(oneOf [
524 description = "Python value";
526 in attrsOf valueType;
527 generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name {
528 nativeBuildInputs = [ python3 black ];
529 value = builtins.toJSON value;
534 with open(os.environ["valuePath"], "r") as f:
535 for key, value in json.load(f).items():
536 print(f"{key} = {repr(value)}")
538 passAsFile = [ "value" "pythonGen" ];
539 preferLocalBuild = true;
542 python3 "$pythonGenPath" > $out