2016-11-17 22:29:32 +01:00
|
|
|
|
/* Functions that generate widespread file
|
2016-11-06 01:51:13 +01:00
|
|
|
|
* formats from nix data structures.
|
2016-11-17 22:29:32 +01:00
|
|
|
|
*
|
|
|
|
|
* They all follow a similar interface:
|
|
|
|
|
* generator { config-attrs } data
|
|
|
|
|
*
|
2018-03-26 17:28:17 +02:00
|
|
|
|
* `config-attrs` are “holes” in the generators
|
|
|
|
|
* with sensible default implementations that
|
|
|
|
|
* can be overwritten. The default implementations
|
|
|
|
|
* are mostly generators themselves, called with
|
|
|
|
|
* their respective default values; they can be reused.
|
|
|
|
|
*
|
2016-11-06 01:51:13 +01:00
|
|
|
|
* Tests can be found in ./tests.nix
|
2016-11-17 22:29:32 +01:00
|
|
|
|
* Documentation in the manual, #sec-generators
|
2016-11-06 01:51:13 +01:00
|
|
|
|
*/
|
2017-07-29 02:05:35 +02:00
|
|
|
|
{ lib }:
|
|
|
|
|
with (lib).trivial;
|
2016-11-06 01:51:13 +01:00
|
|
|
|
let
|
2017-07-29 02:05:35 +02:00
|
|
|
|
libStr = lib.strings;
|
|
|
|
|
libAttr = lib.attrsets;
|
2016-11-06 01:51:13 +01:00
|
|
|
|
|
2018-01-31 20:02:19 +01:00
|
|
|
|
inherit (lib) isFunction;
|
2016-11-06 01:51:13 +01:00
|
|
|
|
in
|
|
|
|
|
|
2016-11-06 14:14:24 +01:00
|
|
|
|
rec {
|
2016-11-06 01:51:13 +01:00
|
|
|
|
|
2018-03-26 17:28:17 +02:00
|
|
|
|
## -- HELPER FUNCTIONS & DEFAULTS --
|
|
|
|
|
|
2018-03-26 17:31:05 +02:00
|
|
|
|
/* Convert a value to a sensible default string representation.
|
|
|
|
|
* The builtin `toString` function has some strange defaults,
|
|
|
|
|
* suitable for bash scripts but not much else.
|
|
|
|
|
*/
|
|
|
|
|
mkValueStringDefault = {}: v: with builtins;
|
|
|
|
|
let err = t: v: abort
|
|
|
|
|
("generators.mkValueStringDefault: " +
|
|
|
|
|
"${t} not supported: ${toPretty {} v}");
|
|
|
|
|
in if isInt v then toString v
|
|
|
|
|
# we default to not quoting strings
|
|
|
|
|
else if isString v then v
|
|
|
|
|
# isString returns "1", which is not a good default
|
|
|
|
|
else if true == v then "true"
|
|
|
|
|
# here it returns to "", which is even less of a good default
|
|
|
|
|
else if false == v then "false"
|
|
|
|
|
else if null == v then "null"
|
|
|
|
|
# if you have lists you probably want to replace this
|
|
|
|
|
else if isList v then err "lists" v
|
|
|
|
|
# same as for lists, might want to replace
|
|
|
|
|
else if isAttrs v then err "attrsets" v
|
2020-01-23 01:07:02 +01:00
|
|
|
|
# functions can’t be printed of course
|
2018-03-26 17:31:05 +02:00
|
|
|
|
else if isFunction v then err "functions" v
|
2019-12-13 00:24:30 +01:00
|
|
|
|
# Floats currently can't be converted to precise strings,
|
|
|
|
|
# condition warning on nix version once this isn't a problem anymore
|
|
|
|
|
# See https://github.com/NixOS/nix/pull/3480
|
|
|
|
|
else if isFloat v then libStr.floatToString v
|
2018-03-26 17:31:05 +02:00
|
|
|
|
else err "this value is" (toString v);
|
|
|
|
|
|
|
|
|
|
|
2016-12-04 22:11:24 +01:00
|
|
|
|
/* Generate a line of key k and value v, separated by
|
|
|
|
|
* character sep. If sep appears in k, it is escaped.
|
|
|
|
|
* Helper for synaxes with different separators.
|
|
|
|
|
*
|
2017-11-09 15:58:14 +01:00
|
|
|
|
* mkValueString specifies how values should be formatted.
|
|
|
|
|
*
|
|
|
|
|
* mkKeyValueDefault {} ":" "f:oo" "bar"
|
2016-12-04 22:11:24 +01:00
|
|
|
|
* > "f\:oo:bar"
|
|
|
|
|
*/
|
2017-11-09 15:58:14 +01:00
|
|
|
|
mkKeyValueDefault = {
|
2018-03-26 17:31:05 +02:00
|
|
|
|
mkValueString ? mkValueStringDefault {}
|
2017-11-09 15:58:14 +01:00
|
|
|
|
}: sep: k: v:
|
|
|
|
|
"${libStr.escape [sep] k}${sep}${mkValueString v}";
|
2016-12-04 22:11:24 +01:00
|
|
|
|
|
|
|
|
|
|
2018-03-26 17:28:17 +02:00
|
|
|
|
## -- FILE FORMAT GENERATORS --
|
|
|
|
|
|
|
|
|
|
|
2016-12-04 22:11:24 +01:00
|
|
|
|
/* Generate a key-value-style config file from an attrset.
|
|
|
|
|
*
|
|
|
|
|
* mkKeyValue is the same as in toINI.
|
|
|
|
|
*/
|
|
|
|
|
toKeyValue = {
|
2020-03-10 02:38:28 +01:00
|
|
|
|
mkKeyValue ? mkKeyValueDefault {} "=",
|
|
|
|
|
listsAsDuplicateKeys ? false
|
|
|
|
|
}:
|
|
|
|
|
let mkLine = k: v: mkKeyValue k v + "\n";
|
|
|
|
|
mkLines = if listsAsDuplicateKeys
|
|
|
|
|
then k: v: map (mkLine k) (if lib.isList v then v else [v])
|
|
|
|
|
else k: v: [ (mkLine k v) ];
|
|
|
|
|
in attrs: libStr.concatStrings (lib.concatLists (libAttr.mapAttrsToList mkLines attrs));
|
2016-12-04 22:11:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Generate an INI-style config file from an
|
2016-11-06 01:51:13 +01:00
|
|
|
|
* attrset of sections to an attrset of key-value pairs.
|
|
|
|
|
*
|
|
|
|
|
* generators.toINI {} {
|
|
|
|
|
* foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
|
|
|
|
|
* baz = { "also, integers" = 42; };
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
*> [baz]
|
|
|
|
|
*> also, integers=42
|
|
|
|
|
*>
|
|
|
|
|
*> [foo]
|
|
|
|
|
*> ciao=bar
|
|
|
|
|
*> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
|
|
|
|
|
*
|
|
|
|
|
* The mk* configuration attributes can generically change
|
|
|
|
|
* the way sections and key-value strings are generated.
|
|
|
|
|
*
|
|
|
|
|
* For more examples see the test cases in ./tests.nix.
|
|
|
|
|
*/
|
|
|
|
|
toINI = {
|
|
|
|
|
# apply transformations (e.g. escapes) to section names
|
|
|
|
|
mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
|
|
|
|
|
# format a setting line from key and value
|
2020-03-10 02:38:28 +01:00
|
|
|
|
mkKeyValue ? mkKeyValueDefault {} "=",
|
|
|
|
|
# allow lists as values for duplicate keys
|
|
|
|
|
listsAsDuplicateKeys ? false
|
2016-11-06 01:51:13 +01:00
|
|
|
|
}: attrsOfAttrs:
|
|
|
|
|
let
|
|
|
|
|
# map function to string for each key val
|
|
|
|
|
mapAttrsToStringsSep = sep: mapFn: attrs:
|
|
|
|
|
libStr.concatStringsSep sep
|
|
|
|
|
(libAttr.mapAttrsToList mapFn attrs);
|
|
|
|
|
mkSection = sectName: sectValues: ''
|
|
|
|
|
[${mkSectionName sectName}]
|
2020-03-10 02:38:28 +01:00
|
|
|
|
'' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues;
|
2016-11-06 01:51:13 +01:00
|
|
|
|
in
|
|
|
|
|
# map input to ini sections
|
|
|
|
|
mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
|
2016-11-06 14:14:24 +01:00
|
|
|
|
|
2020-03-05 18:59:32 +01:00
|
|
|
|
/* Generate a git-config file from an attrset.
|
|
|
|
|
*
|
|
|
|
|
* It has two major differences from the regular INI format:
|
|
|
|
|
*
|
|
|
|
|
* 1. values are indented with tabs
|
|
|
|
|
* 2. sections can have sub-sections
|
|
|
|
|
*
|
|
|
|
|
* generators.toGitINI {
|
|
|
|
|
* url."ssh://git@github.com/".insteadOf = "https://github.com";
|
|
|
|
|
* user.name = "edolstra";
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
*> [url "ssh://git@github.com/"]
|
|
|
|
|
*> insteadOf = https://github.com/
|
|
|
|
|
*>
|
|
|
|
|
*> [user]
|
|
|
|
|
*> name = edolstra
|
|
|
|
|
*/
|
|
|
|
|
toGitINI = attrs:
|
|
|
|
|
with builtins;
|
|
|
|
|
let
|
|
|
|
|
mkSectionName = name:
|
|
|
|
|
let
|
|
|
|
|
containsQuote = libStr.hasInfix ''"'' name;
|
|
|
|
|
sections = libStr.splitString "." name;
|
|
|
|
|
section = head sections;
|
|
|
|
|
subsections = tail sections;
|
|
|
|
|
subsection = concatStringsSep "." subsections;
|
|
|
|
|
in if containsQuote || subsections == [ ] then
|
|
|
|
|
name
|
|
|
|
|
else
|
|
|
|
|
''${section} "${subsection}"'';
|
|
|
|
|
|
|
|
|
|
# generation for multiple ini values
|
|
|
|
|
mkKeyValue = k: v:
|
|
|
|
|
let mkKeyValue = mkKeyValueDefault { } " = " k;
|
|
|
|
|
in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (lib.toList v));
|
|
|
|
|
|
|
|
|
|
# converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
|
|
|
|
|
gitFlattenAttrs = let
|
|
|
|
|
recurse = path: value:
|
|
|
|
|
if isAttrs value then
|
|
|
|
|
lib.mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
|
|
|
|
|
else if length path > 1 then {
|
|
|
|
|
${concatStringsSep "." (lib.reverseList (tail path))}.${head path} = value;
|
|
|
|
|
} else {
|
|
|
|
|
${head path} = value;
|
|
|
|
|
};
|
|
|
|
|
in attrs: lib.foldl lib.recursiveUpdate { } (lib.flatten (recurse [ ] attrs));
|
|
|
|
|
|
|
|
|
|
toINI_ = toINI { inherit mkKeyValue mkSectionName; };
|
|
|
|
|
in
|
|
|
|
|
toINI_ (gitFlattenAttrs attrs);
|
2016-11-06 14:14:24 +01:00
|
|
|
|
|
|
|
|
|
/* Generates JSON from an arbitrary (non-function) value.
|
|
|
|
|
* For more information see the documentation of the builtin.
|
|
|
|
|
*/
|
|
|
|
|
toJSON = {}: builtins.toJSON;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* YAML has been a strict superset of JSON since 1.2, so we
|
|
|
|
|
* use toJSON. Before it only had a few differences referring
|
|
|
|
|
* to implicit typing rules, so it should work with older
|
|
|
|
|
* parsers as well.
|
|
|
|
|
*/
|
|
|
|
|
toYAML = {}@args: toJSON args;
|
2017-06-06 22:41:22 +02:00
|
|
|
|
|
2018-03-26 17:26:20 +02:00
|
|
|
|
|
2017-06-06 22:41:22 +02:00
|
|
|
|
/* Pretty print a value, akin to `builtins.trace`.
|
|
|
|
|
* Should probably be a builtin as well.
|
|
|
|
|
*/
|
|
|
|
|
toPretty = {
|
|
|
|
|
/* If this option is true, attrsets like { __pretty = fn; val = …; }
|
|
|
|
|
will use fn to convert val to a pretty printed representation.
|
|
|
|
|
(This means fn is type Val -> String.) */
|
2020-09-04 17:46:12 +02:00
|
|
|
|
allowPrettyValues ? false,
|
|
|
|
|
/* If this option is true, the output is indented with newlines for attribute sets and lists */
|
2021-07-23 10:43:44 +02:00
|
|
|
|
multiline ? true,
|
|
|
|
|
/* If this option is not null, `toPretty` will stop evaluating at a certain depth */
|
|
|
|
|
depthLimit ? null,
|
|
|
|
|
/* If this option is true, an error will be thrown, if a certain given depth is exceeded */
|
|
|
|
|
throwOnDepthLimit ? false
|
|
|
|
|
}@args:
|
|
|
|
|
assert depthLimit != null -> builtins.isInt depthLimit;
|
|
|
|
|
assert throwOnDepthLimit -> depthLimit != null;
|
|
|
|
|
let
|
|
|
|
|
go = depth: indent: v: with builtins;
|
2018-04-25 15:04:30 +02:00
|
|
|
|
let isPath = v: typeOf v == "path";
|
2020-09-04 17:46:12 +02:00
|
|
|
|
introSpace = if multiline then "\n${indent} " else " ";
|
|
|
|
|
outroSpace = if multiline then "\n${indent}" else " ";
|
2021-07-23 10:43:44 +02:00
|
|
|
|
in if depthLimit != null && depth > depthLimit then
|
|
|
|
|
if throwOnDepthLimit then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to pretty-print with `generators.toPretty'!"
|
|
|
|
|
else "<unevaluated>"
|
|
|
|
|
else if isInt v then toString v
|
2018-10-13 19:55:00 +02:00
|
|
|
|
else if isFloat v then "~${toString v}"
|
2020-09-17 17:58:04 +02:00
|
|
|
|
else if isString v then
|
|
|
|
|
let
|
|
|
|
|
# Separate a string into its lines
|
|
|
|
|
newlineSplits = filter (v: ! isList v) (builtins.split "\n" v);
|
|
|
|
|
# For a '' string terminated by a \n, which happens when the closing '' is on a new line
|
|
|
|
|
multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''";
|
|
|
|
|
# For a '' string not terminated by a \n, which happens when the closing '' is not on a new line
|
|
|
|
|
multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''";
|
|
|
|
|
# For single lines, replace all newlines with their escaped representation
|
|
|
|
|
singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\"";
|
|
|
|
|
in if multiline && length newlineSplits > 1 then
|
|
|
|
|
if lib.last newlineSplits == "" then multilineResult else multilineResult'
|
|
|
|
|
else singlelineResult
|
2018-03-26 17:26:20 +02:00
|
|
|
|
else if true == v then "true"
|
|
|
|
|
else if false == v then "false"
|
2018-04-25 15:04:30 +02:00
|
|
|
|
else if null == v then "null"
|
|
|
|
|
else if isPath v then toString v
|
2020-09-17 18:14:18 +02:00
|
|
|
|
else if isList v then
|
|
|
|
|
if v == [] then "[ ]"
|
|
|
|
|
else "[" + introSpace
|
2021-07-23 10:43:44 +02:00
|
|
|
|
+ libStr.concatMapStringsSep introSpace (go (depth + 1) (indent + " ")) v
|
2020-09-17 18:14:18 +02:00
|
|
|
|
+ outroSpace + "]"
|
2020-09-17 18:18:50 +02:00
|
|
|
|
else if isFunction v then
|
2021-02-01 16:27:38 +01:00
|
|
|
|
let fna = lib.functionArgs v;
|
2020-09-17 18:18:50 +02:00
|
|
|
|
showFnas = concatStringsSep ", " (libAttr.mapAttrsToList
|
|
|
|
|
(name: hasDefVal: if hasDefVal then name + "?" else name)
|
2021-02-01 16:27:38 +01:00
|
|
|
|
fna);
|
|
|
|
|
in if fna == {} then "<function>"
|
|
|
|
|
else "<function, args: {${showFnas}}>"
|
2017-06-06 22:41:22 +02:00
|
|
|
|
else if isAttrs v then
|
|
|
|
|
# apply pretty values if allowed
|
|
|
|
|
if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
|
|
|
|
|
then v.__pretty v.val
|
2020-09-17 18:14:18 +02:00
|
|
|
|
else if v == {} then "{ }"
|
2018-04-25 15:04:30 +02:00
|
|
|
|
else if v ? type && v.type == "derivation" then
|
2021-08-11 15:50:33 +02:00
|
|
|
|
"<derivation ${v.drvPath or "???"}>"
|
2020-09-04 17:46:12 +02:00
|
|
|
|
else "{" + introSpace
|
|
|
|
|
+ libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
|
2017-06-06 22:41:22 +02:00
|
|
|
|
(name: value:
|
2021-07-23 10:43:44 +02:00
|
|
|
|
"${libStr.escapeNixIdentifier name} = ${go (depth + 1) (indent + " ") value};") v)
|
2020-09-04 17:46:12 +02:00
|
|
|
|
+ outroSpace + "}"
|
2018-06-28 17:12:39 +02:00
|
|
|
|
else abort "generators.toPretty: should never happen (v = ${v})";
|
2021-07-23 10:43:44 +02:00
|
|
|
|
in go 0 "";
|
2017-06-06 22:41:22 +02:00
|
|
|
|
|
2018-06-27 21:35:07 +02:00
|
|
|
|
# PLIST handling
|
2018-06-28 17:12:39 +02:00
|
|
|
|
toPlist = {}: v: let
|
2018-07-03 18:24:54 +02:00
|
|
|
|
isFloat = builtins.isFloat or (x: false);
|
|
|
|
|
expr = ind: x: with builtins;
|
2019-04-24 05:48:22 +02:00
|
|
|
|
if x == null then "" else
|
2018-07-03 18:24:54 +02:00
|
|
|
|
if isBool x then bool ind x else
|
|
|
|
|
if isInt x then int ind x else
|
2018-06-28 17:12:39 +02:00
|
|
|
|
if isString x then str ind x else
|
2018-07-03 18:24:54 +02:00
|
|
|
|
if isList x then list ind x else
|
|
|
|
|
if isAttrs x then attrs ind x else
|
|
|
|
|
if isFloat x then float ind x else
|
2018-06-28 17:12:39 +02:00
|
|
|
|
abort "generators.toPlist: should never happen (v = ${v})";
|
2018-06-28 17:11:19 +02:00
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
literal = ind: x: ind + x;
|
2018-06-28 17:11:19 +02:00
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
bool = ind: x: literal ind (if x then "<true/>" else "<false/>");
|
|
|
|
|
int = ind: x: literal ind "<integer>${toString x}</integer>";
|
|
|
|
|
str = ind: x: literal ind "<string>${x}</string>";
|
|
|
|
|
key = ind: x: literal ind "<key>${x}</key>";
|
2018-07-03 18:24:54 +02:00
|
|
|
|
float = ind: x: literal ind "<real>${toString x}</real>";
|
2018-06-28 17:11:19 +02:00
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
indent = ind: expr "\t${ind}";
|
2018-06-28 17:11:19 +02:00
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
item = ind: libStr.concatMapStringsSep "\n" (indent ind);
|
2018-06-28 17:11:19 +02:00
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
list = ind: x: libStr.concatStringsSep "\n" [
|
|
|
|
|
(literal ind "<array>")
|
|
|
|
|
(item ind x)
|
|
|
|
|
(literal ind "</array>")
|
2018-06-28 17:11:19 +02:00
|
|
|
|
];
|
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
attrs = ind: x: libStr.concatStringsSep "\n" [
|
|
|
|
|
(literal ind "<dict>")
|
|
|
|
|
(attr ind x)
|
|
|
|
|
(literal ind "</dict>")
|
2018-06-28 17:11:19 +02:00
|
|
|
|
];
|
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
attr = let attrFilter = name: value: name != "_module" && value != null;
|
2018-06-28 17:11:19 +02:00
|
|
|
|
in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList
|
|
|
|
|
(name: value: lib.optional (attrFilter name value) [
|
2018-06-28 17:12:39 +02:00
|
|
|
|
(key "\t${ind}" name)
|
|
|
|
|
(expr "\t${ind}" value)
|
2018-06-28 17:11:19 +02:00
|
|
|
|
]) x));
|
|
|
|
|
|
2018-06-28 17:12:39 +02:00
|
|
|
|
in ''<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
|
|
|
<plist version="1.0">
|
|
|
|
|
${expr "" v}
|
|
|
|
|
</plist>'';
|
2018-06-27 21:35:07 +02:00
|
|
|
|
|
2021-03-24 11:20:36 +01:00
|
|
|
|
/* Translate a simple Nix expression to Dhall notation.
|
|
|
|
|
* Note that integers are translated to Integer and never
|
|
|
|
|
* the Natural type.
|
|
|
|
|
*/
|
|
|
|
|
toDhall = { }@args: v:
|
|
|
|
|
with builtins;
|
|
|
|
|
let concatItems = lib.strings.concatStringsSep ", ";
|
|
|
|
|
in if isAttrs v then
|
|
|
|
|
"{ ${
|
|
|
|
|
concatItems (lib.attrsets.mapAttrsToList
|
|
|
|
|
(key: value: "${key} = ${toDhall args value}") v)
|
|
|
|
|
} }"
|
|
|
|
|
else if isList v then
|
|
|
|
|
"[ ${concatItems (map (toDhall args) v)} ]"
|
|
|
|
|
else if isInt v then
|
|
|
|
|
"${if v < 0 then "" else "+"}${toString v}"
|
|
|
|
|
else if isBool v then
|
|
|
|
|
(if v then "True" else "False")
|
|
|
|
|
else if isFunction v then
|
|
|
|
|
abort "generators.toDhall: cannot convert a function to Dhall"
|
|
|
|
|
else if isNull v then
|
|
|
|
|
abort "generators.toDhall: cannot convert a null to Dhall"
|
|
|
|
|
else
|
|
|
|
|
builtins.toJSON v;
|
2016-11-06 01:51:13 +01:00
|
|
|
|
}
|