/* 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 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; let isPath = v: typeOf v == "path"; in 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 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 isNull x 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>''; }