readTree
========

This is a Nix program that builds up an attribute set tree for a large
repository based on the filesystem layout.

It is in fact the tool that lays out the attribute set of this repository.

As an example, consider a root (`.`) of a repository and a layout such as:

```
.
├── third_party
│   ├── default.nix
│   └── rustpkgs
│       ├── aho-corasick.nix
│       └── serde.nix
└── tools
    ├── cheddar
    │   └── default.nix
    └── roquefort.nix
```

When `readTree` is called on that tree, it will construct an attribute set with
this shape:

```nix
{
    tools = {
        cheddar = ...;
        roquefort = ...;
    };

    third_party = {
        # the `default.nix` of this folder might have had arbitrary other
        # attributes here, such as this:
        favouriteColour = "orange";

        rustpkgs = {
            aho-corasick = ...;
            serde = ...;
        };
    };
}
```

Every imported Nix file that yields an attribute set will have a `__readTree =
true;` attribute merged into it.

## Traversal logic

`readTree` will follow any subdirectories of a tree and import all Nix files,
with some exceptions:

* If a folder contains a `default.nix` file, no *sibling* Nix files will be
  imported - however children are traversed as normal.
* If a folder contains a `default.nix` it is loaded and, if it
  evaluates to a set, *merged* with the children. If it evaluates to
  anything other than a set, else the children are *not traversed*.
* A folder can opt out from readTree completely by containing a
  `.skip-tree` file. The content of the file is not read. These
  folders will be missing completely from the readTree structure.
* A folder can declare that its children are off-limit by containing a
  `.skip-subtree` file. Since the content of the file is not checked, it can be
  useful to leave a note for a human in the file.
* The `default.nix` of the top-level folder on which readTree is
  called is **not** read to avoid infinite recursion (as, presumably,
  this file is where readTree itself is called).

Traversal is lazy, `readTree` will only build up the tree as requested. This
currently has the downside that directories with no importable files end up in
the tree as empty nodes (`{}`).

## Import structure

`readTree` is called with an argument set containing a few parameters:

* `path`: Initial path at which to start the traversal.
* `args`: Arguments to pass to all imports.
* `filter`: (optional) A function to filter the argument set on each
  import based on the location in the tree. This can be used to, for
  example, implement a "visibility" system inside of a tree.
* `scopedArgs`: (optional) An argument set that is passed to all
  imported files via `builtins.scopedImport`. This will forcefully
  override the given values in the import scope, use with care!

The package headers in this repository follow the form `{ pkgs, ... }:` where
`pkgs` is a fixed-point of the entire package tree (see the `default.nix` at the
root of the depot).

In theory `readTree` can pass arguments of different shapes, but I have found
this to be a good solution for the most part.

Note that `readTree` does not currently make functions overridable, though it is
feasible that it could do that in the future.
