diff options
Diffstat (limited to 'nixpkgs/lib/generators.nix')
-rw-r--r-- | nixpkgs/lib/generators.nix | 158 |
1 files changed, 134 insertions, 24 deletions
diff --git a/nixpkgs/lib/generators.nix b/nixpkgs/lib/generators.nix index 431b93c4ebbc..496845fc9ae4 100644 --- a/nixpkgs/lib/generators.nix +++ b/nixpkgs/lib/generators.nix @@ -240,10 +240,10 @@ rec { * to implicit typing rules, so it should work with older * parsers as well. */ - toYAML = {}@args: toJSON args; + toYAML = toJSON; withRecursion = - args@{ + { /* If this option is not null, the given value will stop evaluating at a certain depth */ depthLimit /* If this option is true, an error will be thrown, if a certain given depth is exceeded */ @@ -278,36 +278,46 @@ rec { mapAny 0; /* Pretty print a value, akin to `builtins.trace`. - * Should probably be a builtin as well. - */ + * Should probably be a builtin as well. + * The pretty-printed string should be suitable for rendering default values + * in the NixOS manual. In particular, it should be as close to a valid Nix expression + * as possible. + */ 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, /* If this option is true, the output is indented with newlines for attribute sets and lists */ - multiline ? true - }@args: + multiline ? true, + /* Initial indentation level */ + indent ? "" + }: let go = indent: v: with builtins; let isPath = v: typeOf v == "path"; introSpace = if multiline then "\n${indent} " else " "; outroSpace = if multiline then "\n${indent}" else " "; in if isInt v then toString v - else if isFloat 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 - # Separate a string into its lines - newlineSplits = filter (v: ! isList v) (builtins.split "\n" v); - # For a '' string terminated by a \n, which happens when the closing '' is on a new line - multilineResult = "''" + introSpace + concatStringsSep introSpace (lib.init newlineSplits) + outroSpace + "''"; - # For a '' string not terminated by a \n, which happens when the closing '' is not on a new line - multilineResult' = "''" + introSpace + concatStringsSep introSpace newlineSplits + "''"; - # For single lines, replace all newlines with their escaped representation - singlelineResult = "\"" + libStr.escape [ "\"" ] (concatStringsSep "\\n" newlineSplits) + "\""; - in if multiline && length newlineSplits > 1 then - if lib.last newlineSplits == "" then multilineResult else multilineResult' - else singlelineResult + lines = filter (v: ! isList v) (builtins.split "\n" v); + escapeSingleline = libStr.escape [ "\\" "\"" "\${" ]; + escapeMultiline = libStr.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 = lib.last escapedLines; + in "''" + introSpace + concatStringsSep introSpace (lib.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" @@ -326,22 +336,26 @@ rec { else "<function, args: {${showFnas}}>" else if isAttrs v then # apply pretty values if allowed - if attrNames v == [ "__pretty" "val" ] && allowPrettyValues + 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.drvPath or "???"}>" + "<derivation ${v.name or "???"}>" else "{" + introSpace + libStr.concatStringsSep introSpace (libAttr.mapAttrsToList (name: value: - "${libStr.escapeNixIdentifier name} = ${go (indent + " ") value};") v) + "${libStr.escapeNixIdentifier name} = ${ + builtins.addErrorContext "while evaluating an attribute `${name}`" + (go (indent + " ") value) + };") v) + outroSpace + "}" else abort "generators.toPretty: should never happen (v = ${v})"; - in go ""; + in go indent; # PLIST handling toPlist = {}: v: let isFloat = builtins.isFloat or (x: false); + isPath = x: builtins.typeOf x == "path"; expr = ind: x: with builtins; if x == null then "" else if isBool x then bool ind x else @@ -349,6 +363,7 @@ rec { 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})"; @@ -378,7 +393,7 @@ rec { 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) [ + (name: value: lib.optionals (attrFilter name value) [ (key "\t${ind}" name) (expr "\t${ind}" value) ]) x)); @@ -409,8 +424,103 @@ ${expr "" v} (if v then "True" else "False") else if isFunction v then abort "generators.toDhall: cannot convert a function to Dhall" - else if isNull v then + else if v == null then abort "generators.toDhall: cannot convert a null to Dhall" else builtins.toJSON v; + + /* + Translate a simple Nix expression to Lua representation with occasional + Lua-inlines that can be constructed by mkLuaInline function. + + Configuration: + * multiline - by default is true which results in indented block-like view. + * indent - initial indent. + * asBindings - by default generate single value, but with this use attrset to set global vars. + + Attention: + Regardless of multiline parameter there is no trailing newline. + + Example: + generators.toLua {} + { + cmd = [ "typescript-language-server" "--stdio" ]; + settings.workspace.library = mkLuaInline ''vim.api.nvim_get_runtime_file("", true)''; + } + -> + { + ["cmd"] = { + "typescript-language-server", + "--stdio" + }, + ["settings"] = { + ["workspace"] = { + ["library"] = (vim.api.nvim_get_runtime_file("", true)) + } + } + } + + Type: + toLua :: AttrSet -> Any -> String + */ + toLua = { + /* If this option is true, the output is indented with newlines for attribute sets and lists */ + multiline ? true, + /* Initial indentation level */ + indent ? "", + /* Interpret as variable bindings */ + asBindings ? false, + }@args: v: + with builtins; + let + innerIndent = "${indent} "; + introSpace = if multiline then "\n${innerIndent}" else " "; + outroSpace = if multiline then "\n${indent}" else " "; + innerArgs = args // { + indent = if asBindings then indent else innerIndent; + asBindings = false; + }; + concatItems = concatStringsSep ",${introSpace}"; + isLuaInline = { _type ? null, ... }: _type == "lua-inline"; + + generatedBindings = + assert lib.assertMsg (badVarNames == []) "Bad Lua var names: ${toPretty {} badVarNames}"; + libStr.concatStrings ( + lib.attrsets.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:]_]*)*"; + badVarNames = filter (name: matchVarName name == null) (attrNames v); + in + if asBindings then + generatedBindings + else if v == null then + "nil" + else if isInt v || isFloat v || isString v || isBool v then + builtins.toJSON v + else if isList v then + (if v == [ ] then "{}" else + "{${introSpace}${concatItems (map (value: "${toLua innerArgs value}") v)}${outroSpace}}") + else if isAttrs v then + ( + if isLuaInline v then + "(${v.expr})" + else if v == { } then + "{}" + else + "{${introSpace}${concatItems ( + lib.attrsets.mapAttrsToList (key: value: "[${builtins.toJSON key}] = ${toLua innerArgs value}") v + )}${outroSpace}}" + ) + else + abort "generators.toLua: type ${typeOf v} is unsupported"; + + /* + Mark string as Lua expression to be inlined when processed by toLua. + + Type: + mkLuaInline :: String -> AttrSet + */ + mkLuaInline = expr: { _type = "lua-inline"; inherit expr; }; } |