blob: 633915f78b3df711eb2454c6f9eee87e88a7c663 [file] [log] [blame]
Bartosz Stebel56ff18c2021-08-30 23:28:45 +02001# Copyright (c) 2019 Vincent Ambo
2# Copyright (c) 2020-2021 The TVL Authors
3# SPDX-License-Identifier: MIT
4#
5# Provides a function to automatically read a a filesystem structure
6# into a Nix attribute set.
7#
8# Optionally accepts an argument `argsFilter` on import, which is a
9# function that receives the current tree location (as a list of
10# strings) and the argument set and can arbitrarily modify it.
11{ argsFilter ? (x: _parts: x)
12, ... }:
13
14let
15 inherit (builtins)
16 attrNames
17 baseNameOf
18 concatStringsSep
19 filter
20 hasAttr
21 head
22 isAttrs
23 length
24 listToAttrs
25 map
26 match
27 readDir
28 substring;
29
30 assertMsg = pred: msg:
31 if pred
32 then true
33 else builtins.trace msg false;
34
35 argsWithPath = args: parts:
36 let meta.locatedAt = parts;
37 in meta // (if isAttrs args then args else args meta);
38
39 readDirVisible = path:
40 let
41 children = readDir path;
42 isVisible = f: f == ".skip-subtree" || (substring 0 1 f) != ".";
43 names = filter isVisible (attrNames children);
44 in listToAttrs (map (name: {
45 inherit name;
46 value = children.${name};
47 }) names);
48
49 # Create a mark containing the location of this attribute.
50 marker = parts: {
51 __readTree = parts;
52 };
53
54 # The marker is added to every set that was imported directly by
55 # readTree.
56 importWithMark = args: path: parts:
57 let
58 importedFile = import path;
59 pathType = builtins.typeOf importedFile;
60 imported =
61 assert assertMsg
62 (pathType == "lambda")
63 "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }";
64 importedFile (argsFilter (argsWithPath args parts) parts);
65 in if (isAttrs imported)
66 then imported // (marker parts)
67 else imported;
68
69 nixFileName = file:
70 let res = match "(.*)\\.nix" file;
71 in if res == null then null else head res;
72
73 readTree = { args, initPath, rootDir, parts }:
74 let
75 dir = readDirVisible initPath;
76 joinChild = c: initPath + ("/" + c);
77
78 self = if rootDir
79 then { __readTree = []; }
80 else importWithMark args initPath parts;
81
82 # Import subdirectories of the current one, unless the special
83 # `.skip-subtree` file exists which makes readTree ignore the
84 # children.
85 #
86 # This file can optionally contain information on why the tree
87 # should be ignored, but its content is not inspected by
88 # readTree
89 filterDir = f: dir."${f}" == "directory";
90 children = if hasAttr ".skip-subtree" dir then [] else map (c: {
91 name = c;
92 value = readTree {
93 args = args;
94 initPath = (joinChild c);
95 rootDir = false;
96 parts = (parts ++ [ c ]);
97 };
98 }) (filter filterDir (attrNames dir));
99
100 # Import Nix files
101 nixFiles = filter (f: f != null) (map nixFileName (attrNames dir));
102 nixChildren = map (c: let p = joinChild (c + ".nix"); in {
103 name = c;
104 value = importWithMark args p (parts ++ [ c ]);
105 }) nixFiles;
106 in if dir ? "default.nix"
107 then (if isAttrs self then self // (listToAttrs children) else self)
108 else (listToAttrs (nixChildren ++ children) // (marker parts));
109
110in {
111 __functor = _: args: initPath: readTree {
112 inherit args initPath;
113 rootDir = true;
114 parts = [];
115 };
116}