aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libcmd/repl.cc3
-rw-r--r--src/libexpr/print-options.hh22
-rw-r--r--src/libexpr/print.cc112
-rw-r--r--tests/functional/lang/eval-fail-bad-string-interpolation-4.err.exp2
-rw-r--r--tests/functional/repl.sh69
-rw-r--r--tests/unit/libexpr/value/print.cc8
6 files changed, 194 insertions, 22 deletions
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 8ff8d04c4..bc5ff9525 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -98,7 +98,8 @@ struct NixRepl
.ansiColors = true,
.force = true,
.derivationPaths = true,
- .maxDepth = maxDepth
+ .maxDepth = maxDepth,
+ .prettyIndent = 2
});
}
};
diff --git a/src/libexpr/print-options.hh b/src/libexpr/print-options.hh
index e03746ece..94767df9c 100644
--- a/src/libexpr/print-options.hh
+++ b/src/libexpr/print-options.hh
@@ -17,24 +17,29 @@ struct PrintOptions
* If true, output ANSI color sequences.
*/
bool ansiColors = false;
+
/**
* If true, force values.
*/
bool force = false;
+
/**
* If true and `force` is set, print derivations as
* `«derivation /nix/store/...»` instead of as attribute sets.
*/
bool derivationPaths = false;
+
/**
* If true, track which values have been printed and skip them on
* subsequent encounters. Useful for self-referential values.
*/
bool trackRepeated = true;
+
/**
* Maximum depth to evaluate to.
*/
size_t maxDepth = std::numeric_limits<size_t>::max();
+
/**
* Maximum number of attributes in attribute sets to print.
*
@@ -42,6 +47,7 @@ struct PrintOptions
* attribute set encountered.
*/
size_t maxAttrs = std::numeric_limits<size_t>::max();
+
/**
* Maximum number of list items to print.
*
@@ -49,10 +55,26 @@ struct PrintOptions
* list encountered.
*/
size_t maxListItems = std::numeric_limits<size_t>::max();
+
/**
* Maximum string length to print.
*/
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 prettyPrint()
+ {
+ return prettyIndent > 0;
+ }
};
/**
diff --git a/src/libexpr/print.cc b/src/libexpr/print.cc
index b792f657d..53d0861bf 100644
--- a/src/libexpr/print.cc
+++ b/src/libexpr/print.cc
@@ -151,6 +151,7 @@ struct ImportantFirstAttrNameCmp
};
typedef std::set<const void *> ValuesSeen;
+typedef std::vector<std::pair<std::string, Value *>> AttrVec;
class Printer
{
@@ -161,6 +162,21 @@ private:
std::optional<ValuesSeen> seen;
size_t attrsPrinted = 0;
size_t listItemsPrinted = 0;
+ std::string indent;
+
+ void increaseIndent()
+ {
+ if (options.prettyPrint()) {
+ indent.append(options.prettyIndent, ' ');
+ }
+ }
+
+ void decreaseIndent()
+ {
+ if (options.prettyPrint()) {
+ indent.resize(indent.size() - options.prettyIndent);
+ }
+ }
void printRepeated()
{
@@ -258,6 +274,28 @@ private:
}
}
+ bool shouldPrettyPrintAttrs(AttrVec & v)
+ {
+ if (!options.prettyPrint() || 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)
{
if (seen && !seen->insert(v.attrs).second) {
@@ -268,9 +306,10 @@ private:
if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v);
} else if (depth < options.maxDepth) {
- output << "{ ";
+ increaseIndent();
+ output << "{";
- std::vector<std::pair<std::string, Value *>> sorted;
+ AttrVec sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
@@ -279,7 +318,15 @@ private:
else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
+ auto prettyPrint = shouldPrettyPrintAttrs(sorted);
+
for (auto & i : sorted) {
+ if (prettyPrint) {
+ output << "\n" << indent;
+ } else {
+ output << " ";
+ }
+
if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
break;
@@ -288,13 +335,42 @@ private:
printAttributeName(output, i.first);
output << " = ";
print(*i.second, depth + 1);
- output << "; ";
+ output << ";";
attrsPrinted++;
}
+ decreaseIndent();
+ if (prettyPrint) {
+ output << "\n" << indent;
+ } else {
+ output << " ";
+ }
output << "}";
- } else
+ } else {
output << "{ ... }";
+ }
+ }
+
+ bool shouldPrettyPrintList(std::span<Value * const> list)
+ {
+ if (!options.prettyPrint() || 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)
@@ -304,9 +380,18 @@ private:
return;
}
- output << "[ ";
if (depth < options.maxDepth) {
- for (auto elem : v.listItems()) {
+ increaseIndent();
+ output << "[";
+ auto listItems = v.listItems();
+ auto prettyPrint = shouldPrettyPrintList(listItems);
+ for (auto elem : listItems) {
+ if (prettyPrint) {
+ output << "\n" << indent;
+ } else {
+ output << " ";
+ }
+
if (listItemsPrinted >= options.maxListItems) {
printElided(v.listSize() - listItemsPrinted, "item", "items");
break;
@@ -317,13 +402,19 @@ private:
} else {
printNullptr();
}
- output << " ";
listItemsPrinted++;
}
+
+ decreaseIndent();
+ if (prettyPrint) {
+ output << "\n" << indent;
+ } else {
+ output << " ";
+ }
+ output << "]";
+ } else {
+ output << "[ ... ]";
}
- else
- output << "... ";
- output << "]";
}
void printFunction(Value & v)
@@ -486,6 +577,7 @@ public:
{
attrsPrinted = 0;
listItemsPrinted = 0;
+ indent.clear();
if (options.trackRepeated) {
seen.emplace();
diff --git a/tests/functional/lang/eval-fail-bad-string-interpolation-4.err.exp b/tests/functional/lang/eval-fail-bad-string-interpolation-4.err.exp
index 5119238d7..6f907106b 100644
--- a/tests/functional/lang/eval-fail-bad-string-interpolation-4.err.exp
+++ b/tests/functional/lang/eval-fail-bad-string-interpolation-4.err.exp
@@ -6,4 +6,4 @@ error:
| ^
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» }
diff --git a/tests/functional/repl.sh b/tests/functional/repl.sh
index 0921edc0f..3765fb492 100644
--- a/tests/functional/repl.sh
+++ b/tests/functional/repl.sh
@@ -151,29 +151,86 @@ echo "$replResult" | grepQuiet -s afterChange
# Normal output should print attributes in lexicographical order non-recursively
testReplResponseNoRegex '
{ 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
testReplResponseNoRegex '
[ 42 1 "thingy" ({ a = 1; }) ([ 1 2 3 ]) ]
-' '[ 42 1 "thingy" { ... } [ ... ] ]'
+' \
+'[
+ 42
+ 1
+ "thingy"
+ { ... }
+ [ ... ]
+]
+'
# Same for let expressions
testReplResponseNoRegex '
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
testReplResponseNoRegex '
: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
testReplResponseNoRegex '
: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
testReplResponseNoRegex '
:p let x = { y = { a = 1; }; inherit x; }; in x
-' '{ x = «repeated»; y = { a = 1; }; }'
+' \
+'{
+ x = «repeated»;
+ y = { a = 1 };
+}
+'
diff --git a/tests/unit/libexpr/value/print.cc b/tests/unit/libexpr/value/print.cc
index c1de3a6a9..aabf156c2 100644
--- a/tests/unit/libexpr/value/print.cc
+++ b/tests/unit/libexpr/value/print.cc
@@ -720,7 +720,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vAttrs.mkAttrs(builder.finish());
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 {
.ansiColors = true,
.maxAttrs = 1
@@ -733,7 +733,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vAttrs.mkAttrs(builder.finish());
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 {
.ansiColors = true,
.maxAttrs = 1
@@ -757,7 +757,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
vList.bigList.size = 2;
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 {
.ansiColors = true,
.maxListItems = 1
@@ -770,7 +770,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
vList.bigList.size = 3;
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 {
.ansiColors = true,
.maxListItems = 1