Commit d70633f9 authored by Oliver Schmidt's avatar Oliver Schmidt
Browse files

lib.attrsets.attrsToList: add function



For transforming back between lists and attrsets, it makes sense to have
a quasi-inverse of `builtins.listToAttrs` available as a library
function.

Co-authored-by: default avatarSilvan Mosberger <github@infinisil.com>
Co-authored-by: default avatarRobert Hensing <roberth@users.noreply.github.com>
parent 27eedb56
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -542,6 +542,36 @@ rec {
    attrs:
    map (name: f name attrs.${name}) (attrNames attrs);

  /*
    Deconstruct an attrset to a list of name-value pairs as expected by [`builtins.listToAttrs`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-listToAttrs).
    Each element of the resulting list is an attribute set with these attributes:
    - `name` (string): The name of the attribute
    - `value` (any): The value of the attribute

    The following is always true:
    ```nix
    builtins.listToAttrs (attrsToList attrs) == attrs
    ```

    :::{.warning}
    The opposite is not always true. In general expect that
    ```nix
    attrsToList (builtins.listToAttrs list) != list
    ```

    This is because the `listToAttrs` removes duplicate names and doesn't preserve the order of the list.
    :::

    Example:
      attrsToList { foo = 1; bar = "asdf"; }
      => [ { name = "bar"; value = "asdf"; } { name = "foo"; value = 1; } ]

    Type:
      attrsToList :: AttrSet -> [ { name :: String; value :: Any; } ]

  */
  attrsToList = mapAttrsToList nameValuePair;


  /* Like `mapAttrs`, except that it recursively applies itself to
     the *leaf* attributes of a potentially-nested attribute set:
+2 −2
Original line number Diff line number Diff line
@@ -81,8 +81,8 @@ let
    inherit (self.attrsets) attrByPath hasAttrByPath setAttrByPath
      getAttrFromPath attrVals attrValues getAttrs catAttrs filterAttrs
      filterAttrsRecursive foldlAttrs foldAttrs collect nameValuePair mapAttrs
      mapAttrs' mapAttrsToList concatMapAttrs mapAttrsRecursive mapAttrsRecursiveCond
      genAttrs isDerivation toDerivation optionalAttrs
      mapAttrs' mapAttrsToList attrsToList concatMapAttrs mapAttrsRecursive
      mapAttrsRecursiveCond genAttrs isDerivation toDerivation optionalAttrs
      zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
      recursiveUpdate matchAttrs overrideExisting showAttrPath getOutput getBin
      getLib getDev getMan chooseDevOutputs zipWithNames zip
+24 −0
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ let
    expr = (builtins.tryEval (builtins.seq expr "didn't throw"));
    expected = { success = false; value = false; };
  };
  testingEval = expr: {
    expr = (builtins.tryEval expr).success;
    expected = true;
  };
  testingDeepThrow = expr: testingThrow (builtins.deepSeq expr expr);

  testSanitizeDerivationName = { name, expected }:
@@ -784,6 +788,26 @@ runTests {
    expected = { a = 1; b = 2; };
  };

  testListAttrsReverse = let
    exampleAttrs = {foo=1; bar="asdf"; baz = [1 3 3 7]; fnord=null;};
    exampleSingletonList = [{name="foo"; value=1;}];
  in {
    expr = {
      isReverseToListToAttrs = builtins.listToAttrs (attrsToList exampleAttrs) == exampleAttrs;
      isReverseToAttrsToList = attrsToList (builtins.listToAttrs exampleSingletonList) == exampleSingletonList;
      testDuplicatePruningBehaviour = attrsToList (builtins.listToAttrs [{name="a"; value=2;} {name="a"; value=1;}]);
    };
    expected = {
      isReverseToAttrsToList = true;
      isReverseToListToAttrs = true;
      testDuplicatePruningBehaviour = [{name="a"; value=2;}];
    };
  };

  testAttrsToListsCanDealWithFunctions = testingEval (
    attrsToList { someFunc= a: a + 1;}
  );

# GENERATORS
# these tests assume attributes are converted to lists
# in alphabetical order