at 18.09-beta 7.5 kB view raw
1/* Functions that generate widespread file 2 * formats from nix data structures. 3 * 4 * They all follow a similar interface: 5 * generator { config-attrs } data 6 * 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. 12 * 13 * Tests can be found in ./tests.nix 14 * Documentation in the manual, #sec-generators 15 */ 16{ lib }: 17with (lib).trivial; 18let 19 libStr = lib.strings; 20 libAttr = lib.attrsets; 21 22 inherit (lib) isFunction; 23in 24 25rec { 26 27 ## -- HELPER FUNCTIONS & DEFAULTS -- 28 29 /* Convert a value to a sensible default string representation. 30 * The builtin `toString` function has some strange defaults, 31 * suitable for bash scripts but not much else. 32 */ 33 mkValueStringDefault = {}: v: with builtins; 34 let err = t: v: abort 35 ("generators.mkValueStringDefault: " + 36 "${t} not supported: ${toPretty {} v}"); 37 in if isInt v then toString v 38 # we default to not quoting strings 39 else if isString v then v 40 # isString returns "1", which is not a good default 41 else if true == v then "true" 42 # here it returns to "", which is even less of a good default 43 else if false == v then "false" 44 else if null == v then "null" 45 # if you have lists you probably want to replace this 46 else if isList v then err "lists" v 47 # same as for lists, might want to replace 48 else if isAttrs v then err "attrsets" v 49 else if isFunction v then err "functions" v 50 else err "this value is" (toString v); 51 52 53 /* Generate a line of key k and value v, separated by 54 * character sep. If sep appears in k, it is escaped. 55 * Helper for synaxes with different separators. 56 * 57 * mkValueString specifies how values should be formatted. 58 * 59 * mkKeyValueDefault {} ":" "f:oo" "bar" 60 * > "f\:oo:bar" 61 */ 62 mkKeyValueDefault = { 63 mkValueString ? mkValueStringDefault {} 64 }: sep: k: v: 65 "${libStr.escape [sep] k}${sep}${mkValueString v}"; 66 67 68 ## -- FILE FORMAT GENERATORS -- 69 70 71 /* Generate a key-value-style config file from an attrset. 72 * 73 * mkKeyValue is the same as in toINI. 74 */ 75 toKeyValue = { 76 mkKeyValue ? mkKeyValueDefault {} "=" 77 }: attrs: 78 let mkLine = k: v: mkKeyValue k v + "\n"; 79 in libStr.concatStrings (libAttr.mapAttrsToList mkLine attrs); 80 81 82 /* Generate an INI-style config file from an 83 * attrset of sections to an attrset of key-value pairs. 84 * 85 * generators.toINI {} { 86 * foo = { hi = "${pkgs.hello}"; ciao = "bar"; }; 87 * baz = { "also, integers" = 42; }; 88 * } 89 * 90 *> [baz] 91 *> also, integers=42 92 *> 93 *> [foo] 94 *> ciao=bar 95 *> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10 96 * 97 * The mk* configuration attributes can generically change 98 * the way sections and key-value strings are generated. 99 * 100 * For more examples see the test cases in ./tests.nix. 101 */ 102 toINI = { 103 # apply transformations (e.g. escapes) to section names 104 mkSectionName ? (name: libStr.escape [ "[" "]" ] name), 105 # format a setting line from key and value 106 mkKeyValue ? mkKeyValueDefault {} "=" 107 }: attrsOfAttrs: 108 let 109 # map function to string for each key val 110 mapAttrsToStringsSep = sep: mapFn: attrs: 111 libStr.concatStringsSep sep 112 (libAttr.mapAttrsToList mapFn attrs); 113 mkSection = sectName: sectValues: '' 114 [${mkSectionName sectName}] 115 '' + toKeyValue { inherit mkKeyValue; } sectValues; 116 in 117 # map input to ini sections 118 mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; 119 120 121 /* Generates JSON from an arbitrary (non-function) value. 122 * For more information see the documentation of the builtin. 123 */ 124 toJSON = {}: builtins.toJSON; 125 126 127 /* YAML has been a strict superset of JSON since 1.2, so we 128 * use toJSON. Before it only had a few differences referring 129 * to implicit typing rules, so it should work with older 130 * parsers as well. 131 */ 132 toYAML = {}@args: toJSON args; 133 134 135 /* Pretty print a value, akin to `builtins.trace`. 136 * Should probably be a builtin as well. 137 */ 138 toPretty = { 139 /* If this option is true, attrsets like { __pretty = fn; val = ; } 140 will use fn to convert val to a pretty printed representation. 141 (This means fn is type Val -> String.) */ 142 allowPrettyValues ? false 143 }@args: v: with builtins; 144 let isPath = v: typeOf v == "path"; 145 in if isInt v then toString v 146 else if isString v then ''"${libStr.escape [''"''] v}"'' 147 else if true == v then "true" 148 else if false == v then "false" 149 else if null == v then "null" 150 else if isPath v then toString v 151 else if isList v then "[ " 152 + libStr.concatMapStringsSep " " (toPretty args) v 153 + " ]" 154 else if isAttrs v then 155 # apply pretty values if allowed 156 if attrNames v == [ "__pretty" "val" ] && allowPrettyValues 157 then v.__pretty v.val 158 # TODO: there is probably a better representation? 159 else if v ? type && v.type == "derivation" then 160 "<δ:${v.name}>" 161 # "<δ:${concatStringsSep "," (builtins.attrNames v)}>" 162 else "{ " 163 + libStr.concatStringsSep " " (libAttr.mapAttrsToList 164 (name: value: 165 "${toPretty args name} = ${toPretty args value};") v) 166 + " }" 167 else if isFunction v then 168 let fna = lib.functionArgs v; 169 showFnas = concatStringsSep "," (libAttr.mapAttrsToList 170 (name: hasDefVal: if hasDefVal then "(${name})" else name) 171 fna); 172 in if fna == {} then "<λ>" 173 else "<λ:{${showFnas}}>" 174 else abort "generators.toPretty: should never happen (v = ${v})"; 175 176 # PLIST handling 177 toPlist = {}: v: let 178 isFloat = builtins.isFloat or (x: false); 179 expr = ind: x: with builtins; 180 if isNull x then "" else 181 if isBool x then bool ind x else 182 if isInt x then int ind x else 183 if isString x then str ind x else 184 if isList x then list ind x else 185 if isAttrs x then attrs ind x else 186 if isFloat x then float ind x else 187 abort "generators.toPlist: should never happen (v = ${v})"; 188 189 literal = ind: x: ind + x; 190 191 bool = ind: x: literal ind (if x then "<true/>" else "<false/>"); 192 int = ind: x: literal ind "<integer>${toString x}</integer>"; 193 str = ind: x: literal ind "<string>${x}</string>"; 194 key = ind: x: literal ind "<key>${x}</key>"; 195 float = ind: x: literal ind "<real>${toString x}</real>"; 196 197 indent = ind: expr "\t${ind}"; 198 199 item = ind: libStr.concatMapStringsSep "\n" (indent ind); 200 201 list = ind: x: libStr.concatStringsSep "\n" [ 202 (literal ind "<array>") 203 (item ind x) 204 (literal ind "</array>") 205 ]; 206 207 attrs = ind: x: libStr.concatStringsSep "\n" [ 208 (literal ind "<dict>") 209 (attr ind x) 210 (literal ind "</dict>") 211 ]; 212 213 attr = let attrFilter = name: value: name != "_module" && value != null; 214 in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList 215 (name: value: lib.optional (attrFilter name value) [ 216 (key "\t${ind}" name) 217 (expr "\t${ind}" value) 218 ]) x)); 219 220 in ''<?xml version="1.0" encoding="UTF-8"?> 221<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 222<plist version="1.0"> 223${expr "" v} 224</plist>''; 225 226}