diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-11-02 15:19:44 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-11-02 15:19:44 +0100 |
| commit | 39a3a1a6f8c0a5bd387dcddf07ea686a3f2e493d (patch) | |
| tree | efabda5300b8247f1b7bc2787247a7351e52d100 /by-name.nix | |
| parent | fix(by-name-overlay): Correctly display the `rawPath` in warnings (diff) | |
| download | nix-library-39a3a1a6f8c0a5bd387dcddf07ea686a3f2e493d.zip | |
treewide: Add new math and options subcategories and restructure
Diffstat (limited to 'by-name.nix')
| -rw-r--r-- | by-name.nix | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/by-name.nix b/by-name.nix new file mode 100644 index 0000000..2d03e75 --- /dev/null +++ b/by-name.nix @@ -0,0 +1,149 @@ +# nixLib - A library of nix functions for vhack.eu +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# 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 <https://www.gnu.org/licenses/gpl-3.0.txt>. +{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, + relativePaths ? false, +}: +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, + relativePaths, + }: 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 -> 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 = pathSegment: type: let + path = rawPath + "/${pathSegment}"; + in + if builtins.pathExists path + then + if toplevelType != "directory" + then + # The `namesForShard` function should have already printed a warning. + [null] + else if relativePaths + then pathSegment + 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 ${builtins.toString 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. + output = flattenAttrs (mapAttrsToList namesForShard (builtins.readDir baseDirectory)); + in + output; + + firstPass = rawByName { + inherit + baseDirectory + finalFileRegex + fileDisplay + finalizeFunction + useShards + relativePaths + ; + }; + + secondPass = + if fileName != null + then + builtins.mapAttrs (name: value: builtins.head value) + (filterAttrs (_: value: value != []) firstPass) + else firstPass; +in + builtins.mapAttrs + finalizeFunction + secondPass |
