lib/attrsets: add mapAttrsToListRecursive(Cond) function

This commit is contained in:
Mikael Voss
2025-08-31 12:11:43 +02:00
parent 0e553a831e
commit e5ac84e29c
3 changed files with 237 additions and 1 deletions
+121 -1
View File
@@ -4,7 +4,8 @@
{ lib }:
let
inherit (builtins) head length;
inherit (builtins) head length typeOf;
inherit (lib.asserts) assertMsg;
inherit (lib.trivial)
oldestSupportedReleaseIsAtLeast
mergeAttrs
@@ -1185,6 +1186,125 @@ rec {
in
recurse [ ] set;
/**
Apply a function to each leaf (nonattributeset attribute) of a tree of
nested attribute sets, returning the results of the function as a list,
ordered lexicographically by their attribute paths.
Like `mapAttrsRecursive`, but concatenates the mapping function results
into a list.
# Inputs
`f`
: Mapping function which, given an attributes path and value, returns a
new value.
This value will be an element of the list returned by
`mapAttrsToListRecursive`.
The first argument to the mapping function is a list of attribute names
forming the path to the leaf attribute. The second argument is the leaf
attribute value, which will never be an attribute set.
`set`
: Attribute set to map over.
# Type
```
mapAttrsToListRecursive :: ([String] -> a -> b) -> AttrSet -> [b]
```
# Examples
:::{.example}
## `lib.attrsets.mapAttrsToListRecursive` usage example
```nix
mapAttrsToListRecursive (path: value: "${concatStringsSep "." path}=${value}")
{ n = { a = "A"; m = { b = "B"; c = "C"; }; }; d = "D"; }
=> [ "n.a=A" "n.m.b=B" "n.m.c=C" "d=D" ]
```
:::
*/
mapAttrsToListRecursive = mapAttrsToListRecursiveCond (_: _: true);
/**
Determine the nodes of a tree of nested attribute sets by applying a
predicate, then apply a function to the leaves, returning the results
as a list, ordered lexicographically by their attribute paths.
Like `mapAttrsToListRecursive`, but takes an additional predicate to
decide whether to recurse into an attribute set.
Unlike `mapAttrsRecursiveCond` this predicate receives the attribute path
as its first argument, in addition to the attribute set.
# Inputs
`pred`
: Predicate to decide whether to recurse into an attribute set.
If the predicate returns true, `mapAttrsToListRecursiveCond` recurses into
the attribute set. If the predicate returns false, it does not recurse
but instead applies the mapping function, treating the attribute set as
a leaf.
The first argument to the predicate is a list of attribute names forming
the path to the attribute set. The second argument is the attribute set.
`f`
: Mapping function which, given an attributes path and value, returns a
new value.
This value will be an element of the list returned by
`mapAttrsToListRecursiveCond`.
The first argument to the mapping function is a list of attribute names
forming the path to the leaf attribute. The second argument is the leaf
attribute value, which may be an attribute set if the predicate returned
false.
`set`
: Attribute set to map over.
# Type
```
mapAttrsToListRecursiveCond :: ([String] -> AttrSet -> Bool) -> ([String] -> a -> b) -> AttrSet -> [b]
```
# Examples
:::{.example}
## `lib.attrsets.mapAttrsToListRecursiveCond` usage example
```nix
mapAttrsToListRecursiveCond
(path: as: !(lib.isDerivation as))
(path: value: "--set=${lib.concatStringsSep "." path}=${toString value}")
{
rust.optimize = 2;
target = {
riscv64-unknown-linux-gnu.linker = pkgs.lld;
};
}
=> [ "--set=rust.optimize=2" "--set=target.riscv64-unknown-linux-gnu.linker=/nix/store/sjw4h1k" ]
```
:::
*/
mapAttrsToListRecursiveCond =
pred: f: set:
let
mapRecursive =
path: value: if isAttrs value && pred path value then recurse path value else [ (f path value) ];
recurse = path: set: concatMap (name: mapRecursive (path ++ [ name ]) set.${name}) (attrNames set);
in
recurse [ ] set;
/**
Generate an attribute set by mapping a function over a list of
attribute names.
+2
View File
@@ -206,6 +206,8 @@ let
concatMapAttrs
mapAttrsRecursive
mapAttrsRecursiveCond
mapAttrsToListRecursive
mapAttrsToListRecursiveCond
genAttrs
isDerivation
toDerivation
+114
View File
@@ -77,6 +77,8 @@ let
makeIncludePath
makeOverridable
mapAttrs
mapAttrsToListRecursive
mapAttrsToListRecursiveCond
mapCartesianProduct
matchAttrs
mergeAttrs
@@ -3237,6 +3239,118 @@ runTests {
];
};
testMapAttrsToListRecursive = {
expr = mapAttrsToListRecursive (p: v: "${concatStringsSep "." p}=${v}") {
a = {
b = "A";
};
c = {
d = "B";
e = {
f = "C";
g = "D";
};
};
h = {
i = {
j = {
k = "E";
};
};
};
};
expected = [
"a.b=A"
"c.d=B"
"c.e.f=C"
"c.e.g=D"
"h.i.j.k=E"
];
};
testMapAttrsToListRecursiveWithLists = {
expr = mapAttrsToListRecursive (p: v: v) {
a = [ ];
b = {
c = [ [ ] ];
};
d = {
e = {
f = [ [ [ ] ] ];
};
};
};
expected = [
[ ]
[ [ ] ]
[ [ [ ] ] ]
];
};
testMapAttrsToListRecursiveCond = {
expr = mapAttrsToListRecursiveCond (p: as: !(as ? stop)) (p: v: v) {
a = {
b = "A";
};
c = {
d = "B";
e = {
stop = null;
f = "C";
g = {
h = "D";
};
};
};
};
expected = [
"A"
"B"
{
stop = null;
f = "C";
g = {
h = "D";
};
}
];
};
testMapAttrsToListRecursiveCondPath = {
expr = mapAttrsToListRecursiveCond (p: as: length p < 2) (p: v: v) {
a = {
b = "A";
};
c = {
d = "B";
e = {
f = "C";
g = "D";
};
};
h = {
i = {
j = {
k = "E";
};
};
};
};
expected = [
"A"
"B"
{
f = "C";
g = "D";
}
{
j = {
k = "E";
};
}
];
};
# The example from the showAttrPath documentation
testShowAttrPathExample = {
expr = showAttrPath [