Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 121 additions & 1 deletion lib/attrsets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
{ lib }:

let
inherit (builtins) head length;
inherit (builtins) head length typeOf;
inherit (lib.asserts) assertMsg;
inherit (lib.trivial)
oldestSupportedReleaseIsAtLeast
mergeAttrs
Expand Down Expand Up @@ -1185,6 +1186,125 @@ rec {
in
recurse [ ] set;

/**
Apply a function to each leaf (non‐attribute‐set 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 attribute’s 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 attribute’s 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.
Expand Down
2 changes: 2 additions & 0 deletions lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ let
concatMapAttrs
mapAttrsRecursive
mapAttrsRecursiveCond
mapAttrsToListRecursive
mapAttrsToListRecursiveCond
genAttrs
isDerivation
toDerivation
Expand Down
114 changes: 114 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ let
makeIncludePath
makeOverridable
mapAttrs
mapAttrsToListRecursive
mapAttrsToListRecursiveCond
mapCartesianProduct
matchAttrs
mergeAttrs
Expand Down Expand Up @@ -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 [
Expand Down
Loading