*: add default.nix/readTree

This makes all Nix files addressable from root by file path.

For instance, if a file is located in //foo/bar:baz.nix containing:

    { pkgs, ... }:

    pkgs.stdenv.mkDerivation {
      pname = "foo";
      # ...
    }

You can then do:

    nix-build -A foo.bar.baz

All nix files loaded this way must be a function taking a 'config'
attrset - see nix/readTree.nix for more information. Currently the
config attrset contains the following fields:

 - hscloud: the root of the hscloud repository itself, which allows
            for traversal via readTree (eg. hscloud.foo.bar.baz)
 - pkgs: nixpkgs
 - pkgsSrc: nixpkgs souce/channel, useful to load NixOS modules.
 - lib, stdenv: lib and stdenv from pkgs.

Change-Id: Ieaacdcabceec18dd6c670d346928bff08b66cf79
diff --git a/nix/readtree.nix b/nix/readtree.nix
new file mode 100644
index 0000000..066d326
--- /dev/null
+++ b/nix/readtree.nix
@@ -0,0 +1,96 @@
+# The MIT License (MIT)
+# 
+# Copyright (c) 2019 Vincent Ambo
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+{ ... }:
+
+args: initPath:
+
+let
+  inherit (builtins)
+    attrNames
+    baseNameOf
+    filter
+    hasAttr
+    head
+    isAttrs
+    length
+    listToAttrs
+    map
+    match
+    readDir
+    substring;
+
+  argsWithPath = 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);
+
+  # The marker is added to every set that was imported directly by
+  # readTree.
+  importWithMark = path: parts:
+    let imported = import path (argsWithPath parts);
+    in if (isAttrs imported)
+      then imported // { __readTree = true; }
+      else imported;
+
+  nixFileName = file:
+    let res = match "(.*)\.nix" file;
+    in if res == null then null else head res;
+
+  readTree = path: parts:
+    let
+      dir = readDirVisible path;
+      self = importWithMark path parts;
+      joinChild = c: path + ("/" + c);
+
+      # 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 (joinChild c) (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 p (parts ++ [ c ]);
+      }) nixFiles;
+    in if dir ? "default.nix"
+      then (if isAttrs self then self // (listToAttrs children) else self)
+      else listToAttrs (nixChildren ++ children);
+in readTree initPath [ (baseNameOf initPath) ]