mirror of
https://github.com/NixOS/nix
synced 2025-06-28 01:11:15 +02:00
Unify and refactor value printing
Previously, there were two mostly-identical value printers -- one in `libexpr/eval.cc` (which didn't force values) and one in `libcmd/repl.cc` (which did force values and also printed ANSI color codes). This PR unifies both of these printers into `print.cc` and provides a `PrintOptions` struct for controlling the output, which allows for toggling whether values are forced, whether repeated values are tracked, and whether ANSI color codes are displayed. Additionally, `PrintOptions` allows tuning the maximum number of attributes, list items, and bytes in a string that will be displayed; this makes it ideal for contexts where printing too much output (e.g. all of Nixpkgs) is distracting. (As requested by @roberth in https://github.com/NixOS/nix/pull/9554#issuecomment-1845095735) Please read the tests for example output. Future work: - It would be nice to provide this function as a builtin, perhaps `builtins.toStringDebug` -- a printing function that never fails would be useful when debugging Nix code. - It would be nice to support customizing `PrintOptions` members on the command line, e.g. `--option to-string-max-attrs 1000`.
This commit is contained in:
parent
c9125603a5
commit
0fa08b4516
18 changed files with 1174 additions and 278 deletions
|
@ -1,24 +1,66 @@
|
|||
#include "print.hh"
|
||||
#include <limits>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "print.hh"
|
||||
#include "ansicolor.hh"
|
||||
#include "signals.hh"
|
||||
#include "store-api.hh"
|
||||
#include "terminal.hh"
|
||||
#include "english.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::ostream &
|
||||
printLiteralString(std::ostream & str, const std::string_view string)
|
||||
void printElided(
|
||||
std::ostream & output,
|
||||
unsigned int value,
|
||||
const std::string_view single,
|
||||
const std::string_view plural,
|
||||
bool ansiColors)
|
||||
{
|
||||
if (ansiColors)
|
||||
output << ANSI_FAINT;
|
||||
output << " «";
|
||||
pluralize(output, value, single, plural);
|
||||
output << " elided»";
|
||||
if (ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
|
||||
std::ostream &
|
||||
printLiteralString(std::ostream & str, const std::string_view string, size_t maxLength, bool ansiColors)
|
||||
{
|
||||
size_t charsPrinted = 0;
|
||||
if (ansiColors)
|
||||
str << ANSI_MAGENTA;
|
||||
str << "\"";
|
||||
for (auto i = string.begin(); i != string.end(); ++i) {
|
||||
if (charsPrinted >= maxLength) {
|
||||
str << "\"";
|
||||
printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors);
|
||||
return str;
|
||||
}
|
||||
|
||||
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
||||
else if (*i == '\n') str << "\\n";
|
||||
else if (*i == '\r') str << "\\r";
|
||||
else if (*i == '\t') str << "\\t";
|
||||
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
|
||||
else str << *i;
|
||||
charsPrinted++;
|
||||
}
|
||||
str << "\"";
|
||||
if (ansiColors)
|
||||
str << ANSI_NORMAL;
|
||||
return str;
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
printLiteralString(std::ostream & str, const std::string_view string)
|
||||
{
|
||||
return printLiteralString(str, string, std::numeric_limits<size_t>::max(), false);
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
printLiteralBool(std::ostream & str, bool boolean)
|
||||
{
|
||||
|
@ -90,5 +132,373 @@ printAttributeName(std::ostream & str, std::string_view name) {
|
|||
return str;
|
||||
}
|
||||
|
||||
bool isImportantAttrName(const std::string& attrName)
|
||||
{
|
||||
return attrName == "type" || attrName == "_type";
|
||||
}
|
||||
|
||||
typedef std::pair<std::string, Value *> AttrPair;
|
||||
|
||||
struct ImportantFirstAttrNameCmp
|
||||
{
|
||||
|
||||
bool operator()(const AttrPair& lhs, const AttrPair& rhs) const
|
||||
{
|
||||
auto lhsIsImportant = isImportantAttrName(lhs.first);
|
||||
auto rhsIsImportant = isImportantAttrName(rhs.first);
|
||||
return std::forward_as_tuple(!lhsIsImportant, lhs.first)
|
||||
< std::forward_as_tuple(!rhsIsImportant, rhs.first);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::set<Value *> ValuesSeen;
|
||||
|
||||
class Printer
|
||||
{
|
||||
private:
|
||||
std::ostream & output;
|
||||
EvalState & state;
|
||||
PrintOptions options;
|
||||
std::optional<ValuesSeen> seen;
|
||||
|
||||
void printRepeated()
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_MAGENTA;
|
||||
output << "«repeated»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printNullptr()
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_MAGENTA;
|
||||
output << "«nullptr»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printElided(unsigned int value, const std::string_view single, const std::string_view plural)
|
||||
{
|
||||
::nix::printElided(output, value, single, plural, options.ansiColors);
|
||||
}
|
||||
|
||||
void printInt(Value & v)
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_CYAN;
|
||||
output << v.integer;
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printFloat(Value & v)
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_CYAN;
|
||||
output << v.fpoint;
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printBool(Value & v)
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_CYAN;
|
||||
printLiteralBool(output, v.boolean);
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printString(Value & v)
|
||||
{
|
||||
printLiteralString(output, v.string_view(), options.maxStringLength, options.ansiColors);
|
||||
}
|
||||
|
||||
void printPath(Value & v)
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_GREEN;
|
||||
output << v.path().to_string(); // !!! escaping?
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printNull()
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_CYAN;
|
||||
output << "null";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printDerivation(Value & v)
|
||||
{
|
||||
try {
|
||||
Bindings::iterator i = v.attrs->find(state.sDrvPath);
|
||||
NixStringContext context;
|
||||
std::string storePath;
|
||||
if (i != v.attrs->end())
|
||||
storePath = state.store->printStorePath(state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
|
||||
|
||||
if (options.ansiColors)
|
||||
output << ANSI_GREEN;
|
||||
output << "«derivation";
|
||||
if (!storePath.empty()) {
|
||||
output << " " << storePath;
|
||||
}
|
||||
output << "»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
} catch (BaseError & e) {
|
||||
printError_(e);
|
||||
}
|
||||
}
|
||||
|
||||
void printAttrs(Value & v, size_t depth)
|
||||
{
|
||||
if (seen && !seen->insert(&v).second) {
|
||||
printRepeated();
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.force && options.derivationPaths && state.isDerivation(v)) {
|
||||
printDerivation(v);
|
||||
} else if (depth < options.maxDepth) {
|
||||
output << "{ ";
|
||||
|
||||
std::vector<std::pair<std::string, Value *>> sorted;
|
||||
for (auto & i : *v.attrs)
|
||||
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
|
||||
|
||||
if (options.maxAttrs == std::numeric_limits<size_t>::max())
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
else
|
||||
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
|
||||
|
||||
size_t attrsPrinted = 0;
|
||||
for (auto & i : sorted) {
|
||||
if (attrsPrinted >= options.maxAttrs) {
|
||||
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
|
||||
break;
|
||||
}
|
||||
|
||||
printAttributeName(output, i.first);
|
||||
output << " = ";
|
||||
print(*i.second, depth + 1);
|
||||
output << "; ";
|
||||
attrsPrinted++;
|
||||
}
|
||||
|
||||
output << "}";
|
||||
} else
|
||||
output << "{ ... }";
|
||||
}
|
||||
|
||||
void printList(Value & v, size_t depth)
|
||||
{
|
||||
if (seen && v.listSize() && !seen->insert(&v).second) {
|
||||
printRepeated();
|
||||
return;
|
||||
}
|
||||
|
||||
output << "[ ";
|
||||
if (depth < options.maxDepth) {
|
||||
size_t listItemsPrinted = 0;
|
||||
for (auto elem : v.listItems()) {
|
||||
if (listItemsPrinted >= options.maxListItems) {
|
||||
printElided(v.listSize() - listItemsPrinted, "item", "items");
|
||||
break;
|
||||
}
|
||||
|
||||
if (elem) {
|
||||
print(*elem, depth + 1);
|
||||
} else {
|
||||
printNullptr();
|
||||
}
|
||||
output << " ";
|
||||
listItemsPrinted++;
|
||||
}
|
||||
}
|
||||
else
|
||||
output << "... ";
|
||||
output << "]";
|
||||
}
|
||||
|
||||
void printFunction(Value & v)
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_BLUE;
|
||||
output << "«";
|
||||
|
||||
if (v.isLambda()) {
|
||||
output << "lambda";
|
||||
if (v.lambda.fun) {
|
||||
if (v.lambda.fun->name) {
|
||||
output << " " << state.symbols[v.lambda.fun->name];
|
||||
}
|
||||
|
||||
std::ostringstream s;
|
||||
s << state.positions[v.lambda.fun->pos];
|
||||
output << " @ " << filterANSIEscapes(s.str());
|
||||
}
|
||||
} else if (v.isPrimOp()) {
|
||||
if (v.primOp)
|
||||
output << *v.primOp;
|
||||
else
|
||||
output << "primop";
|
||||
} else if (v.isPrimOpApp()) {
|
||||
output << "partially applied ";
|
||||
auto primOp = v.primOpAppPrimOp();
|
||||
if (primOp)
|
||||
output << *primOp;
|
||||
else
|
||||
output << "primop";
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
output << "»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printThunk(Value & v)
|
||||
{
|
||||
if (v.isBlackhole()) {
|
||||
// Although we know for sure that it's going to be an infinite recursion
|
||||
// when this value is accessed _in the current context_, it's likely
|
||||
// that the user will misinterpret a simpler «infinite recursion» output
|
||||
// as a definitive statement about the value, while in fact it may be
|
||||
// a valid value after `builtins.trace` and perhaps some other steps
|
||||
// have completed.
|
||||
if (options.ansiColors)
|
||||
output << ANSI_RED;
|
||||
output << "«potential infinite recursion»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
} else if (v.isThunk() || v.isApp()) {
|
||||
if (options.ansiColors)
|
||||
output << ANSI_MAGENTA;
|
||||
output << "«thunk»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void printExternal(Value & v)
|
||||
{
|
||||
v.external->print(output);
|
||||
}
|
||||
|
||||
void printUnknown()
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_RED;
|
||||
output << "«unknown»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void printError_(BaseError & e)
|
||||
{
|
||||
if (options.ansiColors)
|
||||
output << ANSI_RED;
|
||||
output << "«" << e.msg() << "»";
|
||||
if (options.ansiColors)
|
||||
output << ANSI_NORMAL;
|
||||
}
|
||||
|
||||
void print(Value & v, size_t depth)
|
||||
{
|
||||
output.flush();
|
||||
checkInterrupt();
|
||||
|
||||
if (options.force) {
|
||||
try {
|
||||
state.forceValue(v, v.determinePos(noPos));
|
||||
} catch (BaseError & e) {
|
||||
printError_(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (v.type()) {
|
||||
|
||||
case nInt:
|
||||
printInt(v);
|
||||
break;
|
||||
|
||||
case nFloat:
|
||||
printFloat(v);
|
||||
break;
|
||||
|
||||
case nBool:
|
||||
printBool(v);
|
||||
break;
|
||||
|
||||
case nString:
|
||||
printString(v);
|
||||
break;
|
||||
|
||||
case nPath:
|
||||
printPath(v);
|
||||
break;
|
||||
|
||||
case nNull:
|
||||
printNull();
|
||||
break;
|
||||
|
||||
case nAttrs:
|
||||
printAttrs(v, depth);
|
||||
break;
|
||||
|
||||
case nList:
|
||||
printList(v, depth);
|
||||
break;
|
||||
|
||||
case nFunction:
|
||||
printFunction(v);
|
||||
break;
|
||||
|
||||
case nThunk:
|
||||
printThunk(v);
|
||||
break;
|
||||
|
||||
case nExternal:
|
||||
printExternal(v);
|
||||
break;
|
||||
|
||||
default:
|
||||
printUnknown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Printer(std::ostream & output, EvalState & state, PrintOptions options)
|
||||
: output(output), state(state), options(options) { }
|
||||
|
||||
void print(Value & v)
|
||||
{
|
||||
if (options.trackRepeated) {
|
||||
seen.emplace();
|
||||
} else {
|
||||
seen.reset();
|
||||
}
|
||||
|
||||
ValuesSeen seen;
|
||||
print(v, 0);
|
||||
}
|
||||
};
|
||||
|
||||
void printValue(EvalState & state, std::ostream & output, Value & v, PrintOptions options)
|
||||
{
|
||||
Printer(output, state, options).print(v);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue