about summary refs log tree commit diff
path: root/nix/readTree/default.nix
blob: 68988424fb8cc5df8d373d5103396b38e7dddbe1 (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
# Copyright (c) 2019 Vincent Ambo
# Copyright (c) 2020-2021 The TVL Authors
# SPDX-License-Identifier: MIT
#
# Provides a function to automatically read a a filesystem structure
# into a Nix attribute set.
#
# Called with an attribute set taking the following arguments:
#
#   path: Path to a directory from which to start reading the tree.
#
#   args: Argument set to pass to each imported file.
#
#   filter: Function to filter `args` based on the tree location. This should
#           be a function of the form `args -> location -> args`, where the
#           location is a list of strings representing the path components of
#           the current readTree target. Optional.
{ ... }:

let
  inherit (builtins)
    attrNames
    baseNameOf
    concatStringsSep
    filter
    hasAttr
    head
    isAttrs
    length
    listToAttrs
    map
    match
    readDir
    substring;

  argsWithPath = args: parts:
    let meta.locatedAt = parts;
    in meta // (if isAttrs args then args else args meta);

  readDirVisible = path:
    let
      children = readDir path;
      isVisible = f: f == ".skip-subtree" || (substring 0 1 f) != ".";
      names = filter isVisible (attrNames children);
    in listToAttrs (map (name: {
      inherit name;
      value = children.${name};
    }) names);

  # Create a mark containing the location of this attribute and
  # a list of all child attribute names added by readTree.
  marker = parts: children: {
    __readTree = parts;
    __readTreeChildren = builtins.attrNames children;
  };

  # Import a file and enforce our calling convention
  importFile = args: scopedArgs: path: parts: filter:
  let
      importedFile = if scopedArgs != {}
                     then builtins.scopedImport scopedArgs path
                     else import path;
      pathType = builtins.typeOf importedFile;
  in
    if pathType != "lambda"
    then builtins.throw "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }"
    else importedFile (filter (argsWithPath args parts) parts);

  nixFileName = file:
    let res = match "(.*)\\.nix" file;
    in if res == null then null else head res;

  readTree = { args, initPath, rootDir, parts, argsFilter, scopedArgs }:
    let
      dir = readDirVisible initPath;
      joinChild = c: initPath + ("/" + c);

      self = if rootDir
        then { __readTree = []; }
        else importFile args scopedArgs initPath parts argsFilter;

      # Import subdirectories of the current one, unless the special
      # `.skip-subtree` file exists which makes readTree ignore the
      # children.
      #
      # This file can optionally contain information on why the tree
      # should be ignored, but its content is not inspected by
      # readTree
      filterDir = f: dir."${f}" == "directory";
      children = if hasAttr ".skip-subtree" dir then [] else map (c: {
        name = c;
        value = readTree {
          inherit argsFilter scopedArgs;
          args = args;
          initPath = (joinChild c);
          rootDir = false;
          parts = (parts ++ [ c ]);
        };
      }) (filter filterDir (attrNames dir));

      # Import Nix files
      nixFiles = filter (f: f != null) (map nixFileName (attrNames dir));
      nixChildren = map (c: let
        p = joinChild (c + ".nix");
        childParts = parts ++ [ c ];
        imported = importFile args scopedArgs p childParts argsFilter;
      in {
        name = c;
        value =
          if isAttrs imported
          then imported // marker parts {}
          else imported;
      }) nixFiles;

      nodeValue = if dir ? "default.nix" then self else {};

      allChildren = listToAttrs (
        if dir ? "default.nix"
        then children
        else nixChildren ++ children
      );

    in
      if isAttrs nodeValue
      then nodeValue // allChildren // (marker parts allChildren)
      else nodeValue;

in {
  __functor = _:
    { path
    , args
    , filter ? (x: _parts: x)
    , scopedArgs ? {} }:
      readTree {
        inherit args scopedArgs;
        argsFilter = filter;
        initPath = path;
        rootDir = true;
        parts = [];
      };
}