diff --git a/lib/attrsets.nix b/lib/attrsets.nix index 8482887023f76..2396f20375ba9 100644 --- a/lib/attrsets.nix +++ b/lib/attrsets.nix @@ -492,11 +492,11 @@ rec { + "update an attribute inside of it." ); - # We get the final result by applying all the updates on this level - # after having applied all the nested updates - # We use foldl instead of foldl' so that in case of multiple updates, - # intermediate values aren't evaluated if not needed in + # We get the final result by applying all the updates on this level + # after having applied all the nested updates + # We use foldl instead of foldl' so that in case of multiple updates, + # intermediate values aren't evaluated if not needed foldl (acc: el: el.update acc) withNestedMods split.right; in @@ -871,6 +871,62 @@ rec { else [ ]; + /** + Recursively collect sets that verify a given predicate named `pred` + from the set `attrs`. The recursion is stopped when the predicate is + verified. This version of `collect` also collects the attribute paths + of the items. + + # Inputs + + `pred` + + : Given an attribute's value, determine if recursion should stop. + + `attrs` + + : The attribute set to recursively collect. + + # Type + + ``` + collect' :: (AttrSet -> Bool) -> AttrSet -> [{ path :: [ String ], value :: x }] + ``` + + # Examples + :::{.example} + ## `lib.attrsets.collect'` usage example + + ```nix + collect' isList { a = { b = ["b"]; }; c = [1]; } + => [ + { path = [ "a" "b" ]; value = [ "b" ]; + { path = [ "c" ]; value = [ 1 ]; } + ] + + collect (x: x ? outPath) + { a = { outPath = "a/"; }; b = { outPath = "b/"; }; } + => [ + { path = [ "a" ]; value = { outPath = "a/"; }; } + { path = [ "b" ]; value = { outPath = "b/"; }; } + ] + ``` + + ::: + */ + collect' = + let + collect'' = + path: pred: value: + if pred value then + [ { inherit path value; } ] + else if isAttrs value then + concatMap ({ name, value }: collect'' (path ++ [ name ]) pred value) (attrsToList value) + else + [ ]; + in + collect'' [ ]; + /** Return the cartesian product of attribute set value combinations. diff --git a/lib/default.nix b/lib/default.nix index e671fcf9546d9..f13b74959029f 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -198,6 +198,7 @@ let foldlAttrs foldAttrs collect + collect' nameValuePair mapAttrs mapAttrs' diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index f5f1fb5e7c2d5..7ff37cbe3fcd1 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -35,6 +35,8 @@ let callPackageWith cartesianProduct cli + collect + collect' composeExtensions composeManyExtensions concatLines @@ -297,6 +299,83 @@ runTests { ]; }; + testCollect = { + expr = [ + (collect (x: x ? special) { + a.b.c.special = true; + x.y.z.special = false; + }) + (collect (x: x == 1) { + a = 1; + b = 2; + c = 3; + d.inner = 1; + }) + ]; + expected = [ + [ + { special = true; } + { special = false; } + ] + [ + 1 + 1 + ] + ]; + }; + + testCollect' = { + expr = [ + (collect' (x: x ? special) { + a.b.c.special = true; + x.y.z.special = false; + }) + (collect' (x: x == 1) { + a = 1; + b = 2; + c = 3; + d.inner = 1; + }) + ]; + expected = [ + [ + { + path = [ + "a" + "b" + "c" + ]; + value = { + special = true; + }; + } + { + path = [ + "x" + "y" + "z" + ]; + value = { + special = false; + }; + } + ] + [ + { + path = [ "a" ]; + value = 1; + } + { + path = [ + "d" + "inner" + ]; + value = 1; + } + ] + ]; + }; + testComposeExtensions = { expr = let