1 /* Functions that generate widespread file
2 * formats from nix data structures.
4 * They all follow a similar interface:
5 * generator { config-attrs } data
7 * `config-attrs` are “holes” in the generators
8 * with sensible default implementations that
9 * can be overwritten. The default implementations
10 * are mostly generators themselves, called with
11 * their respective default values; they can be reused.
13 * Tests can be found in ./tests/misc.nix
14 * Documentation in the manual, #sec-generators
33 functionArgs # Note: not the builtin; considers `__functor` in attrsets.
43 isFunction # Note: not the builtin; considers `__functor` in attrsets.
70 ## -- HELPER FUNCTIONS & DEFAULTS --
72 /* Convert a value to a sensible default string representation.
73 * The builtin `toString` function has some strange defaults,
74 * suitable for bash scripts but not much else.
76 mkValueStringDefault = {}: v:
78 ("generators.mkValueStringDefault: " +
79 "${t} not supported: ${toPretty {} v}");
80 in if isInt v then toString v
81 # convert derivations to store paths
82 else if isDerivation v then toString v
83 # we default to not quoting strings
84 else if isString v then v
85 # isString returns "1", which is not a good default
86 else if true == v then "true"
87 # here it returns to "", which is even less of a good default
88 else if false == v then "false"
89 else if null == v then "null"
90 # if you have lists you probably want to replace this
91 else if isList v then err "lists" v
92 # same as for lists, might want to replace
93 else if isAttrs v then err "attrsets" v
94 # functions can’t be printed of course
95 else if isFunction v then err "functions" v
96 # Floats currently can't be converted to precise strings,
97 # condition warning on nix version once this isn't a problem anymore
98 # See https://github.com/NixOS/nix/pull/3480
99 else if isFloat v then floatToString v
100 else err "this value is" (toString v);
103 /* Generate a line of key k and value v, separated by
104 * character sep. If sep appears in k, it is escaped.
105 * Helper for synaxes with different separators.
107 * mkValueString specifies how values should be formatted.
109 * mkKeyValueDefault {} ":" "f:oo" "bar"
112 mkKeyValueDefault = {
113 mkValueString ? mkValueStringDefault {}
115 "${escape [sep] k}${sep}${mkValueString v}";
118 ## -- FILE FORMAT GENERATORS --
121 /* Generate a key-value-style config file from an attrset.
123 * mkKeyValue is the same as in toINI.
126 mkKeyValue ? mkKeyValueDefault {} "=",
127 listsAsDuplicateKeys ? false,
130 let mkLine = k: v: indent + mkKeyValue k v + "\n";
131 mkLines = if listsAsDuplicateKeys
132 then k: v: map (mkLine k) (if isList v then v else [v])
133 else k: v: [ (mkLine k v) ];
134 in attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs));
137 /* Generate an INI-style config file from an
138 * attrset of sections to an attrset of key-value pairs.
140 * generators.toINI {} {
141 * foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
142 * baz = { "also, integers" = 42; };
150 *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
152 * The mk* configuration attributes can generically change
153 * the way sections and key-value strings are generated.
155 * For more examples see the test cases in ./tests/misc.nix.
158 # apply transformations (e.g. escapes) to section names
159 mkSectionName ? (name: escape [ "[" "]" ] name),
160 # format a setting line from key and value
161 mkKeyValue ? mkKeyValueDefault {} "=",
162 # allow lists as values for duplicate keys
163 listsAsDuplicateKeys ? false
166 # map function to string for each key val
167 mapAttrsToStringsSep = sep: mapFn: attrs:
169 (mapAttrsToList mapFn attrs);
170 mkSection = sectName: sectValues: ''
171 [${mkSectionName sectName}]
172 '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues;
174 # map input to ini sections
175 mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
177 /* Generate an INI-style config file from an attrset
178 * specifying the global section (no header), and an
179 * attrset of sections to an attrset of key-value pairs.
181 * generators.toINIWithGlobalSection {} {
183 * someGlobalKey = "hi";
186 * foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
187 * baz = { "also, integers" = 42; };
197 *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
199 * The mk* configuration attributes can generically change
200 * the way sections and key-value strings are generated.
202 * For more examples see the test cases in ./tests/misc.nix.
204 * If you don’t need a global section, you can also use
205 * `generators.toINI` directly, which only takes
206 * the part in `sections`.
208 toINIWithGlobalSection = {
209 # apply transformations (e.g. escapes) to section names
210 mkSectionName ? (name: escape [ "[" "]" ] name),
211 # format a setting line from key and value
212 mkKeyValue ? mkKeyValueDefault {} "=",
213 # allow lists as values for duplicate keys
214 listsAsDuplicateKeys ? false
215 }: { globalSection, sections ? {} }:
216 ( if globalSection == {}
218 else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection)
220 + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections);
222 /* Generate a git-config file from an attrset.
224 * It has two major differences from the regular INI format:
226 * 1. values are indented with tabs
227 * 2. sections can have sub-sections
229 * generators.toGitINI {
230 * url."ssh://git@github.com/".insteadOf = "https://github.com";
231 * user.name = "edolstra";
234 *> [url "ssh://git@github.com/"]
235 *> insteadOf = "https://github.com"
242 mkSectionName = name:
244 containsQuote = hasInfix ''"'' name;
245 sections = splitString "." name;
246 section = head sections;
247 subsections = tail sections;
248 subsection = concatStringsSep "." subsections;
249 in if containsQuote || subsections == [ ] then
252 ''${section} "${subsection}"'';
258 replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v
260 in mkValueStringDefault { } (if isString v then escapedV else v);
262 # generation for multiple ini values
264 let mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k;
265 in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v));
267 # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
268 gitFlattenAttrs = let
269 recurse = path: value:
270 if isAttrs value && !isDerivation value then
271 mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
272 else if length path > 1 then {
273 ${concatStringsSep "." (reverseList (tail path))}.${head path} = value;
275 ${head path} = value;
277 in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs));
279 toINI_ = toINI { inherit mkKeyValue mkSectionName; };
281 toINI_ (gitFlattenAttrs attrs);
283 # mkKeyValueDefault wrapper that handles dconf INI quirks.
284 # The main differences of the format is that it requires strings to be quoted.
285 mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (gvariant.mkValue v); } "=";
287 # Generates INI in dconf keyfile style. See https://help.gnome.org/admin/system-admin-guide/stable/dconf-keyfiles.html.en
289 toDconfINI = toINI { mkKeyValue = mkDconfKeyValue; };
293 /* If this option is not null, the given value will stop evaluating at a certain depth */
295 /* If this option is true, an error will be thrown, if a certain given depth is exceeded */
296 , throwOnDepthLimit ? true
298 assert isInt depthLimit;
306 stepIntoAttr = evalNext: name:
307 if elem name specialAttrs
311 if depthLimit != null && depth > depthLimit then
313 then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!"
314 else const "<unevaluated>"
318 evalNext = x: mapAny (depth + 1) (transform (depth + 1) x);
320 if isAttrs v then mapAttrs (stepIntoAttr evalNext) v
321 else if isList v then map evalNext v
322 else transform (depth + 1) v;
326 /* Pretty print a value, akin to `builtins.trace`.
327 * Should probably be a builtin as well.
328 * The pretty-printed string should be suitable for rendering default values
329 * in the NixOS manual. In particular, it should be as close to a valid Nix expression
333 /* If this option is true, attrsets like { __pretty = fn; val = …; }
334 will use fn to convert val to a pretty printed representation.
335 (This means fn is type Val -> String.) */
336 allowPrettyValues ? false,
337 /* If this option is true, the output is indented with newlines for attribute sets and lists */
339 /* Initial indentation level */
344 let introSpace = if multiline then "\n${indent} " else " ";
345 outroSpace = if multiline then "\n${indent}" else " ";
346 in if isInt v then toString v
347 # toString loses precision on floats, so we use toJSON instead. This isn't perfect
348 # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for
349 # pretty-printing purposes this is acceptable.
350 else if isFloat v then builtins.toJSON v
351 else if isString v then
353 lines = filter (v: ! isList v) (split "\n" v);
354 escapeSingleline = escape [ "\\" "\"" "\${" ];
355 escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ];
356 singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\"";
357 multilineResult = let
358 escapedLines = map escapeMultiline lines;
359 # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer"
360 # indentation level. Otherwise, '' is appended to the last line.
361 lastLine = last escapedLines;
362 in "''" + introSpace + concatStringsSep introSpace (init escapedLines)
363 + (if lastLine == "" then outroSpace else introSpace + lastLine) + "''";
365 if multiline && length lines > 1 then multilineResult else singlelineResult
366 else if true == v then "true"
367 else if false == v then "false"
368 else if null == v then "null"
369 else if isPath v then toString v
370 else if isList v then
371 if v == [] then "[ ]"
372 else "[" + introSpace
373 + concatMapStringsSep introSpace (go (indent + " ")) v
375 else if isFunction v then
376 let fna = functionArgs v;
377 showFnas = concatStringsSep ", " (mapAttrsToList
378 (name: hasDefVal: if hasDefVal then name + "?" else name)
380 in if fna == {} then "<function>"
381 else "<function, args: {${showFnas}}>"
382 else if isAttrs v then
383 # apply pretty values if allowed
384 if allowPrettyValues && v ? __pretty && v ? val
385 then v.__pretty v.val
386 else if v == {} then "{ }"
387 else if v ? type && v.type == "derivation" then
388 "<derivation ${v.name or "???"}>"
389 else "{" + introSpace
390 + concatStringsSep introSpace (mapAttrsToList
392 "${escapeNixIdentifier name} = ${
393 addErrorContext "while evaluating an attribute `${name}`"
394 (go (indent + " ") value)
397 else abort "generators.toPretty: should never happen (v = ${v})";
403 if x == null then "" else
404 if isBool x then bool ind x else
405 if isInt x then int ind x else
406 if isString x then str ind x else
407 if isList x then list ind x else
408 if isAttrs x then attrs ind x else
409 if isPath x then str ind (toString x) else
410 if isFloat x then float ind x else
411 abort "generators.toPlist: should never happen (v = ${v})";
413 literal = ind: x: ind + x;
415 bool = ind: x: literal ind (if x then "<true/>" else "<false/>");
416 int = ind: x: literal ind "<integer>${toString x}</integer>";
417 str = ind: x: literal ind "<string>${x}</string>";
418 key = ind: x: literal ind "<key>${x}</key>";
419 float = ind: x: literal ind "<real>${toString x}</real>";
421 indent = ind: expr "\t${ind}";
423 item = ind: concatMapStringsSep "\n" (indent ind);
425 list = ind: x: concatStringsSep "\n" [
426 (literal ind "<array>")
428 (literal ind "</array>")
431 attrs = ind: x: concatStringsSep "\n" [
432 (literal ind "<dict>")
434 (literal ind "</dict>")
437 attr = let attrFilter = name: value: name != "_module" && value != null;
438 in ind: x: concatStringsSep "\n" (flatten (mapAttrsToList
439 (name: value: optionals (attrFilter name value) [
440 (key "\t${ind}" name)
441 (expr "\t${ind}" value)
444 in ''<?xml version="1.0" encoding="UTF-8"?>
445 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
446 <plist version="1.0">
450 /* Translate a simple Nix expression to Dhall notation.
451 * Note that integers are translated to Integer and never
454 toDhall = { }@args: v:
455 let concatItems = concatStringsSep ", ";
458 concatItems (mapAttrsToList
459 (key: value: "${key} = ${toDhall args value}") v)
461 else if isList v then
462 "[ ${concatItems (map (toDhall args) v)} ]"
464 "${if v < 0 then "" else "+"}${toString v}"
465 else if isBool v then
466 (if v then "True" else "False")
467 else if isFunction v then
468 abort "generators.toDhall: cannot convert a function to Dhall"
469 else if v == null then
470 abort "generators.toDhall: cannot convert a null to Dhall"
475 Translate a simple Nix expression to Lua representation with occasional
476 Lua-inlines that can be constructed by mkLuaInline function.
479 * multiline - by default is true which results in indented block-like view.
480 * indent - initial indent.
481 * asBindings - by default generate single value, but with this use attrset to set global vars.
484 Regardless of multiline parameter there is no trailing newline.
489 cmd = [ "typescript-language-server" "--stdio" ];
490 settings.workspace.library = mkLuaInline ''vim.api.nvim_get_runtime_file("", true)'';
495 "typescript-language-server",
500 ["library"] = (vim.api.nvim_get_runtime_file("", true))
506 toLua :: AttrSet -> Any -> String
509 /* If this option is true, the output is indented with newlines for attribute sets and lists */
511 /* Initial indentation level */
513 /* Interpret as variable bindings */
517 innerIndent = "${indent} ";
518 introSpace = if multiline then "\n${innerIndent}" else " ";
519 outroSpace = if multiline then "\n${indent}" else " ";
520 innerArgs = args // {
521 indent = if asBindings then indent else innerIndent;
524 concatItems = concatStringsSep ",${introSpace}";
525 isLuaInline = { _type ? null, ... }: _type == "lua-inline";
528 assert assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}";
530 mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v
533 # https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names
534 matchVarName = match "[[:alpha:]_][[:alnum:]_]*(\\.[[:alpha:]_][[:alnum:]_]*)*";
535 badVarNames = filter (name: matchVarName name == null) (attrNames v);
539 else if v == null then
541 else if isInt v || isFloat v || isString v || isBool v then
543 else if isList v then
544 (if v == [ ] then "{}" else
545 "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}")
546 else if isAttrs v then
548 if isLuaInline v then
550 else if v == { } then
552 else if isDerivation v then
555 "{${introSpace}${concatItems (
556 mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v
560 abort "generators.toLua: type ${typeOf v} is unsupported";
563 Mark string as Lua expression to be inlined when processed by toLua.
566 mkLuaInline :: String -> AttrSet
568 mkLuaInline = expr: { _type = "lua-inline"; inherit expr; };
572 # Everything in this attrset is the public interface of the file.
583 toINIWithGlobalSection
591 /* Generates JSON from an arbitrary (non-function) value.
592 * For more information see the documentation of the builtin.
596 /* YAML has been a strict superset of JSON since 1.2, so we
597 * use toJSON. Before it only had a few differences referring
598 * to implicit typing rules, so it should work with older