2 Functions that generate widespread file
3 formats from nix data structures.
5 They all follow a similar interface:
8 generator { config-attrs } data
11 `config-attrs` are “holes” in the generators
12 with sensible default implementations that
13 can be overwritten. The default implementations
14 are mostly generators themselves, called with
15 their respective default values; they can be reused.
17 Tests can be found in ./tests/misc.nix
19 Further Documentation can be found [here](#sec-generators).
38 functionArgs # Note: not the builtin; considers `__functor` in attrsets.
48 isFunction # Note: not the builtin; considers `__functor` in attrsets.
75 ## -- HELPER FUNCTIONS & DEFAULTS --
78 Convert a value to a sensible default string representation.
79 The builtin `toString` function has some strange defaults,
80 suitable for bash scripts but not much else.
85 : Empty set, there may be configuration options in the future
88 : 2\. Function argument
90 mkValueStringDefault = {}: v:
92 ("generators.mkValueStringDefault: " +
93 "${t} not supported: ${toPretty {} v}");
94 in if isInt v then toString v
95 # convert derivations to store paths
96 else if isDerivation v then toString v
97 # we default to not quoting strings
98 else if isString v then v
99 # isString returns "1", which is not a good default
100 else if true == v then "true"
101 # here it returns to "", which is even less of a good default
102 else if false == v then "false"
103 else if null == v then "null"
104 # if you have lists you probably want to replace this
105 else if isList v then err "lists" v
106 # same as for lists, might want to replace
107 else if isAttrs v then err "attrsets" v
108 # functions can’t be printed of course
109 else if isFunction v then err "functions" v
110 # Floats currently can't be converted to precise strings,
111 # condition warning on nix version once this isn't a problem anymore
112 # See https://github.com/NixOS/nix/pull/3480
113 else if isFloat v then floatToString v
114 else err "this value is" (toString v);
118 Generate a line of key k and value v, separated by
119 character sep. If sep appears in k, it is escaped.
120 Helper for synaxes with different separators.
122 mkValueString specifies how values should be formatted.
125 mkKeyValueDefault {} ":" "f:oo" "bar"
131 Structured function argument
132 : mkValueString (optional, default: `mkValueStringDefault {}`)
133 : Function to convert values to strings
137 : 2\. Function argument
141 : 3\. Function argument
145 : 4\. Function argument
147 mkKeyValueDefault = {
148 mkValueString ? mkValueStringDefault {}
150 "${escape [sep] k}${sep}${mkValueString v}";
153 ## -- FILE FORMAT GENERATORS --
157 Generate a key-value-style config file from an attrset.
161 Structured function argument
163 : mkKeyValue (optional, default: `mkKeyValueDefault {} "="`)
164 : format a setting line from key and value
166 : listsAsDuplicateKeys (optional, default: `false`)
167 : allow lists as values for duplicate keys
169 : indent (optional, default: `""`)
170 : Initial indentation level
174 mkKeyValue ? mkKeyValueDefault {} "=",
175 listsAsDuplicateKeys ? false,
178 let mkLine = k: v: indent + mkKeyValue k v + "\n";
179 mkLines = if listsAsDuplicateKeys
180 then k: v: map (mkLine k) (if isList v then v else [v])
181 else k: v: [ (mkLine k v) ];
182 in attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs));
186 Generate an INI-style config file from an
187 attrset of sections to an attrset of key-value pairs.
191 Structured function argument
193 : mkSectionName (optional, default: `(name: escape [ "[" "]" ] name)`)
194 : apply transformations (e.g. escapes) to section names
196 : mkKeyValue (optional, default: `{} "="`)
197 : format a setting line from key and value
199 : listsAsDuplicateKeys (optional, default: `false`)
200 : allow lists as values for duplicate keys
204 ## `lib.generators.toINI` usage example
207 generators.toINI {} {
208 foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
209 baz = { "also, integers" = 42; };
217 > hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
220 The mk* configuration attributes can generically change
221 the way sections and key-value strings are generated.
223 For more examples see the test cases in ./tests/misc.nix.
228 mkSectionName ? (name: escape [ "[" "]" ] name),
229 mkKeyValue ? mkKeyValueDefault {} "=",
230 listsAsDuplicateKeys ? false
233 # map function to string for each key val
234 mapAttrsToStringsSep = sep: mapFn: attrs:
236 (mapAttrsToList mapFn attrs);
237 mkSection = sectName: sectValues: ''
238 [${mkSectionName sectName}]
239 '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues;
241 # map input to ini sections
242 mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
245 Generate an INI-style config file from an attrset
246 specifying the global section (no header), and an
247 attrset of sections to an attrset of key-value pairs.
251 1\. Structured function argument
253 : mkSectionName (optional, default: `(name: escape [ "[" "]" ] name)`)
254 : apply transformations (e.g. escapes) to section names
256 : mkKeyValue (optional, default: `{} "="`)
257 : format a setting line from key and value
259 : listsAsDuplicateKeys (optional, default: `false`)
260 : allow lists as values for duplicate keys
262 2\. Structured function argument
264 : globalSection (required)
265 : global section key-value pairs
267 : sections (optional, default: `{}`)
268 : attrset of sections to key-value pairs
272 ## `lib.generators.toINIWithGlobalSection` usage example
275 generators.toINIWithGlobalSection {} {
277 someGlobalKey = "hi";
280 foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
281 baz = { "also, integers" = 42; };
291 > hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
294 The mk* configuration attributes can generically change
295 the way sections and key-value strings are generated.
297 For more examples see the test cases in ./tests/misc.nix.
301 If you don’t need a global section, you can also use
302 `generators.toINI` directly, which only takes
303 the part in `sections`.
305 toINIWithGlobalSection = {
306 mkSectionName ? (name: escape [ "[" "]" ] name),
307 mkKeyValue ? mkKeyValueDefault {} "=",
308 listsAsDuplicateKeys ? false
309 }: { globalSection, sections ? {} }:
310 ( if globalSection == {}
312 else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection)
314 + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections);
317 Generate a git-config file from an attrset.
319 It has two major differences from the regular INI format:
321 1. values are indented with tabs
322 2. sections can have sub-sections
324 Further: https://git-scm.com/docs/git-config#EXAMPLES
328 ## `lib.generators.toGitINI` usage example
331 generators.toGitINI {
332 url."ssh://git@github.com/".insteadOf = "https://github.com";
333 user.name = "edolstra";
336 > [url "ssh://git@github.com/"]
337 > insteadOf = "https://github.com"
349 : Key-value pairs to be converted to a git-config file.
350 See: https://git-scm.com/docs/git-config#_variables for possible values.
355 mkSectionName = name:
357 containsQuote = hasInfix ''"'' name;
358 sections = splitString "." name;
359 section = head sections;
360 subsections = tail sections;
361 subsection = concatStringsSep "." subsections;
362 in if containsQuote || subsections == [ ] then
365 ''${section} "${subsection}"'';
371 replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v
373 in mkValueStringDefault { } (if isString v then escapedV else v);
375 # generation for multiple ini values
377 let mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k;
378 in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v));
380 # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
381 gitFlattenAttrs = let
382 recurse = path: value:
383 if isAttrs value && !isDerivation value then
384 mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
385 else if length path > 1 then {
386 ${concatStringsSep "." (reverseList (tail path))}.${head path} = value;
388 ${head path} = value;
390 in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs));
392 toINI_ = toINI { inherit mkKeyValue mkSectionName; };
394 toINI_ (gitFlattenAttrs attrs);
397 mkKeyValueDefault wrapper that handles dconf INI quirks.
398 The main differences of the format is that it requires strings to be quoted.
400 mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (gvariant.mkValue v); } "=";
403 Generates INI in dconf keyfile style. See https://help.gnome.org/admin/system-admin-guide/stable/dconf-keyfiles.html.en
406 toDconfINI = toINI { mkKeyValue = mkDconfKeyValue; };
409 Recurses through a `Value` limited to a certain depth. (`depthLimit`)
411 If the depth is exceeded, an error is thrown, unless `throwOnDepthLimit` is set to `false`.
415 Structured function argument
417 : depthLimit (required)
418 : If this option is not null, the given value will stop evaluating at a certain depth
420 : throwOnDepthLimit (optional, default: `true`)
421 : If this option is true, an error will be thrown, if a certain given depth is exceeded
424 : The value to be evaluated recursively
429 throwOnDepthLimit ? true
431 assert isInt depthLimit;
439 stepIntoAttr = evalNext: name:
440 if elem name specialAttrs
444 if depthLimit != null && depth > depthLimit then
446 then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!"
447 else const "<unevaluated>"
451 evalNext = x: mapAny (depth + 1) (transform (depth + 1) x);
453 if isAttrs v then mapAttrs (stepIntoAttr evalNext) v
454 else if isList v then map evalNext v
455 else transform (depth + 1) v;
460 Pretty print a value, akin to `builtins.trace`.
462 Should probably be a builtin as well.
464 The pretty-printed string should be suitable for rendering default values
465 in the NixOS manual. In particular, it should be as close to a valid Nix expression
470 Structured function argument
472 : If this option is true, attrsets like { __pretty = fn; val = …; }
473 will use fn to convert val to a pretty printed representation.
474 (This means fn is type Val -> String.)
476 : If this option is true, the output is indented with newlines for attribute sets and lists
478 : Initial indentation level
481 : The value to be pretty printed
484 allowPrettyValues ? false,
490 let introSpace = if multiline then "\n${indent} " else " ";
491 outroSpace = if multiline then "\n${indent}" else " ";
492 in if isInt v then toString v
493 # toString loses precision on floats, so we use toJSON instead. This isn't perfect
494 # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for
495 # pretty-printing purposes this is acceptable.
496 else if isFloat v then builtins.toJSON v
497 else if isString v then
499 lines = filter (v: ! isList v) (split "\n" v);
500 escapeSingleline = escape [ "\\" "\"" "\${" ];
501 escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ];
502 singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\"";
503 multilineResult = let
504 escapedLines = map escapeMultiline lines;
505 # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer"
506 # indentation level. Otherwise, '' is appended to the last line.
507 lastLine = last escapedLines;
508 in "''" + introSpace + concatStringsSep introSpace (init escapedLines)
509 + (if lastLine == "" then outroSpace else introSpace + lastLine) + "''";
511 if multiline && length lines > 1 then multilineResult else singlelineResult
512 else if true == v then "true"
513 else if false == v then "false"
514 else if null == v then "null"
515 else if isPath v then toString v
516 else if isList v then
517 if v == [] then "[ ]"
518 else "[" + introSpace
519 + concatMapStringsSep introSpace (go (indent + " ")) v
521 else if isFunction v then
522 let fna = functionArgs v;
523 showFnas = concatStringsSep ", " (mapAttrsToList
524 (name: hasDefVal: if hasDefVal then name + "?" else name)
526 in if fna == {} then "<function>"
527 else "<function, args: {${showFnas}}>"
528 else if isAttrs v then
529 # apply pretty values if allowed
530 if allowPrettyValues && v ? __pretty && v ? val
531 then v.__pretty v.val
532 else if v == {} then "{ }"
533 else if v ? type && v.type == "derivation" then
534 "<derivation ${v.name or "???"}>"
535 else "{" + introSpace
536 + concatStringsSep introSpace (mapAttrsToList
538 "${escapeNixIdentifier name} = ${
539 addErrorContext "while evaluating an attribute `${name}`"
540 (go (indent + " ") value)
543 else abort "generators.toPretty: should never happen (v = ${v})";
547 Translate a simple Nix expression to [Plist notation](https://en.wikipedia.org/wiki/Property_list).
552 : Empty set, there may be configuration options in the future
555 : The value to be converted to Plist
559 if x == null then "" else
560 if isBool x then bool ind x else
561 if isInt x then int ind x else
562 if isString x then str ind x else
563 if isList x then list ind x else
564 if isAttrs x then attrs ind x else
565 if isPath x then str ind (toString x) else
566 if isFloat x then float ind x else
567 abort "generators.toPlist: should never happen (v = ${v})";
569 literal = ind: x: ind + x;
571 bool = ind: x: literal ind (if x then "<true/>" else "<false/>");
572 int = ind: x: literal ind "<integer>${toString x}</integer>";
573 str = ind: x: literal ind "<string>${x}</string>";
574 key = ind: x: literal ind "<key>${x}</key>";
575 float = ind: x: literal ind "<real>${toString x}</real>";
577 indent = ind: expr "\t${ind}";
579 item = ind: concatMapStringsSep "\n" (indent ind);
581 list = ind: x: concatStringsSep "\n" [
582 (literal ind "<array>")
584 (literal ind "</array>")
587 attrs = ind: x: concatStringsSep "\n" [
588 (literal ind "<dict>")
590 (literal ind "</dict>")
593 attr = let attrFilter = name: value: name != "_module" && value != null;
594 in ind: x: concatStringsSep "\n" (flatten (mapAttrsToList
595 (name: value: optionals (attrFilter name value) [
596 (key "\t${ind}" name)
597 (expr "\t${ind}" value)
600 in ''<?xml version="1.0" encoding="UTF-8"?>
601 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
602 <plist version="1.0">
607 Translate a simple Nix expression to Dhall notation.
609 Note that integers are translated to Integer and never
616 : Empty set, there may be configuration options in the future
620 : The value to be converted to Dhall
622 toDhall = { }@args: v:
623 let concatItems = concatStringsSep ", ";
626 concatItems (mapAttrsToList
627 (key: value: "${key} = ${toDhall args value}") v)
629 else if isList v then
630 "[ ${concatItems (map (toDhall args) v)} ]"
632 "${if v < 0 then "" else "+"}${toString v}"
633 else if isBool v then
634 (if v then "True" else "False")
635 else if isFunction v then
636 abort "generators.toDhall: cannot convert a function to Dhall"
637 else if v == null then
638 abort "generators.toDhall: cannot convert a null to Dhall"
643 Translate a simple Nix expression to Lua representation with occasional
644 Lua-inlines that can be constructed by mkLuaInline function.
648 * multiline - by default is true which results in indented block-like view.
649 * indent - initial indent.
650 * asBindings - by default generate single value, but with this use attrset to set global vars.
654 Regardless of multiline parameter there is no trailing newline.
659 Structured function argument
661 : multiline (optional, default: `true`)
662 : If this option is true, the output is indented with newlines for attribute sets and lists
663 : indent (optional, default: `""`)
664 : Initial indentation level
665 : asBindings (optional, default: `false`)
666 : Interpret as variable bindings
670 : The value to be converted to Lua
675 toLua :: AttrSet -> Any -> String
680 ## `lib.generators.toLua` usage example
685 cmd = [ "typescript-language-server" "--stdio" ];
686 settings.workspace.library = mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
691 "typescript-language-server",
696 ["library"] = (vim.api.nvim_get_runtime_file("", true))
710 innerIndent = "${indent} ";
711 introSpace = if multiline then "\n${innerIndent}" else " ";
712 outroSpace = if multiline then "\n${indent}" else " ";
713 innerArgs = args // {
714 indent = if asBindings then indent else innerIndent;
717 concatItems = concatStringsSep ",${introSpace}";
718 isLuaInline = { _type ? null, ... }: _type == "lua-inline";
721 assert assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}";
723 mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v
726 # https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names
727 matchVarName = match "[[:alpha:]_][[:alnum:]_]*(\\.[[:alpha:]_][[:alnum:]_]*)*";
728 badVarNames = filter (name: matchVarName name == null) (attrNames v);
732 else if v == null then
734 else if isInt v || isFloat v || isString v || isBool v then
736 else if isList v then
737 (if v == [ ] then "{}" else
738 "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}")
739 else if isAttrs v then
741 if isLuaInline v then
743 else if v == { } then
745 else if isDerivation v then
748 "{${introSpace}${concatItems (
749 mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v
753 abort "generators.toLua: type ${typeOf v} is unsupported";
756 Mark string as Lua expression to be inlined when processed by toLua.
763 : 1\. Function argument
768 mkLuaInline :: String -> AttrSet
771 mkLuaInline = expr: { _type = "lua-inline"; inherit expr; };
774 Generates JSON from an arbitrary (non-function) value.
775 For more information see the documentation of the builtin.
781 : Empty set, there may be configuration options in the future
785 : The value to be converted to JSON
787 toJSON = {}: lib.strings.toJSON;
790 YAML has been a strict superset of JSON since 1.2, so we
791 use toJSON. Before it only had a few differences referring
792 to implicit typing rules, so it should work with older
799 : Empty set, there may be configuration options in the future
803 : The value to be converted to YAML
805 toYAML = {}: lib.strings.toJSON;