Skip to content

Commit

Permalink
Add builtins.toStringDebugOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
9999years committed Mar 10, 2024
1 parent 3af61fe commit adcb263
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 3 deletions.
3 changes: 3 additions & 0 deletions doc/manual/rl-next/to-string-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
63 changes: 60 additions & 3 deletions src/libexpr/primops/toStringDebug.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "primops.hh"
#include "print-options.hh"
#include "value.hh"

namespace nix {

Expand All @@ -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,
});

}
85 changes: 85 additions & 0 deletions src/libexpr/print-options.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <boost/numeric/conversion/cast.hpp>

#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<size_t>::max();
}

try {
return boost::numeric_cast<size_t>(i);
} catch (boost::numeric::bad_numeric_cast & e) {
state.error<EvalError>(
"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,
};
}

}
10 changes: 10 additions & 0 deletions src/libexpr/print-options.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down

0 comments on commit adcb263

Please sign in to comment.