summary refs log tree commit diff stats
path: root/by-name-overlay.nix
blob: da53c5a440e2e0ca7749bb9f8b8838aa9df70b88 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# 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,
}:
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.
    output = flattenAttrs (mapAttrsToList namesForShard (builtins.readDir baseDirectory));
  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
  builtins.mapAttrs
  finalizeFunction
  secondPass