nixpkgs/lib/generators.nix
Profpatsch fa71407f36 lib/generators: introduce a sane default for mkValueString
So far, `mkValueString` defaulted to `toString`,
which is a bad match for most configuration file formats,
especially because how booleans are formatted.
This also improves error messages for unsupported types.

Add a test to codify the formatting.
2018-03-29 16:53:06 +02:00

174 lines
5.6 KiB
Nix

/* Functions that generate widespread file
* formats from nix data structures.
*
* They all follow a similar interface:
* generator { config-attrs } data
*
* `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.
*
* Tests can be found in ./tests.nix
* Documentation in the manual, #sec-generators
*/
{ lib }:
with (lib).trivial;
let
libStr = lib.strings;
libAttr = lib.attrsets;
flipMapAttrs = flip libAttr.mapAttrs;
inherit (lib) isFunction;
in
rec {
## -- HELPER FUNCTIONS & DEFAULTS --
/* 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
else if isFunction v then err "functions" v
else err "this value is" (toString v);
/* 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.
*
* mkValueString specifies how values should be formatted.
*
* mkKeyValueDefault {} ":" "f:oo" "bar"
* > "f\:oo:bar"
*/
mkKeyValueDefault = {
mkValueString ? mkValueStringDefault {}
}: sep: k: v:
"${libStr.escape [sep] k}${sep}${mkValueString v}";
## -- FILE FORMAT GENERATORS --
/* Generate a key-value-style config file from an attrset.
*
* mkKeyValue is the same as in toINI.
*/
toKeyValue = {
mkKeyValue ? mkKeyValueDefault {} "="
}: attrs:
let mkLine = k: v: mkKeyValue k v + "\n";
in libStr.concatStrings (libAttr.mapAttrsToList mkLine attrs);
/* Generate an INI-style config file from an
* 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
mkKeyValue ? mkKeyValueDefault {} "="
}: 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}]
'' + toKeyValue { inherit mkKeyValue; } sectValues;
in
# map input to ini sections
mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
/* 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;
/* 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.) */
allowPrettyValues ? false
}@args: v: with builtins;
if isInt v then toString v
else if isString v then ''"${libStr.escape [''"''] v}"''
else if true == v then "true"
else if false == v then "false"
else if null == v then "null"
else if isFunction v then
let fna = lib.functionArgs v;
showFnas = concatStringsSep "," (libAttr.mapAttrsToList
(name: hasDefVal: if hasDefVal then "(${name})" else name)
fna);
in if fna == {} then "<λ>"
else "<λ:{${showFnas}}>"
else if isList v then "[ "
+ libStr.concatMapStringsSep " " (toPretty args) v
+ " ]"
else if isAttrs v then
# apply pretty values if allowed
if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
then v.__pretty v.val
# TODO: there is probably a better representation?
else if v ? type && v.type == "derivation" then "<δ>"
else "{ "
+ libStr.concatStringsSep " " (libAttr.mapAttrsToList
(name: value:
"${toPretty args name} = ${toPretty args value};") v)
+ " }"
else abort "generators.toPretty: should never happen (v = ${v})";
}