145 lines
4.9 KiB
Nix
145 lines
4.9 KiB
Nix
|
lib: {
|
||
|
generators = {
|
||
|
## Limit evaluation of a valud to a certain depth.
|
||
|
##
|
||
|
## @type { limit? :: Int | Null, throw? :: Bool } -> a -> a
|
||
|
withRecursion = {
|
||
|
limit ? null,
|
||
|
throw ? true,
|
||
|
}:
|
||
|
assert builtins.isInt limit; let
|
||
|
special = [
|
||
|
"__functor"
|
||
|
"__functionArgs"
|
||
|
"__toString"
|
||
|
"__pretty__"
|
||
|
];
|
||
|
attr = next: name:
|
||
|
if builtins.elem name special
|
||
|
then lib.fp.id
|
||
|
else next;
|
||
|
transform = depth:
|
||
|
if limit != null && depth > limit
|
||
|
then
|
||
|
if throw
|
||
|
then builtins.throw "Exceeded maximum eval-depth limit of ${builtins.toString limit} while trying to evaluate with `lib.generators.withRecursion'!"
|
||
|
else lib.fp.const "<unevaluated>"
|
||
|
else lib.fp.id;
|
||
|
process = depth: value: let
|
||
|
next = x: process (depth + 1) (transform (depth + 1) x);
|
||
|
in
|
||
|
if builtins.isAttrs value
|
||
|
then builtins.mapAttrs (attr next) value
|
||
|
else if builtins.isList value
|
||
|
then builtins.map next value
|
||
|
else transform (depth + 1) value;
|
||
|
in
|
||
|
process 0;
|
||
|
|
||
|
## Create a pretty printer for Nix values.
|
||
|
##
|
||
|
## @type { indent? :: String, multiline? :: Bool, allowCustomPrettifiers? :: Bool } -> a -> string
|
||
|
pretty = {
|
||
|
indent ? "",
|
||
|
multiline ? true,
|
||
|
allowCustomPrettifiers ? false,
|
||
|
}: let
|
||
|
process = indent: value: let
|
||
|
prefix =
|
||
|
if multiline
|
||
|
then "\n${indent} "
|
||
|
else " ";
|
||
|
suffix =
|
||
|
if multiline
|
||
|
then "\n${indent}"
|
||
|
else " ";
|
||
|
|
||
|
prettyNull = "null";
|
||
|
prettyNumber = lib.numbers.into.string value;
|
||
|
prettyBool = lib.bools.into.string value;
|
||
|
prettyPath = builtins.toString value;
|
||
|
|
||
|
prettyString = let
|
||
|
lines = builtins.filter (x: !builtins.isList x) (builtins.split "\n" value);
|
||
|
escapeSingleline = lib.strings.escape.any ["\\" "\"" "\${"];
|
||
|
escapeMultiline = builtins.replaceStrings ["\${" "''"] ["''\${" "'''"];
|
||
|
singlelineResult = "\"" + lib.strings.concatMapSep "\\n" escapeSingleline lines + "\"";
|
||
|
multilineResult = let
|
||
|
escapedLines = builtins.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;
|
||
|
contents = builtins.concatStringsSep prefix (lib.lists.init escapedLines);
|
||
|
contentsSuffix =
|
||
|
if lastLine == ""
|
||
|
then suffix
|
||
|
else prefix + lastLine;
|
||
|
in
|
||
|
"''"
|
||
|
+ prefix
|
||
|
+ contents
|
||
|
+ contentsSuffix
|
||
|
+ "''";
|
||
|
in
|
||
|
if multiline && builtins.length lines > 1
|
||
|
then multilineResult
|
||
|
else singlelineResult;
|
||
|
|
||
|
prettyList = let
|
||
|
contents = lib.strings.concatMapSep prefix (process (indent + " ")) value;
|
||
|
in
|
||
|
if builtins.length value == 0
|
||
|
then "[ ]"
|
||
|
else "[${prefix}${contents}${suffix}]";
|
||
|
|
||
|
prettyFunction = let
|
||
|
args = lib.fp.args value;
|
||
|
markArgOptional = name: default:
|
||
|
if default
|
||
|
then name + "?"
|
||
|
else name;
|
||
|
argsWithDefaults = lib.attrs.mapToList markArgOptional args;
|
||
|
serializedArgs = builtins.concatStringsSep ", " argsWithDefaults;
|
||
|
in
|
||
|
if args == {}
|
||
|
then "<function>"
|
||
|
else "<function, args: {${serializedArgs}}>";
|
||
|
|
||
|
prettyAttrs = let
|
||
|
contents = builtins.concatStringsSep prefix (lib.attrs.mapToList
|
||
|
(name: value: "${lib.strings.escape.nix.identifier name} = ${
|
||
|
builtins.addErrorContext "while evaluating an attribute `${name}`"
|
||
|
(process (indent + " ") value)
|
||
|
};")
|
||
|
value);
|
||
|
in
|
||
|
if allowCustomPrettifiers && value ? __pretty__ && value ? value
|
||
|
then value.__pretty__ value.value
|
||
|
else if value == {}
|
||
|
then "{ }"
|
||
|
else if lib.packages.isDerivation value
|
||
|
then "<derivation ${value.name or "???"}>"
|
||
|
else "{${prefix}${contents}${suffix}}";
|
||
|
in
|
||
|
if null == value
|
||
|
then prettyNull
|
||
|
else if builtins.isInt value || builtins.isFloat value
|
||
|
then prettyNumber
|
||
|
else if builtins.isBool value
|
||
|
then prettyBool
|
||
|
else if builtins.isString value
|
||
|
then prettyString
|
||
|
else if builtins.isPath value
|
||
|
then prettyPath
|
||
|
else if builtins.isList value
|
||
|
then prettyList
|
||
|
else if builtins.isFunction value
|
||
|
then prettyFunction
|
||
|
else if builtins.isAttrs value
|
||
|
then prettyAttrs
|
||
|
else builtins.abort "lib.generators.pretty: should never happen (value = ${value})";
|
||
|
in
|
||
|
process indent;
|
||
|
};
|
||
|
}
|