about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--lib/generators.nix24
-rw-r--r--lib/tests/misc.nix18
2 files changed, 35 insertions, 7 deletions
diff --git a/lib/generators.nix b/lib/generators.nix
index bcb0f371a9b51..b1639b677f65d 100644
--- a/lib/generators.nix
+++ b/lib/generators.nix
@@ -205,13 +205,23 @@ rec {
        (This means fn is type Val -> String.) */
     allowPrettyValues ? false,
     /* If this option is true, the output is indented with newlines for attribute sets and lists */
-    multiline ? true
-  }@args: let
-    go = indent: v: with builtins;
+    multiline ? true,
+    /* If this option is not null, `toPretty` will stop evaluating at a certain depth */
+    depthLimit ? null,
+    /* If this option is true, an error will be thrown, if a certain given depth is exceeded */
+    throwOnDepthLimit ? false
+  }@args:
+    assert depthLimit != null -> builtins.isInt depthLimit;
+    assert throwOnDepthLimit -> depthLimit != null;
+    let
+    go = depth: indent: v: with builtins;
     let     isPath   = v: typeOf v == "path";
             introSpace = if multiline then "\n${indent}  " else " ";
             outroSpace = if multiline then "\n${indent}" else " ";
-    in if   isInt      v then toString v
+    in if depthLimit != null && depth > depthLimit then
+      if throwOnDepthLimit then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to pretty-print with `generators.toPretty'!"
+      else "<unevaluated>"
+    else if   isInt      v then toString v
     else if isFloat    v then "~${toString v}"
     else if isString   v then
       let
@@ -233,7 +243,7 @@ rec {
     else if isList     v then
       if v == [] then "[ ]"
       else "[" + introSpace
-        + libStr.concatMapStringsSep introSpace (go (indent + "  ")) v
+        + libStr.concatMapStringsSep introSpace (go (depth + 1) (indent + "  ")) v
         + outroSpace + "]"
     else if isFunction v then
       let fna = lib.functionArgs v;
@@ -252,10 +262,10 @@ rec {
       else "{" + introSpace
           + libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
               (name: value:
-                "${libStr.escapeNixIdentifier name} = ${go (indent + "  ") value};") v)
+                "${libStr.escapeNixIdentifier name} = ${go (depth + 1) (indent + "  ") value};") v)
         + outroSpace + "}"
     else abort "generators.toPretty: should never happen (v = ${v})";
-  in go "";
+  in go 0 "";
 
   # PLIST handling
   toPlist = {}: v: let
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 4b2e5afc1d609..110716ca69132 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -529,6 +529,24 @@ runTests {
     };
   };
 
+  testToPrettyLimit =
+    let
+      a.b = 1;
+      a.c = a;
+    in {
+      expr = generators.toPretty { depthLimit = 2; } a;
+      expected = "{\n  b = 1;\n  c = {\n    b = 1;\n    c = {\n      b = <unevaluated>;\n      c = <unevaluated>;\n    };\n  };\n}";
+    };
+
+  testToPrettyLimitThrow =
+    let
+      a.b = 1;
+      a.c = a;
+    in {
+      expr = (builtins.tryEval (generators.toPretty { depthLimit = 2; throwOnDepthLimit = true; } a)).success;
+      expected = false;
+    };
+
   testToPrettyMultiline = {
     expr = mapAttrs (const (generators.toPretty { })) rec {
       list = [ 3 4 [ false ] ];