# nixLib - A library of nix functions for vhack.eu # # Copyright (C) 2025 Benedikt Peetz # SPDX-License-Identifier: LGPL-3.0-or-later # # This file is part of vhack.eu's nix library. # # You should have received a copy of the License along with this program. # If not, see . {warn}: # Adapted from this: https://github.com/NixOS/nixpkgs/blob/1814b56453c91192f6d5a6276079948f9fe96c18/pkgs/top-level/by-name-overlay.nix # This file should not depend on `pkgs` and thus not use `lib`. { baseDirectory, fileName ? null, fileRegex ? null, finalizeFunction ? name: value: value, useShards ? true, }: assert fileName == null -> fileRegex != null; assert fileRegex == null -> fileName != null; let finalFileRegex = if fileRegex == null then "^${escapeRegex fileName}$" else fileRegex; fileDisplay = if fileName == null then "matching ${fileRegex}" else fileName; # From nixpkgs/lib {{{ # These functions are taken straight out of the `nixpkgs/lib`. # We can't depended on `pkgs` (and thus on `lib`), because the `pkgs` module argument # is only defined in the `nixpkgs` module (which is imported through this function). mapAttrsToList = f: attrs: builtins.map (name: f name attrs.${name}) (builtins.attrNames attrs); stringToCharacters = s: builtins.genList (p: builtins.substring p 1 s) (builtins.stringLength s); escape = list: builtins.replaceStrings list (map (c: "\\${c}") list); escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); mapAttrs' = f: set: builtins.listToAttrs (map (attr: f attr set.${attr}) (builtins.attrNames set)); nameValuePair = name: value: {inherit name value;}; filterAttrs = pred: set: builtins.listToAttrs (builtins.concatMap (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (builtins.attrNames set)); # }}} rawByName = { baseDirectory, finalFileRegex, fileDisplay, finalizeFunction, useShards, }: let # Takes a list of attrs as input and returns one merged attr set. flattenAttrs = list: if builtins.isList list then builtins.foldl' (acc: elem: if builtins.isList elem # Merging them with `//` is okay here, as we can be sure that the attr names are # unique (they were separate dictionary after all) then acc // (flattenAttrs elem) else acc // elem) {} list else list; # Module files for a single shard # Type: String -> String -> ListOf Path namesForShard = shard: type: if type != "directory" then warn "Ignored non-directory, whilst importing by-name directory (${fileDisplay}): '${shard}'" {} else if useShards then namesForElementShard shard type else namesForElementDirect shard type; # Type: String -> String -> ListOf Path mkPath = shard: name: toplevelType: let rawPath = baseDirectory + "/${shard}/${name}"; paths = filterAttrs (_: v: v != null) (mapAttrs' (name: value: if builtins.match finalFileRegex name != null then nameValuePair name value else nameValuePair name null) (builtins.readDir rawPath)); checkPath = path: type: if builtins.pathExists "${rawPath}/${path}" then if toplevelType != "directory" then # The `namesForShard` function should have already printed a warning. [null] else path else warn "'${builtins.toString path}' does not exist. Skipped" null; in if toplevelType != "directory" then # The `namesForShard` function should have already printed a warning. [null] else if (builtins.attrValues paths) == [] then warn "'${fileDisplay}' did not match anything in ${rawPath}. Skipped" [null] else mapAttrsToList checkPath paths; filterNull = list: builtins.filter (value: value != null) list; # Type: String -> String -> AttrSet namesForElementShard = shard: _type: (builtins.mapAttrs (name: type: filterNull (mkPath shard name type)) (builtins.readDir (baseDirectory + "/${shard}"))); # Type: String -> String -> AttrSet namesForElementDirect = name: type: {"${name}" = filterNull ((mkPath ".") name type);}; # A list of all paths. files = flattenAttrs (mapAttrsToList namesForShard (builtins.readDir baseDirectory)); output = builtins.mapAttrs finalizeFunction files; in output; firstPass = rawByName { inherit baseDirectory finalFileRegex fileDisplay finalizeFunction useShards ; }; secondPass = if fileName != null then builtins.mapAttrs (name: value: builtins.head value) (filterAttrs (_: value: value != []) firstPass) else firstPass; in secondPass