Merge pull request #9931 from 9999years/pretty-printer

Pretty-print values in the REPL
This commit is contained in:
Théophane Hufschmitt 2024-02-14 13:32:58 +01:00 committed by GitHub
commit d857914e1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 195 additions and 23 deletions

View file

@ -101,7 +101,8 @@ struct NixRepl
.ansiColors = true, .ansiColors = true,
.force = true, .force = true,
.derivationPaths = true, .derivationPaths = true,
.maxDepth = maxDepth .maxDepth = maxDepth,
.prettyIndent = 2
}); });
} }
}; };

View file

@ -17,24 +17,29 @@ struct PrintOptions
* If true, output ANSI color sequences. * If true, output ANSI color sequences.
*/ */
bool ansiColors = false; bool ansiColors = false;
/** /**
* If true, force values. * If true, force values.
*/ */
bool force = false; bool force = false;
/** /**
* If true and `force` is set, print derivations as * If true and `force` is set, print derivations as
* `«derivation /nix/store/...»` instead of as attribute sets. * `«derivation /nix/store/...»` instead of as attribute sets.
*/ */
bool derivationPaths = false; bool derivationPaths = false;
/** /**
* If true, track which values have been printed and skip them on * If true, track which values have been printed and skip them on
* subsequent encounters. Useful for self-referential values. * subsequent encounters. Useful for self-referential values.
*/ */
bool trackRepeated = true; bool trackRepeated = true;
/** /**
* Maximum depth to evaluate to. * Maximum depth to evaluate to.
*/ */
size_t maxDepth = std::numeric_limits<size_t>::max(); size_t maxDepth = std::numeric_limits<size_t>::max();
/** /**
* Maximum number of attributes in attribute sets to print. * Maximum number of attributes in attribute sets to print.
* *
@ -42,6 +47,7 @@ struct PrintOptions
* attribute set encountered. * attribute set encountered.
*/ */
size_t maxAttrs = std::numeric_limits<size_t>::max(); size_t maxAttrs = std::numeric_limits<size_t>::max();
/** /**
* Maximum number of list items to print. * Maximum number of list items to print.
* *
@ -49,10 +55,26 @@ struct PrintOptions
* list encountered. * list encountered.
*/ */
size_t maxListItems = std::numeric_limits<size_t>::max(); size_t maxListItems = std::numeric_limits<size_t>::max();
/** /**
* Maximum string length to print. * Maximum string length to print.
*/ */
size_t maxStringLength = std::numeric_limits<size_t>::max(); size_t maxStringLength = std::numeric_limits<size_t>::max();
/**
* Indentation width for pretty-printing.
*
* If set to 0 (the default), values are not pretty-printed.
*/
size_t prettyIndent = 0;
/**
* True if pretty-printing is enabled.
*/
inline bool shouldPrettyPrint()
{
return prettyIndent > 0;
}
}; };
/** /**

View file

@ -153,6 +153,7 @@ struct ImportantFirstAttrNameCmp
}; };
typedef std::set<const void *> ValuesSeen; typedef std::set<const void *> ValuesSeen;
typedef std::vector<std::pair<std::string, Value *>> AttrVec;
class Printer class Printer
{ {
@ -163,6 +164,37 @@ private:
std::optional<ValuesSeen> seen; std::optional<ValuesSeen> seen;
size_t attrsPrinted = 0; size_t attrsPrinted = 0;
size_t listItemsPrinted = 0; size_t listItemsPrinted = 0;
std::string indent;
void increaseIndent()
{
if (options.shouldPrettyPrint()) {
indent.append(options.prettyIndent, ' ');
}
}
void decreaseIndent()
{
if (options.shouldPrettyPrint()) {
assert(indent.size() >= options.prettyIndent);
indent.resize(indent.size() - options.prettyIndent);
}
}
/**
* Print a space (for separating items or attributes).
*
* If pretty-printing is enabled, a newline and the current `indent` is
* printed instead.
*/
void printSpace(bool prettyPrint)
{
if (prettyPrint) {
output << "\n" << indent;
} else {
output << " ";
}
}
void printRepeated() void printRepeated()
{ {
@ -260,6 +292,28 @@ private:
} }
} }
bool shouldPrettyPrintAttrs(AttrVec & v)
{
if (!options.shouldPrettyPrint() || v.empty()) {
return false;
}
// Pretty-print attrsets with more than one item.
if (v.size() > 1) {
return true;
}
auto item = v[0].second;
if (!item) {
return true;
}
// Pretty-print single-item attrsets only if they contain nested
// structures.
auto itemType = item->type();
return itemType == nList || itemType == nAttrs || itemType == nThunk;
}
void printAttrs(Value & v, size_t depth) void printAttrs(Value & v, size_t depth)
{ {
if (seen && !seen->insert(v.attrs).second) { if (seen && !seen->insert(v.attrs).second) {
@ -270,9 +324,10 @@ private:
if (options.force && options.derivationPaths && state.isDerivation(v)) { if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v); printDerivation(v);
} else if (depth < options.maxDepth) { } else if (depth < options.maxDepth) {
output << "{ "; increaseIndent();
output << "{";
std::vector<std::pair<std::string, Value *>> sorted; AttrVec sorted;
for (auto & i : *v.attrs) for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value)); sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
@ -281,7 +336,11 @@ private:
else else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp()); std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
auto prettyPrint = shouldPrettyPrintAttrs(sorted);
for (auto & i : sorted) { for (auto & i : sorted) {
printSpace(prettyPrint);
if (attrsPrinted >= options.maxAttrs) { if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes"); printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
break; break;
@ -290,14 +349,39 @@ private:
printAttributeName(output, i.first); printAttributeName(output, i.first);
output << " = "; output << " = ";
print(*i.second, depth + 1); print(*i.second, depth + 1);
output << "; "; output << ";";
attrsPrinted++; attrsPrinted++;
} }
decreaseIndent();
printSpace(prettyPrint);
output << "}"; output << "}";
} else } else {
output << "{ ... }"; output << "{ ... }";
} }
}
bool shouldPrettyPrintList(std::span<Value * const> list)
{
if (!options.shouldPrettyPrint() || list.empty()) {
return false;
}
// Pretty-print lists with more than one item.
if (list.size() > 1) {
return true;
}
auto item = list[0];
if (!item) {
return true;
}
// Pretty-print single-item lists only if they contain nested
// structures.
auto itemType = item->type();
return itemType == nList || itemType == nAttrs || itemType == nThunk;
}
void printList(Value & v, size_t depth) void printList(Value & v, size_t depth)
{ {
@ -306,11 +390,16 @@ private:
return; return;
} }
output << "[ ";
if (depth < options.maxDepth) { if (depth < options.maxDepth) {
for (auto elem : v.listItems()) { increaseIndent();
output << "[";
auto listItems = v.listItems();
auto prettyPrint = shouldPrettyPrintList(listItems);
for (auto elem : listItems) {
printSpace(prettyPrint);
if (listItemsPrinted >= options.maxListItems) { if (listItemsPrinted >= options.maxListItems) {
printElided(v.listSize() - listItemsPrinted, "item", "items"); printElided(listItems.size() - listItemsPrinted, "item", "items");
break; break;
} }
@ -319,13 +408,15 @@ private:
} else { } else {
printNullptr(); printNullptr();
} }
output << " ";
listItemsPrinted++; listItemsPrinted++;
} }
}
else decreaseIndent();
output << "... "; printSpace(prettyPrint);
output << "]"; output << "]";
} else {
output << "[ ... ]";
}
} }
void printFunction(Value & v) void printFunction(Value & v)
@ -488,6 +579,7 @@ public:
{ {
attrsPrinted = 0; attrsPrinted = 0;
listItemsPrinted = 0; listItemsPrinted = 0;
indent.clear();
if (options.trackRepeated) { if (options.trackRepeated) {
seen.emplace(); seen.emplace();

View file

@ -6,4 +6,4 @@ error:
| ^ | ^
10| 10|
error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «4294967295 attributes elided»}; «4294967294 attributes elided»}; «4294967293 attributes elided»} error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «4294967295 attributes elided» }; «4294967294 attributes elided» }; «4294967293 attributes elided» }

