lib: rec {
  /*
  Flatten a tree of paths (i.e. a tree generated by `rakeLeaves`).

  This is a function taken from digga.

  @param  tree the tree to flatten
  @return      flattened attrset with paths to values given in reverse DNS notation
  */
  flattenTree = tree: let
    op = sum: path: val: let
      pathStr = builtins.concatStringsSep "." path; # Dot-based reverse DNS notation
    in
      if builtins.isPath val
      then (sum // {"${pathStr}" = val;}) # Path is complete
      else if builtins.isAttrs val
      then (recurse sum path val) # Recurse into this attrset
      else sum; # Ignore values that are not paths or attrsets

    recurse = sum: path: val:
      builtins.foldl'
      (sum: key: op sum (path ++ [key]) val.${key})
      sum
      (builtins.attrNames val);
  in
    recurse {} [] tree;

  /*
  Collect all Nix files in a directory recursively into a tree that preserves
  information about the files' locations.

  This is a function taken from digga, and is only relevant to Nix files; it's pretty
  irrelevant for other filetypes. Directories with a `default.nix` file are not recursed
  into, and are added to this attrset like ordinary Nix files.

  @param  directory location of Nix files
  @return           attrset of Nix files with locations corresponding to attrset paths
  */
  rakeLeaves = directory: let
    # This is only a useful operation for Nix files and directories.
    seive = file: type:
      (type == "regular" && lib.hasSuffix ".nix" file) || (type == "directory");

    collect = file: type: {
      name = lib.removeSuffix ".nix" file;
      value = let
        path = directory + "/${file}";
      in
        if
          (type == "regular")
          || (type == "directory" && builtins.pathExists (path + "/default.nix"))
        then path
        else rakeLeaves path;
    };

    files =
      if builtins.pathExists directory
      then lib.filterAttrs seive (builtins.readDir directory)
      else {};
  in
    lib.filterAttrs (_: v: v != {}) (lib.mapAttrs' collect files);

  collectLeaves = directory: lib.attrValues (flattenTree (rakeLeaves directory));

  /*
  Collect all modules in a directory into an attrset where keys are module names,
  and values are the modules themselves.

  This is particularly useful when modules need names, such as the `nixosModules`
  part of the flake schema.

  @param  directory location of Nix modules
  @return           attrset of Nix modules
  */
  importModules = directory:
    with lib; let
      moduleName = name: let
        path = splitString "." name;
      in
        last path;
    in
      mapAttrs' (n: nameValuePair (moduleName n)) (flattenTree (rakeLeaves directory));
}
