diff options
Diffstat (limited to 'lib/generators.nix')
| -rw-r--r-- | lib/generators.nix | 604 |
1 files changed, 356 insertions, 248 deletions
diff --git a/lib/generators.nix b/lib/generators.nix index eb8d76626e71..9f9fb834ca07 100644 --- a/lib/generators.nix +++ b/lib/generators.nix @@ -35,7 +35,7 @@ let filter flatten foldl - functionArgs # Note: not the builtin; considers `__functor` in attrsets. + functionArgs # Note: not the builtin; considers `__functor` in attrsets. gvariant hasInfix head @@ -45,7 +45,7 @@ let isBool isDerivation isFloat - isFunction # Note: not the builtin; considers `__functor` in attrsets. + isFunction # Note: not the builtin; considers `__functor` in attrsets. isInt isList isPath @@ -74,7 +74,8 @@ let ; ## -- HELPER FUNCTIONS & DEFAULTS -- -in rec { +in +rec { /** Convert a value to a sensible default string representation. The builtin `toString` function has some strange defaults, @@ -88,32 +89,44 @@ in rec { `v` : 2\. Function argument */ - mkValueStringDefault = {}: v: - let err = t: v: abort - ("generators.mkValueStringDefault: " + - "${t} not supported: ${toPretty {} v}"); - in if isInt v then toString v + mkValueStringDefault = + { }: + v: + let + err = t: v: abort ("generators.mkValueStringDefault: " + "${t} not supported: ${toPretty { } v}"); + in + if isInt v then + toString v # convert derivations to store paths - else if isDerivation v then toString v + else if isDerivation v then + toString v # we default to not quoting strings - else if isString v then v + else if isString v then + v # isString returns "1", which is not a good default - else if true == v then "true" + 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" + 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 + 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 isAttrs v then + err "attrsets" v # functions can’t be printed of course - else if isFunction v then err "functions" v + else if isFunction v then + err "functions" v # 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 floatToString v - else err "this value is" (toString v); - + else if isFloat v then + floatToString v + else + err "this value is" (toString v); /** Generate a line of key k and value v, separated by @@ -145,15 +158,15 @@ in rec { : 4\. Function argument */ - mkKeyValueDefault = { - mkValueString ? mkValueStringDefault {} - }: sep: k: v: - "${escape [sep] k}${sep}${mkValueString v}"; - + mkKeyValueDefault = + { + mkValueString ? mkValueStringDefault { }, + }: + sep: k: v: + "${escape [ sep ] k}${sep}${mkValueString v}"; ## -- FILE FORMAT GENERATORS -- - /** Generate a key-value-style config file from an attrset. @@ -169,19 +182,22 @@ in rec { : indent (optional, default: `""`) : Initial indentation level - */ - toKeyValue = { - mkKeyValue ? mkKeyValueDefault {} "=", - listsAsDuplicateKeys ? false, - indent ? "" - }: - let mkLine = k: v: indent + mkKeyValue k v + "\n"; - mkLines = if listsAsDuplicateKeys - then k: v: map (mkLine k) (if isList v then v else [v]) - else k: v: [ (mkLine k v) ]; - in attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs)); - + toKeyValue = + { + mkKeyValue ? mkKeyValueDefault { } "=", + listsAsDuplicateKeys ? false, + indent ? "", + }: + let + mkLine = k: v: indent + mkKeyValue k v + "\n"; + mkLines = + if listsAsDuplicateKeys then + k: v: map (mkLine k) (if isList v then v else [ v ]) + else + k: v: [ (mkLine k v) ]; + in + attrs: concatStrings (concatLists (mapAttrsToList mkLines attrs)); /** Generate an INI-style config file from an @@ -225,22 +241,27 @@ in rec { ::: */ - toINI = { - mkSectionName ? (name: escape [ "[" "]" ] name), - mkKeyValue ? mkKeyValueDefault {} "=", - listsAsDuplicateKeys ? false - }: attrsOfAttrs: + toINI = + { + mkSectionName ? (name: escape [ "[" "]" ] name), + mkKeyValue ? mkKeyValueDefault { } "=", + listsAsDuplicateKeys ? false, + }: + attrsOfAttrs: let - # map function to string for each key val - mapAttrsToStringsSep = sep: mapFn: attrs: - concatStringsSep sep - (mapAttrsToList mapFn attrs); - mkSection = sectName: sectValues: '' + # map function to string for each key val + mapAttrsToStringsSep = + sep: mapFn: attrs: + concatStringsSep sep (mapAttrsToList mapFn attrs); + mkSection = + sectName: sectValues: + '' [${mkSectionName sectName}] - '' + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; + '' + + toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } sectValues; in - # map input to ini sections - mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; + # map input to ini sections + mapAttrsToStringsSep "\n" mkSection attrsOfAttrs; /** Generate an INI-style config file from an attrset @@ -303,15 +324,22 @@ in rec { `generators.toINI` directly, which only takes the part in `sections`. */ - toINIWithGlobalSection = { - mkSectionName ? (name: escape [ "[" "]" ] name), - mkKeyValue ? mkKeyValueDefault {} "=", - listsAsDuplicateKeys ? false - }: { globalSection, sections ? {} }: - ( if globalSection == {} - then "" - else (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) - + "\n") + toINIWithGlobalSection = + { + mkSectionName ? (name: escape [ "[" "]" ] name), + mkKeyValue ? mkKeyValueDefault { } "=", + listsAsDuplicateKeys ? false, + }: + { + globalSection, + sections ? { }, + }: + ( + if globalSection == { } then + "" + else + (toKeyValue { inherit mkKeyValue listsAsDuplicateKeys; } globalSection) + "\n" + ) + (toINI { inherit mkSectionName mkKeyValue listsAsDuplicateKeys; } sections); /** @@ -349,50 +377,57 @@ in rec { : Key-value pairs to be converted to a git-config file. See: https://git-scm.com/docs/git-config#_variables for possible values. - */ - toGitINI = attrs: + toGitINI = + attrs: let - mkSectionName = name: + mkSectionName = + name: let containsQuote = hasInfix ''"'' name; sections = splitString "." name; section = head sections; subsections = tail sections; subsection = concatStringsSep "." subsections; - in if containsQuote || subsections == [ ] then - name - else - ''${section} "${subsection}"''; + in + if containsQuote || subsections == [ ] then name else ''${section} "${subsection}"''; - mkValueString = v: + mkValueString = + v: let - escapedV = '' - "${ - replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v - }"''; - in mkValueStringDefault { } (if isString v then escapedV else v); + escapedV = ''"${replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v}"''; + in + mkValueStringDefault { } (if isString v then escapedV else v); # generation for multiple ini values - mkKeyValue = k: v: - let mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k; - in concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v)); + mkKeyValue = + k: v: + let + mkKeyValue = mkKeyValueDefault { inherit mkValueString; } " = " k; + in + concatStringsSep "\n" (map (kv: "\t" + mkKeyValue kv) (toList v)); # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI - gitFlattenAttrs = let - recurse = path: value: - if isAttrs value && !isDerivation value then - mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value - else if length path > 1 then { - ${concatStringsSep "." (reverseList (tail path))}.${head path} = value; - } else { - ${head path} = value; - }; - in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs)); + gitFlattenAttrs = + let + recurse = + path: value: + if isAttrs value && !isDerivation value then + mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value + else if length path > 1 then + { + ${concatStringsSep "." (reverseList (tail path))}.${head path} = value; + } + else + { + ${head path} = value; + }; + in + attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs)); toINI_ = toINI { inherit mkKeyValue mkSectionName; }; in - toINI_ (gitFlattenAttrs attrs); + toINI_ (gitFlattenAttrs attrs); /** mkKeyValueDefault wrapper that handles dconf INI quirks. @@ -427,35 +462,39 @@ in rec { withRecursion = { depthLimit, - throwOnDepthLimit ? true + throwOnDepthLimit ? true, }: - assert isInt depthLimit; - let - specialAttrs = [ - "__functor" - "__functionArgs" - "__toString" - "__pretty" - ]; - stepIntoAttr = evalNext: name: - if elem name specialAttrs - then id - else evalNext; - transform = depth: - if depthLimit != null && depth > depthLimit then - if throwOnDepthLimit - then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!" - else const "<unevaluated>" - else id; - mapAny = depth: v: - let - evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); - in - if isAttrs v then mapAttrs (stepIntoAttr evalNext) v - else if isList v then map evalNext v - else transform (depth + 1) v; - in - mapAny 0; + assert isInt depthLimit; + let + specialAttrs = [ + "__functor" + "__functionArgs" + "__toString" + "__pretty" + ]; + stepIntoAttr = evalNext: name: if elem name specialAttrs then id else evalNext; + transform = + depth: + if depthLimit != null && depth > depthLimit then + if throwOnDepthLimit then + throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to evaluate with `generators.withRecursion'!" + else + const "<unevaluated>" + else + id; + mapAny = + depth: v: + let + evalNext = x: mapAny (depth + 1) (transform (depth + 1) x); + in + if isAttrs v then + mapAttrs (stepIntoAttr evalNext) v + else if isList v then + map evalNext v + else + transform (depth + 1) v; + in + mapAny 0; /** Pretty print a value, akin to `builtins.trace`. @@ -481,68 +520,96 @@ in rec { Value : The value to be pretty printed */ - toPretty = { - allowPrettyValues ? false, - multiline ? true, - indent ? "" - }: + toPretty = + { + allowPrettyValues ? false, + multiline ? true, + indent ? "", + }: let - go = indent: v: - let introSpace = if multiline then "\n${indent} " else " "; - outroSpace = if multiline then "\n${indent}" else " "; - in if isInt v then toString v - # toString loses precision on floats, so we use toJSON instead. This isn't perfect - # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for - # pretty-printing purposes this is acceptable. - else if isFloat v then builtins.toJSON v - else if isString v then - let - lines = filter (v: ! isList v) (split "\n" v); - escapeSingleline = escape [ "\\" "\"" "\${" ]; - escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ]; - singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\""; - multilineResult = let - escapedLines = map escapeMultiline lines; - # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer" - # indentation level. Otherwise, '' is appended to the last line. - lastLine = last escapedLines; - in "''" + introSpace + concatStringsSep introSpace (init escapedLines) - + (if lastLine == "" then outroSpace else introSpace + lastLine) + "''"; - in - if multiline && length lines > 1 then multilineResult else singlelineResult - 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 - if v == [] then "[ ]" - else "[" + introSpace - + concatMapStringsSep introSpace (go (indent + " ")) v - + outroSpace + "]" - else if isFunction v then - let fna = functionArgs v; - showFnas = concatStringsSep ", " (mapAttrsToList - (name: hasDefVal: if hasDefVal then name + "?" else name) - fna); - in if fna == {} then "<function>" - else "<function, args: {${showFnas}}>" - else if isAttrs v then - # apply pretty values if allowed - if allowPrettyValues && v ? __pretty && v ? val - then v.__pretty v.val - else if v == {} then "{ }" - else if v ? type && v.type == "derivation" then - "<derivation ${v.name or "???"}>" - else "{" + introSpace - + concatStringsSep introSpace (mapAttrsToList - (name: value: + go = + indent: v: + let + introSpace = if multiline then "\n${indent} " else " "; + outroSpace = if multiline then "\n${indent}" else " "; + in + if isInt v then + toString v + # toString loses precision on floats, so we use toJSON instead. This isn't perfect + # as the resulting string may not parse back as a float (e.g. 42, 1e-06), but for + # pretty-printing purposes this is acceptable. + else if isFloat v then + builtins.toJSON v + else if isString v then + let + lines = filter (v: !isList v) (split "\n" v); + escapeSingleline = escape [ + "\\" + "\"" + "\${" + ]; + escapeMultiline = replaceStrings [ "\${" "''" ] [ "''\${" "'''" ]; + singlelineResult = "\"" + concatStringsSep "\\n" (map escapeSingleline lines) + "\""; + multilineResult = + let + escapedLines = map escapeMultiline lines; + # The last line gets a special treatment: if it's empty, '' is on its own line at the "outer" + # indentation level. Otherwise, '' is appended to the last line. + lastLine = last escapedLines; + in + "''" + + introSpace + + concatStringsSep introSpace (init escapedLines) + + (if lastLine == "" then outroSpace else introSpace + lastLine) + + "''"; + in + if multiline && length lines > 1 then multilineResult else singlelineResult + 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 + if v == [ ] then + "[ ]" + else + "[" + introSpace + concatMapStringsSep introSpace (go (indent + " ")) v + outroSpace + "]" + else if isFunction v then + let + fna = functionArgs v; + showFnas = concatStringsSep ", " ( + mapAttrsToList (name: hasDefVal: if hasDefVal then name + "?" else name) fna + ); + in + if fna == { } then "<function>" else "<function, args: {${showFnas}}>" + else if isAttrs v then + # apply pretty values if allowed + if allowPrettyValues && v ? __pretty && v ? val then + v.__pretty v.val + else if v == { } then + "{ }" + else if v ? type && v.type == "derivation" then + "<derivation ${v.name or "???"}>" + else + "{" + + introSpace + + concatStringsSep introSpace ( + mapAttrsToList ( + name: value: "${escapeNixIdentifier name} = ${ - addErrorContext "while evaluating an attribute `${name}`" - (go (indent + " ") value) - };") v) - + outroSpace + "}" - else abort "generators.toPretty: should never happen (v = ${v})"; - in go indent; + addErrorContext "while evaluating an attribute `${name}`" (go (indent + " ") value) + };" + ) v + ) + + outroSpace + + "}" + else + abort "generators.toPretty: should never happen (v = ${v})"; + in + go indent; /** Translate a simple Nix expression to [Plist notation](https://en.wikipedia.org/wiki/Property_list). @@ -557,61 +624,90 @@ in rec { Value : The value to be converted to Plist */ - toPlist = { - escape ? false - }: v: let - expr = ind: x: - 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 isPath x then str ind (toString x) else - if isFloat x then float ind x else - abort "generators.toPlist: should never happen (v = ${v})"; + toPlist = + { + escape ? false, + }: + v: + let + expr = + ind: x: + 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 isPath x then + str ind (toString x) + else if isFloat x then + float ind x + else + abort "generators.toPlist: should never happen (v = ${v})"; - literal = ind: x: ind + x; + literal = ind: x: ind + x; - maybeEscapeXML = if escape then escapeXML else x: x; + maybeEscapeXML = if escape then escapeXML else x: 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>${maybeEscapeXML x}</string>"; - key = ind: x: literal ind "<key>${maybeEscapeXML x}</key>"; - float = ind: x: literal ind "<real>${toString x}</real>"; + 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>${maybeEscapeXML x}</string>"; + key = ind: x: literal ind "<key>${maybeEscapeXML x}</key>"; + float = ind: x: literal ind "<real>${toString x}</real>"; - indent = ind: expr "\t${ind}"; + indent = ind: expr "\t${ind}"; - item = ind: concatMapStringsSep "\n" (indent ind); + item = ind: concatMapStringsSep "\n" (indent ind); - list = ind: x: concatStringsSep "\n" [ - (literal ind "<array>") - (item ind x) - (literal ind "</array>") - ]; + list = + ind: x: + concatStringsSep "\n" [ + (literal ind "<array>") + (item ind x) + (literal ind "</array>") + ]; - attrs = ind: x: concatStringsSep "\n" [ - (literal ind "<dict>") - (attr ind x) - (literal ind "</dict>") - ]; + attrs = + ind: x: + concatStringsSep "\n" [ + (literal ind "<dict>") + (attr ind x) + (literal ind "</dict>") + ]; - attr = let attrFilter = name: value: name != "_module" && value != null; - in ind: x: concatStringsSep "\n" (flatten (mapAttrsToList - (name: value: optionals (attrFilter name value) [ - (key "\t${ind}" name) - (expr "\t${ind}" value) - ]) x)); + attr = + let + attrFilter = name: value: name != "_module" && value != null; + in + ind: x: + concatStringsSep "\n" ( + flatten ( + mapAttrsToList ( + name: value: + optionals (attrFilter name value) [ + (key "\t${ind}" name) + (expr "\t${ind}" value) + ] + ) x + ) + ); - in - # TODO: As discussed in #356502, deprecated functionality should be removed sometime after 25.11. - lib.warnIf (!escape && lib.oldestSupportedReleaseIsAtLeast 2505) "Using `lib.generators.toPlist` without `escape = true` is deprecated" '' - <?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>''; + in + # TODO: As discussed in #356502, deprecated functionality should be removed sometime after 25.11. + lib.warnIf (!escape && lib.oldestSupportedReleaseIsAtLeast 2505) + "Using `lib.generators.toPlist` without `escape = true` is deprecated" + '' + <?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>''; /** Translate a simple Nix expression to Dhall notation. @@ -629,13 +725,14 @@ in rec { : The value to be converted to Dhall */ - toDhall = { }@args: v: - let concatItems = concatStringsSep ", "; - in if isAttrs v then - "{ ${ - concatItems (mapAttrsToList - (key: value: "${key} = ${toDhall args value}") v) - } }" + toDhall = + { }@args: + v: + let + concatItems = concatStringsSep ", "; + in + if isAttrs v then + "{ ${concatItems (mapAttrsToList (key: value: "${key} = ${toDhall args value}") v)} }" else if isList v then "[ ${concatItems (map (toDhall args) v)} ]" else if isInt v then @@ -663,7 +760,6 @@ in rec { Regardless of multiline parameter there is no trailing newline. - # Inputs Structured function argument @@ -711,11 +807,13 @@ in rec { ::: */ - toLua = { - multiline ? true, - indent ? "", - asBindings ? false, - }@args: v: + toLua = + { + multiline ? true, + indent ? "", + asBindings ? false, + }@args: + v: let innerIndent = "${indent} "; introSpace = if multiline then "\n${innerIndent}" else " "; @@ -725,13 +823,16 @@ in rec { asBindings = false; }; concatItems = concatStringsSep ",${introSpace}"; - isLuaInline = { _type ? null, ... }: _type == "lua-inline"; + isLuaInline = + { + _type ? null, + ... + }: + _type == "lua-inline"; generatedBindings = - assert assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}"; - concatStrings ( - mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v - ); + assert assertMsg (badVarNames == [ ]) "Bad Lua var names: ${toPretty { } badVarNames}"; + concatStrings (mapAttrsToList (key: value: "${indent}${key} = ${toLua innerArgs value}\n") v); # https://en.wikibooks.org/wiki/Lua_Programming/variable#Variable_names matchVarName = match "[[:alpha:]_][[:alnum:]_]*(\\.[[:alpha:]_][[:alnum:]_]*)*"; @@ -746,8 +847,12 @@ in rec { else if isPath v || isDerivation v then toJSON "${v}" else if isList v then - (if v == [ ] then "{}" else - "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}") + ( + if v == [ ] then + "{}" + else + "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}" + ) else if isAttrs v then ( if isLuaInline v then @@ -755,9 +860,9 @@ in rec { else if v == { } then "{}" else - "{${introSpace}${concatItems ( - mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v - )}${outroSpace}}" + "{${introSpace}${ + concatItems (mapAttrsToList (key: value: "[${toJSON key}] = ${toLua innerArgs value}") v) + }${outroSpace}}" ) else abort "generators.toLua: type ${typeOf v} is unsupported"; @@ -765,7 +870,6 @@ in rec { /** Mark string as Lua expression to be inlined when processed by toLua. - # Inputs `expr` @@ -778,8 +882,12 @@ in rec { mkLuaInline :: String -> AttrSet ``` */ - mkLuaInline = expr: { _type = "lua-inline"; inherit expr; }; -} // { + mkLuaInline = expr: { + _type = "lua-inline"; + inherit expr; + }; +} +// { /** Generates JSON from an arbitrary (non-function) value. For more information see the documentation of the builtin. @@ -794,7 +902,7 @@ in rec { : The value to be converted to JSON */ - toJSON = {}: lib.strings.toJSON; + toJSON = { }: lib.strings.toJSON; /** YAML has been a strict superset of JSON since 1.2, so we @@ -812,5 +920,5 @@ in rec { : The value to be converted to YAML */ - toYAML = {}: lib.strings.toJSON; + toYAML = { }: lib.strings.toJSON; } |