View file

@ -146,29 +146,86 @@ echo "$replResult" | grepQuiet -s afterChange
# Normal output should print attributes in lexicographical order non-recursively # Normal output should print attributes in lexicographical order non-recursively
testReplResponseNoRegex ' testReplResponseNoRegex '
{ a = { b = 2; }; l = [ 1 2 3 ]; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; } { a = { b = 2; }; l = [ 1 2 3 ]; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; }
' '{ a = { ... }; l = [ ... ]; n = 1234; s = "string"; x = { ... }; }' ' \
'{
a = { ... };
l = [ ... ];
n = 1234;
s = "string";
x = { ... };
}
'
# Same for lists, but order is preserved # Same for lists, but order is preserved
testReplResponseNoRegex ' testReplResponseNoRegex '
[ 42 1 "thingy" ({ a = 1; }) ([ 1 2 3 ]) ] [ 42 1 "thingy" ({ a = 1; }) ([ 1 2 3 ]) ]
' '[ 42 1 "thingy" { ... } [ ... ] ]' ' \
'[
42
1
"thingy"
{ ... }
[ ... ]
]
'
# Same for let expressions # Same for let expressions
testReplResponseNoRegex ' testReplResponseNoRegex '
let x = { y = { a = 1; }; inherit x; }; in x let x = { y = { a = 1; }; inherit x; }; in x
' '{ x = «repeated»; y = { ... }; }' ' \
'{
x = { ... };
y = { ... };
}
'
# The :p command should recursively print sets, but prevent infinite recursion # The :p command should recursively print sets, but prevent infinite recursion
testReplResponseNoRegex ' testReplResponseNoRegex '
:p { a = { b = 2; }; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; } :p { a = { b = 2; }; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; }
' '{ a = { b = 2; }; n = 1234; s = "string"; x = { y = { z = { y = «repeated»; }; }; }; }' ' \
'{
a = { b = 2; };
n = 1234;
s = "string";
x = {
y = {
z = {
y = «repeated»;
};
};
};
}
'
# Same for lists # Same for lists
testReplResponseNoRegex ' testReplResponseNoRegex '
:p [ 42 1 "thingy" (rec { a = 1; b = { inherit a; inherit b; }; }) ([ 1 2 3 ]) ] :p [ 42 1 "thingy" (rec { a = 1; b = { inherit a; inherit b; }; }) ([ 1 2 3 ]) ]
' '[ 42 1 "thingy" { a = 1; b = { a = 1; b = «repeated»; }; } [ 1 2 3 ] ]' ' \
'[
42
1
"thingy"
{
a = 1;
b = {
a = 1;
b = «repeated»;
};
}
[
1
2
3
]
]
'
# Same for let expressions # Same for let expressions
testReplResponseNoRegex ' testReplResponseNoRegex '
:p let x = { y = { a = 1; }; inherit x; }; in x :p let x = { y = { a = 1; }; inherit x; }; in x
' '{ x = «repeated»; y = { a = 1; }; }' ' \
'{
x = «repeated»;
y = { a = 1 };
}
'

View file

@ -720,7 +720,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL "}", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL " }",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxAttrs = 1 .maxAttrs = 1
@ -733,7 +733,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL "}", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxAttrs = 1 .maxAttrs = 1
@ -757,7 +757,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
vList.bigList.size = 2; vList.bigList.size = 2;
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL "]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxListItems = 1 .maxListItems = 1
@ -770,7 +770,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
vList.bigList.size = 3; vList.bigList.size = 3;
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL "]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxListItems = 1 .maxListItems = 1