nix: upgrade readTree
Change-Id: I460800dc3d8095e2ae89b8bd6ed7c5f0c90b6ccf
diff --git a/nix/readtree/default.nix b/nix/readtree/default.nix
new file mode 100644
index 0000000..633915f
--- /dev/null
+++ b/nix/readtree/default.nix
@@ -0,0 +1,116 @@
+# 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.
+#
+# Optionally accepts an argument `argsFilter` on import, which is a
+# function that receives the current tree location (as a list of
+# strings) and the argument set and can arbitrarily modify it.
+{ argsFilter ? (x: _parts: x)
+, ... }:
+
+let
+ inherit (builtins)
+ attrNames
+ baseNameOf
+ concatStringsSep
+ filter
+ hasAttr
+ head
+ isAttrs
+ length
+ listToAttrs
+ map
+ match
+ readDir
+ substring;
+
+ assertMsg = pred: msg:
+ if pred
+ then true
+ else builtins.trace msg false;
+
+ 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.
+ marker = parts: {
+ __readTree = parts;
+ };
+
+ # The marker is added to every set that was imported directly by
+ # readTree.
+ importWithMark = args: path: parts:
+ let
+ importedFile = import path;
+ pathType = builtins.typeOf importedFile;
+ imported =
+ assert assertMsg
+ (pathType == "lambda")
+ "readTree: trying to import ${toString path}, but it’s a ${pathType}, you need to make it a function like { depot, pkgs, ... }";
+ importedFile (argsFilter (argsWithPath args parts) parts);
+ in if (isAttrs imported)
+ then imported // (marker parts)
+ else imported;
+
+ nixFileName = file:
+ let res = match "(.*)\\.nix" file;
+ in if res == null then null else head res;
+
+ readTree = { args, initPath, rootDir, parts }:
+ let
+ dir = readDirVisible initPath;
+ joinChild = c: initPath + ("/" + c);
+
+ self = if rootDir
+ then { __readTree = []; }
+ else importWithMark args initPath parts;
+
+ # 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 {
+ 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"); in {
+ name = c;
+ value = importWithMark args p (parts ++ [ c ]);
+ }) nixFiles;
+ in if dir ? "default.nix"
+ then (if isAttrs self then self // (listToAttrs children) else self)
+ else (listToAttrs (nixChildren ++ children) // (marker parts));
+
+in {
+ __functor = _: args: initPath: readTree {
+ inherit args initPath;
+ rootDir = true;
+ parts = [];
+ };
+}