diff --git a/doc/manual/rl-next/to-string-debug.md b/doc/manual/rl-next/to-string-debug.md index dd75176dde3..891528de376 100644 --- a/doc/manual/rl-next/to-string-debug.md +++ b/doc/manual/rl-next/to-string-debug.md @@ -8,3 +8,6 @@ purposes. Unlike `builtins.toString`, `builtins.toStringDebug` will never error and will always produce human-readable, pretty-printed output (including for expressions that error). This makes it ideal for interpolation into `builtins.trace` calls and `assert` messages. + +A variant, `builtins.toStringDebugOptions`, accepts as its first argument a set +of options for additional control over the output. diff --git a/src/libexpr/primops/toStringDebug.cc b/src/libexpr/primops/toStringDebug.cc index 5287bc94cb9..c81323be2cb 100644 --- a/src/libexpr/primops/toStringDebug.cc +++ b/src/libexpr/primops/toStringDebug.cc @@ -1,5 +1,6 @@ #include "primops.hh" #include "print-options.hh" +#include "value.hh" namespace nix { @@ -22,11 +23,67 @@ static RegisterPrimOp primop_toStringDebug({ calls and [`assert`](@docroot@/language/constructs.html#assertions) statements. - Output will be pretty-printed and include ANSI escape sequences. - If the value contains too many values (for instance, more than 32 - attributes or list items), some values will be elided. + Output may change in future Nix versions. Currently, output is + pretty-printed and include ANSI escape sequences. If the value contains + too many values (for instance, more than 32 attributes or list items), + some values will be elided. )", .fun = prim_toStringDebug, }); +static void prim_toStringDebugOptions(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto options = PrintOptions::fromValue(state, *args[0]); + v.mkString(printValue(state, *args[1], options)); +} + +static RegisterPrimOp primop_toStringDebugOptions({ + .name = "toStringDebugOptions", + .args = {"options", "value"}, + .doc = R"( + Format a value as a string for debugging purposes. + + Like + [`toStringDebug`](@docroot@/language/builtins.md#builtins-toStringDebug) + but accepts an additional attribute set of arguments as its first value: + + - `ansiColors` (boolean, default `true`): Whether or not to include ANSI + escapes for coloring in the output. + - `force` (boolean, default `true`): Whether or not to force values while + printing output. + - `derivationPaths` (boolean, default `true`): If `force` is set, print + derivations as `.drv` paths instead of as attribute sets. + - `trackRepeated` (boolean, default `true`): Whether or not to track + repeated values while printing output. This will help avoid excessive + output while printing self-referential structures. The specific cycle + detection algorithm may not detect all repeated values and may change + between releases. + - `maxDepth` (integer, default 15): The maximum depth to print values to. + Depth is increased when printing nested lists and attribute sets. If + `maxDepth` is -1, values will be printed to unlimited depth (or until + Nix crashes). + - `maxAttrs` (integer, default 32): The maximum number of attributes to + print in attribute sets. Further attributes will be replaced with a + `«234 attributes elided»` message. Note that this is the maximum number + of attributes to print for the entire `toStringDebugOptions` call (if + it were per-attribute set, it would be possible for + `toStringDebugOptions` to produce essentially unbounded output). If + `maxAttrs` is -1, all attributes will be printed. + - `maxListItems` (integer, default 32): The maximum number of list items to + print. Further items will be replaced with a `«234 items elided»` + message. If `maxListItems` is -1, all items will be printed. + - `maxStringLength` (integer, default 1024): The maximum number of bytes + to print of strings. Further data will be replaced with a `«234 bytes + elided»` message. If `maxStringLength` is -1, full strings will be + printed. + - `prettyIndent` (integer, default 2): The number of spaces of indent to + use when pretty-printing values. If `prettyIndent` is 0, values will be + printed on a single line. + + Missing attributes will be substituted with a default value. Default + values may change between releases. + )", + .fun = prim_toStringDebugOptions, +}); + } diff --git a/src/libexpr/print-options.cc b/src/libexpr/print-options.cc new file mode 100644 index 00000000000..09a8cbd6fa5 --- /dev/null +++ b/src/libexpr/print-options.cc @@ -0,0 +1,85 @@ +#include + +#include "print-options.hh" +#include "value.hh" +#include "eval.hh" + +namespace nix { + +namespace { +static std::string_view ERROR_CONTEXT = "while constructing printing options"; +} + +size_t nixIntToSizeT(EvalState & state, Value & v, NixInt i, bool minusOneIsMax) +{ + if (minusOneIsMax && i == -1) { + return std::numeric_limits::max(); + } + + try { + return boost::numeric_cast(i); + } catch (boost::numeric::bad_numeric_cast & e) { + state.error( + "Failed to convert integer to `size_t`: %1%", e.what() + ) + .atPos(v) + .debugThrow(); + } +} + +bool boolAttr(EvalState & state, Value & v, std::string_view attrName, bool defaultValue) +{ + auto attr = v.attrs->find(state.symbols.create(attrName)); + if (attr != v.attrs->end()) { + return state.forceBool(*attr->value, attr->pos, ERROR_CONTEXT); + } else { + return defaultValue; + } +} + +size_t intAttr(EvalState & state, Value & v, std::string_view attrName, size_t defaultValue, bool minusOneIsMax) +{ + auto attr = v.attrs->find(state.symbols.create(attrName)); + if (attr != v.attrs->end()) { + return nixIntToSizeT( + state, + v, + state.forceInt(*attr->value, attr->pos, ERROR_CONTEXT), + minusOneIsMax + ); + } else { + return defaultValue; + } +} + +PrintOptions PrintOptions::fromValue(EvalState & state, Value & v) +{ + state.forceAttrs( + v, [v]() { return v.determinePos(noPos); }, ERROR_CONTEXT); + + auto ansiColors = boolAttr(state, v, "ansiColors", true); + auto force = boolAttr(state, v, "force", true); + auto derivationPaths = boolAttr(state, v, "derivationPaths", true); + auto trackRepeated = boolAttr(state, v, "trackRepeated", true); + + auto maxDepth = intAttr(state, v, "trackRepeated", 15, true); + auto maxAttrs = intAttr(state, v, "maxAttrs", 32, true); + auto maxListItems = intAttr(state, v, "maxListItems", 32, true); + auto maxStringLength = intAttr(state, v, "maxStringLength", 1024, true); + + auto prettyIndent = intAttr(state, v, "prettyIndent", 2, false); + + return PrintOptions { + .ansiColors = ansiColors, + .force = force, + .derivationPaths = derivationPaths, + .trackRepeated = trackRepeated, + .maxDepth = maxDepth, + .maxAttrs = maxAttrs, + .maxListItems = maxListItems, + .maxStringLength = maxStringLength, + .prettyIndent = prettyIndent, + }; +} + +} diff --git a/src/libexpr/print-options.hh b/src/libexpr/print-options.hh index e868790e060..9ea4693fa07 100644 --- a/src/libexpr/print-options.hh +++ b/src/libexpr/print-options.hh @@ -8,11 +8,21 @@ namespace nix { +struct Value; +class EvalState; + /** * Options for printing Nix values. */ struct PrintOptions { + /** + * Construct `PrintOptions` from a Nix `Value`. + * + * See `builtins.toStringDebugOptions` for details on the format. + */ + static PrintOptions fromValue(EvalState & state, Value & v); + /** * If true, output ANSI color sequences. */