diff options
Diffstat (limited to 'nixpkgs/lib/generators.nix')
-rw-r--r-- | nixpkgs/lib/generators.nix | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix new file mode 100644 index 00000000000..a64e94bd5cb --- /dev/null +++ b/nixpkgs/lib/generators.nix @@ -0,0 +1,230 @@ +/* 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; + + 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 + # functions can’t be printed of course + else if isFunction v then err "functions" v + # let’s not talk about floats. There is no sensible `toString` for them. + else if isFloat v then err "floats" 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; + let isPath = v: typeOf v == "path"; + in if isInt v then toString v + else if isFloat 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 isPath v then toString v + 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 + "<δ:${v.name}>" + # "<δ:${concatStringsSep "," (builtins.attrNames v)}>" + else "{ " + + libStr.concatStringsSep " " (libAttr.mapAttrsToList + (name: value: + "${toPretty args name} = ${toPretty args value};") v) + + " }" + 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 abort "generators.toPretty: should never happen (v = ${v})"; + + # PLIST handling + toPlist = {}: v: let + isFloat = builtins.isFloat or (x: false); + expr = ind: x: with builtins; + if x == null then "" else + if isBool x then bool ind x else + if isInt x then int ind x else + if isString x then str ind x else + if isList x then list ind x else + if isAttrs x then attrs ind x else + if isFloat x then float ind x else + abort "generators.toPlist: should never happen (v = ${v})"; + + literal = ind: x: ind + x; + + 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>"; + float = ind: x: literal ind "<real>${toString x}</real>"; + + indent = ind: expr "\t${ind}"; + + item = ind: libStr.concatMapStringsSep "\n" (indent ind); + + list = ind: x: libStr.concatStringsSep "\n" [ + (literal ind "<array>") + (item ind x) + (literal ind "</array>") + ]; + + attrs = ind: x: libStr.concatStringsSep "\n" [ + (literal ind "<dict>") + (attr ind x) + (literal ind "</dict>") + ]; + + attr = let attrFilter = name: value: name != "_module" && value != null; + in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList + (name: value: lib.optional (attrFilter name value) [ + (key "\t${ind}" name) + (expr "\t${ind}" value) + ]) x)); + + 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>''; + +} |