{ ... }: { collectModules = { path, scope ? { }, fileName ? "default.nix", }: ( let #some helpers (mostly from nixpkgs or at least derived from that) sublist = start: count: list: let len = builtins.length list; in builtins.genList (n: builtins.elemAt list (n + start)) ( if start >= len then 0 else if start + count > len then len - start else count ); pipe = builtins.foldl' (x: f: f x); flatten = x: if builtins.isList x then builtins.concatMap (y: flatten y) x else [ x ]; drop = count: list: sublist count (builtins.length list) list; splitPath = path: builtins.filter builtins.isString (builtins.split "/" (builtins.toString path)); #actual logic starts here fileNameLength = builtins.stringLength fileName; basePathLength = builtins.length (splitPath ./.); collectFiles = path: pipe (builtins.readDir path) [ (builtins.mapAttrs ( name: value: if value == "directory" then (collectFiles (path + "/${name}")) else (path + "/${name}") )) builtins.attrValues flatten (builtins.filter ( elem: let pathLength = builtins.length (splitPath elem); in ( ( let strLength = builtins.stringLength elem; in builtins.substring (strLength - fileNameLength) strLength elem ) == fileName ) && (pathLength - 1) != basePathLength )) ]; in pipe (collectFiles path) [ (builtins.map (filePath: { name = ( let pathParts = drop basePathLength (splitPath filePath); in builtins.concatStringsSep "-" (sublist 0 ((builtins.length pathParts) - 1) pathParts) ); value = import filePath scope; })) builtins.listToAttrs ] ); }