Struggling with a simple transformation on attrsets

I’ve been having a really hard time trying to figure out this simple transformation in the nix language, not finding any examples or tutorial subjects on it either.

Starting with an attrset that looks sort of like this:

{
    "folder1"
    {
        path = "~/Folder1";
        subscribers = ["alice" "bob"];
    }

    "folder2"
    {
        path = "~/Folder2";
        subscribers = ["alice"];
    }
}

I want to transform it into an attrset that looks like this:

{
    "/wherever/alice/Folder1"
    {
        source = "/somewhere/Folder1";
    }
    
    "/wherever/bob/Folder1"
    {
        source = "/somewhere/Folder1";
    }

    "/wherever/alice/Folder2"
    {
        source = "/somewhere/Folder2";
    }
}

If subscribers isn’t a list, this is very easy:

lib.attrsets.mapAttrs' (
    name: value:
    lib.attrsets.nameValuePair (makeNameFn value.path value.subscriber) (
        makeSourceFn value.path value.subscriber
    )
) srcFolders

But it’s not clear how I can generate multiple nameValuePair inside of the mapAttrs, or genAttrs for that matter. I’ve tried lists.forEach value.subscribers (…), but that simply errors, claiming that there is no name member present (which I thought nameValuePair would have handled???)

Anyhow, this is all incredibly confusing and nearly impossible to visualize, so any help is appreciated.

You cannot. map is fundamentally a, well, mapping. That’s 1:1.

This is a relatively awkward transform. You’d probably do best with a genAttrs-alike for each top-level attr, and then merging them.

Something like:

input: lib.listToAttrs 
  (lib.flatten
    (lib.mapAttrsToList
      (name: folder:
        map (subscriber: {
          name = "/wherever/${subscriber}/${name}";
          value.source = "/somewhere/${name}";
       })
     folder.subscribers)
    input))

I’m sure someone who does more functional programming could come up with an even cleaner solution.

1 Like

I would use lib.concatMapAttrs.

nix-repl> srcFolders = {
        >   folder1 = {
        >     path = "Folder1";
        >     subscribers = [ "alice" "bob" ];
        >   };
        >   folder2 = { 
        >     path = "Folder2";
        >     subscribers = [ "alice" ];
        >   };
        > }
nix-repl> :p (lib.concatMapAttrs
        >   (name: value: builtins.listToAttrs (map
        >     (subscriber: {
        >       name = "/wherever/${subscriber}/${value.path}";
        >       value.source = "/somewhere/${value.path}";
        >     })
        >     value.subscribers))
        >   srcFolders)
{
  "/wherever/alice/Folder1" = { source = "/somewhere/Folder1"; };
  "/wherever/alice/Folder2" = { source = "/somewhere/Folder2"; };
  "/wherever/bob/Folder1" = { source = "/somewhere/Folder1"; };
}


2 Likes

Wow, that worked, thanks.

I swear that I already tried something similar, but I must have been overcomplicating it.

1 Like