diff options
author | Robert Hensing <robert@roberthensing.nl> | 2023-12-08 22:59:40 +0100 |
---|---|---|
committer | github-actions[bot] <github-actions[bot]@users.noreply.github.com> | 2023-12-11 12:26:16 +0000 |
commit | aa42f176f60e8fbd2ad6c17f817ae4edd9688f97 (patch) | |
tree | 00c6f83431a5653a95265cb7c8b5b8639f618b6a /lib | |
parent | 26780e483dd33582307d21b553608294a025f72d (diff) |
lib.attrsets.longestValidPathPrefix: init
Allows finding the most specific path that exists. This is useful for error messages relating to attribute paths. (cherry picked from commit 72bd4bbb58f6867a00af2a0ddaae2d601c1ee2c7)
Diffstat (limited to 'lib')
-rw-r--r-- | lib/attrsets.nix | 65 | ||||
-rw-r--r-- | lib/tests/misc.nix | 40 |
2 files changed, 105 insertions, 0 deletions
diff --git a/lib/attrsets.nix b/lib/attrsets.nix index 14ce9c2577313..798a53646d811 100644 --- a/lib/attrsets.nix +++ b/lib/attrsets.nix @@ -66,6 +66,71 @@ rec { else false; + /* + Return the longest prefix of an attribute path that refers to an existing attribute in a nesting of attribute sets. + + Can be used after [`mapAttrsRecursiveCond`](#function-library-lib.attrsets.mapAttrsRecursiveCond) to apply a condition, + although this will evaluate the predicate function on sibling attributes as well. + + Note that the empty attribute path is valid for all values, so this function only throws an exception if any of its inputs does. + + **Laws**: + 1. ```nix + attrsets.longestValidPathPrefix [] x == [] + ``` + + 2. ```nix + hasAttrByPath (attrsets.longestValidPathPrefix p x) x == true + ``` + + Example: + x = { a = { b = 3; }; } + attrsets.longestValidPathPrefix ["a" "b" "c"] x + => ["a" "b"] + attrsets.longestValidPathPrefix ["a"] x + => ["a"] + attrsets.longestValidPathPrefix ["z" "z"] x + => [] + attrsets.longestValidPathPrefix ["z" "z"] (throw "no need") + => [] + + Type: + attrsets.longestValidPathPrefix :: [String] -> Value -> [String] + */ + longestValidPathPrefix = + # A list of strings representing the longest possible path that may be returned. + attrPath: + # The nested attribute set to check. + v: + let + lenAttrPath = length attrPath; + getPrefixForSetAtIndex = + # The nested attribute set to check, if it is an attribute set, which + # is not a given. + remainingSet: + # The index of the attribute we're about to check, as well as + # the length of the prefix we've already checked. + remainingPathIndex: + + if remainingPathIndex == lenAttrPath then + # All previously checked attributes exist, and no attr names left, + # so we return the whole path. + attrPath + else + let + attr = elemAt attrPath remainingPathIndex; + in + if remainingSet ? ${attr} then + getPrefixForSetAtIndex + remainingSet.${attr} # advance from the set to the attribute value + (remainingPathIndex + 1) # advance the path + else + # The attribute doesn't exist, so we return the prefix up to the + # previously checked length. + take remainingPathIndex attrPath; + in + getPrefixForSetAtIndex v 0; + /* Create a new attribute set with `value` set at the nested attribute location specified in `attrPath`. Example: diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 608af656d02c0..b97f080cca3a8 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -697,6 +697,46 @@ runTests { expected = false; }; + testLongestValidPathPrefix_empty_empty = { + expr = attrsets.longestValidPathPrefix [ ] { }; + expected = [ ]; + }; + + testLongestValidPathPrefix_empty_nonStrict = { + expr = attrsets.longestValidPathPrefix [ ] (throw "do not use"); + expected = [ ]; + }; + + testLongestValidPathPrefix_zero = { + expr = attrsets.longestValidPathPrefix [ "a" (throw "do not use") ] { d = null; }; + expected = [ ]; + }; + + testLongestValidPathPrefix_zero_b = { + expr = attrsets.longestValidPathPrefix [ "z" "z" ] "remarkably harmonious"; + expected = [ ]; + }; + + testLongestValidPathPrefix_one = { + expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a = null; }; + expected = [ "a" ]; + }; + + testLongestValidPathPrefix_two = { + expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b = null; }; + expected = [ "a" "b" ]; + }; + + testLongestValidPathPrefix_three = { + expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c = null; }; + expected = [ "a" "b" "c" ]; + }; + + testLongestValidPathPrefix_three_extra = { + expr = attrsets.longestValidPathPrefix [ "a" "b" "c" ] { a.b.c.d = throw "nope"; }; + expected = [ "a" "b" "c" ]; + }; + testFindFirstIndexExample1 = { expr = lists.findFirstIndex (x: x > 3) (abort "index found, so a default must not be evaluated") [ 1 6 4 ]; expected = 1; |