chore(lib): remove lib from labs
This commit is contained in:
parent
c30f94972c
commit
5bec48ef8a
59 changed files with 208 additions and 8096 deletions
README.md
foundation
lib
LICENSEREADME.mddefault.nixdefault.test.nixflake.nixtest.sh
src
attrs
bools
dag
default.nixdefault.test.nixerrors
fp
generators
importers
licenses
lists
math
modules
numbers
options
packages
paths
points
strings
types
versions
tidepool
|
@ -27,6 +27,5 @@ may collaborate.
|
|||
|
||||
| Name | Phase | Description |
|
||||
| ------------------------------ | --------- | ------------------------------------------------------------------------------------ |
|
||||
| [Aux Lib](./lib) | Iteration | A library of common functions used in the Aux ecosystem. |
|
||||
| [Aux Foundation](./foundation) | Iteration | Foundational packages which allow for bootstrapping a greater package set. |
|
||||
| [Aux Tidepool](./tidepool) | Idea | An initial package set built on top of Aux Foundation using Aux Lib's module system. |
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
system ? builtins.currentSystem,
|
||||
}:
|
||||
let
|
||||
lib = import ./../lib;
|
||||
pins = import ./npins;
|
||||
lib = import pins.lib;
|
||||
|
||||
modules = import ./src;
|
||||
|
||||
|
|
23
foundation/flake.lock
generated
23
foundation/flake.lock
generated
|
@ -1,27 +1,6 @@
|
|||
{
|
||||
"nodes": {
|
||||
"lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1723737980,
|
||||
"narHash": "sha256-1WnFatW5kSuO2jjt62hvSbH84TSYyO+VmvkJ0d5e/ZY=",
|
||||
"ref": "master",
|
||||
"rev": "cadfaabc853d20f2bc20bad794fcbe520ea48f13",
|
||||
"revCount": 82,
|
||||
"type": "git",
|
||||
"url": "file:../"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"type": "git",
|
||||
"url": "file:../"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"lib": "lib"
|
||||
}
|
||||
}
|
||||
"root": {}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
{
|
||||
description = "A set of foundational packages required for bootstrapping a larger package set.";
|
||||
|
||||
inputs = {
|
||||
lib = {
|
||||
url = "git+file:../?dir=lib";
|
||||
};
|
||||
};
|
||||
inputs = { };
|
||||
|
||||
outputs =
|
||||
inputs:
|
||||
_:
|
||||
let
|
||||
inherit (inputs.lib) lib;
|
||||
pins = import ./npins;
|
||||
lib = import pins.lib;
|
||||
|
||||
modules = import ./src;
|
||||
|
||||
|
@ -27,7 +24,7 @@
|
|||
system:
|
||||
let
|
||||
result = lib.modules.run {
|
||||
modules = (builtins.attrValues modules) ++ [ { config.aux.system = system; } ];
|
||||
modules = (builtins.attrValues modules) ++ [{ config.aux.system = system; }];
|
||||
};
|
||||
in
|
||||
result.config.exports.resolved.packages
|
||||
|
|
80
foundation/npins/default.nix
Normal file
80
foundation/npins/default.nix
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Generated by npins. Do not modify; will be overwritten regularly
|
||||
let
|
||||
data = builtins.fromJSON (builtins.readFile ./sources.json);
|
||||
version = data.version;
|
||||
|
||||
mkSource =
|
||||
spec:
|
||||
assert spec ? type;
|
||||
let
|
||||
path =
|
||||
if spec.type == "Git" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "GitRelease" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "PyPi" then
|
||||
mkPyPiSource spec
|
||||
else if spec.type == "Channel" then
|
||||
mkChannelSource spec
|
||||
else
|
||||
builtins.throw "Unknown source type ${spec.type}";
|
||||
in
|
||||
spec // { outPath = path; };
|
||||
|
||||
mkGitSource =
|
||||
{
|
||||
repository,
|
||||
revision,
|
||||
url ? null,
|
||||
hash,
|
||||
branch ? null,
|
||||
...
|
||||
}:
|
||||
assert repository ? type;
|
||||
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
|
||||
# In the latter case, there we will always be an url to the tarball
|
||||
if url != null then
|
||||
(builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash; # FIXME: check nix version & use SRI hashes
|
||||
})
|
||||
else
|
||||
assert repository.type == "Git";
|
||||
let
|
||||
urlToName =
|
||||
url: rev:
|
||||
let
|
||||
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
|
||||
|
||||
short = builtins.substring 0 7 rev;
|
||||
|
||||
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
|
||||
in
|
||||
"${if matched == null then "source" else builtins.head matched}${appendShort}";
|
||||
name = urlToName repository.url revision;
|
||||
in
|
||||
builtins.fetchGit {
|
||||
url = repository.url;
|
||||
rev = revision;
|
||||
inherit name;
|
||||
# hash = hash;
|
||||
};
|
||||
|
||||
mkPyPiSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchurl {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkChannelSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
in
|
||||
if version == 3 then
|
||||
builtins.mapAttrs (_: mkSource) data.pins
|
||||
else
|
||||
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
|
16
foundation/npins/sources.json
Normal file
16
foundation/npins/sources.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"pins": {
|
||||
"lib": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "Git",
|
||||
"url": "git+ssh://forgejo@git.auxolotl.org/auxolotl/lib.git"
|
||||
},
|
||||
"branch": "main",
|
||||
"revision": "7552ab48bb394d59d2bf1f7a558d28ce59da524d",
|
||||
"url": null,
|
||||
"hash": "0705fm00k9f95b6idf5qnfvqm4qf1a0cv966ghgd48kd1qy4il5c"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
24
lib/LICENSE
24
lib/LICENSE
|
@ -1,24 +0,0 @@
|
|||
MIT License
|
||||
Copyright (c) 2003-2023 Eelco Dolstra and the Nixpkgs/NixOS contributors
|
||||
Copyright (c) 2017-2023 Home Manager contributors
|
||||
Copyright (c) 2024 Aux Contributors
|
||||
|
||||
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
|
||||
(including the next paragraph) 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.
|
132
lib/README.md
132
lib/README.md
|
@ -1,132 +0,0 @@
|
|||
# Aux Lib
|
||||
|
||||
Aux Lib is intended to be a replacement for NixPkg's `lib` with stronger constraints around naming,
|
||||
organization, and inclusion of functions. In addition to replacing library functions, Aux Lib also
|
||||
defines a revamped version of the NixOS Module system intended to make it easier, more approachable,
|
||||
and intuitive.
|
||||
|
||||
## Usage
|
||||
|
||||
The library can be imported both with and without Nix Flakes. To import the library using Nix Flakes,
|
||||
add this repository as an input.
|
||||
|
||||
```nix
|
||||
inputs.lib.url = "https://git.auxolotl.org/auxolotl/labs/archive/main.tar.gz?dir=lib";
|
||||
```
|
||||
|
||||
To import the library without using Nix Flakes, you will need to use `fetchTarball` and import the
|
||||
library entrypoint.
|
||||
|
||||
```nix
|
||||
let
|
||||
labs = builtins.fetchTarball {
|
||||
url = "https://git.auxolotl.org/auxolotl/labs/archive/main.tar.gz";
|
||||
sha256 = "<sha256>";
|
||||
};
|
||||
lib = import "${labs}/lib";
|
||||
in
|
||||
# ...
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To contribute to the project, we accept pull requests to this repository. Please see the following
|
||||
sections for information on the appropriate practices and required steps for working on Aux Lib.
|
||||
|
||||
### Documentation
|
||||
|
||||
We want our code to survive in the world, but without proper documentation that won't happen. In
|
||||
order to not lose knowledge and also make it easier for others to begin participating in the
|
||||
project we require that every function have an appropriate documentation comment. The format for
|
||||
these comments is as follows:
|
||||
|
||||
```nix
|
||||
let
|
||||
## This is a description of what the function does. Any necessary information can be added
|
||||
## here. After this point comes a gap before a Hindley-Milner style type signature. Note
|
||||
## that these types are not actually checked, but serve as a helpful addition for the user
|
||||
## in addition to being provided in generated documentation.
|
||||
##
|
||||
## @type Int -> String
|
||||
func = x: builtins.toString x;
|
||||
in
|
||||
# ...
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
All functions that are added to the project should include tests. The test suites are located
|
||||
next to their implementation in files ending in `.test.nix`. These tests should ensure that
|
||||
the library behaves as expected. The typical structure of these test suites is:
|
||||
|
||||
```nix
|
||||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"my function" = {
|
||||
"test 1" = let
|
||||
expected = 1;
|
||||
input = {};
|
||||
actual = lib.myFunction input;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Successful tests will return `true` while failing test will resolve with `false`. You can run
|
||||
all tests with the following command:
|
||||
|
||||
```shell
|
||||
./test.sh
|
||||
```
|
||||
|
||||
If you want to run a specific test suite, you can run the command, specifying the directory
|
||||
to the tests file:
|
||||
|
||||
```shell
|
||||
./test.sh $namespace
|
||||
```
|
||||
|
||||
For example, to run the tests for only `attrs`, use the following command:
|
||||
|
||||
```shell
|
||||
./test.sh attrs
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
> **Note:** To keep this flake light and keep its inputs empty we do not include a package
|
||||
> set which would provide a formatter. Instead please run `nix run nixpkgs#nixfmt-rfc-style`
|
||||
> until an improved solution is available.
|
||||
|
||||
All code in this project must be formatted using the provided formatter in the `flake.nix`
|
||||
file. You can run this formatter using the command `nix fmt` (not currently available).
|
||||
|
||||
### Code Quality
|
||||
|
||||
In order to keep the project approachable and easy to maintain, certain patterns are not allowed.
|
||||
In particular, the use of `with` and `rec` are not allowed. Additionally, you should prefer the
|
||||
fully qualified name of a variable rather than creating intermediate ones using `inherit`.
|
||||
|
||||
### Adding Functionality
|
||||
|
||||
Before adding new features to the library, submit an issue or talk the idea over with one or
|
||||
more of the project maintainers. We want to make sure that the library does not become bloated
|
||||
with tools that aren't used. Some features may be better handled in a separate project. If you
|
||||
do get the go-ahead to begin working on your feature, please place it in the library structure
|
||||
similarly to how existing features are. For example, things dealing with strings should go in
|
||||
`src/strings/default.nix`.
|
||||
|
||||
Additionally, you should prefer to group things in attribute sets for like-functionality. More
|
||||
broad categories such as `strings` and `lists` are helpful, but scoped groups for things like
|
||||
`into`, `from`, and `validate` also make the library more discoverable. Having all of the
|
||||
different parts of the library mirroring this organizational structure makes building intuition
|
||||
for working with the library much easier. To know when to group new things, consider the
|
||||
following:
|
||||
|
||||
- Would your function name be multiple words like `fromString`?
|
||||
- Are there multiple variants of this function?
|
||||
- Would it be easier to find in a group?
|
||||
- Would grouping help avoid name collisions or confusion?
|
|
@ -1 +0,0 @@
|
|||
import ./src
|
|
@ -1,100 +0,0 @@
|
|||
let
|
||||
lib = import ./default.nix;
|
||||
|
||||
root = ./.;
|
||||
|
||||
files = [
|
||||
./src/default.test.nix
|
||||
./src/attrs/default.test.nix
|
||||
./src/bools/default.test.nix
|
||||
./src/dag/default.test.nix
|
||||
./src/errors/default.test.nix
|
||||
./src/fp/default.test.nix
|
||||
./src/generators/default.test.nix
|
||||
./src/importers/default.test.nix
|
||||
./src/lists/default.test.nix
|
||||
./src/math/default.test.nix
|
||||
./src/modules/default.test.nix
|
||||
./src/numbers/default.test.nix
|
||||
./src/options/default.test.nix
|
||||
./src/packages/default.test.nix
|
||||
./src/paths/default.test.nix
|
||||
./src/points/default.test.nix
|
||||
./src/strings/default.test.nix
|
||||
./src/types/default.test.nix
|
||||
./src/versions/default.test.nix
|
||||
];
|
||||
|
||||
resolve =
|
||||
file:
|
||||
let
|
||||
imported = import file;
|
||||
value = if builtins.isFunction imported then imported { inherit lib; } else imported;
|
||||
relative = lib.strings.removePrefix (builtins.toString root) (builtins.toString file);
|
||||
in
|
||||
{
|
||||
inherit file value;
|
||||
relative = if lib.strings.hasPrefix "/" relative then "." + relative else relative;
|
||||
namespace = getNamespace file;
|
||||
};
|
||||
|
||||
resolved = builtins.map resolve files;
|
||||
|
||||
getNamespace =
|
||||
path:
|
||||
let
|
||||
relative = lib.strings.removePrefix (builtins.toString root) (builtins.toString path);
|
||||
parts = lib.strings.split "/" relative;
|
||||
in
|
||||
if builtins.length parts > 2 then builtins.elemAt parts 2 else relative;
|
||||
|
||||
results =
|
||||
let
|
||||
getTests =
|
||||
file: prefix: suite:
|
||||
let
|
||||
nested = lib.attrs.mapToList (name: value: getTests file (prefix ++ [ name ]) value) suite;
|
||||
relative = lib.strings.removePrefix (builtins.toString root) (builtins.toString file);
|
||||
in
|
||||
if builtins.isAttrs suite then
|
||||
builtins.concatLists nested
|
||||
else
|
||||
[
|
||||
{
|
||||
inherit prefix file;
|
||||
name = builtins.concatStringsSep " > " prefix;
|
||||
value = suite;
|
||||
relative = if lib.strings.hasPrefix "/" relative then "." + relative else relative;
|
||||
}
|
||||
];
|
||||
|
||||
base = builtins.map (entry: getTests entry.file [ entry.namespace ] entry.value) resolved;
|
||||
in
|
||||
builtins.concatLists base;
|
||||
|
||||
successes = builtins.filter (test: test.value) results;
|
||||
failures = builtins.filter (test: !test.value) results;
|
||||
|
||||
total = "${builtins.toString (builtins.length successes)} / ${builtins.toString (builtins.length results)}";
|
||||
in
|
||||
if failures == [ ] then
|
||||
let
|
||||
message = lib.strings.concatMapSep "\n" (test: "✅ ${test.name}") successes;
|
||||
in
|
||||
''
|
||||
SUCCESS (${total})
|
||||
|
||||
${message}
|
||||
''
|
||||
else
|
||||
let
|
||||
successMessage = lib.strings.concatMapSep "\n" (test: "✅ ${test.name}") successes;
|
||||
failureMessage = lib.strings.concatMapSep "\n\n" (
|
||||
test: "❎ ${test.name}\n" + " -> ${test.relative}"
|
||||
) failures;
|
||||
in
|
||||
''
|
||||
FAILURE (${total})
|
||||
|
||||
${failureMessage}
|
||||
''
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
description = "A NixPkgs library replacement containing helper functions and a module system.";
|
||||
|
||||
outputs = _: { lib = import ./src; };
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
lib: {
|
||||
attrs = {
|
||||
## Merge two attribute sets at the base level.
|
||||
##
|
||||
## @type Attrs a b c => a -> b -> c
|
||||
merge = x: y: x // y;
|
||||
|
||||
## Merge two attribute sets recursively until a given predicate returns true.
|
||||
## Any values that are _not_ attribute sets will be overridden with the value
|
||||
## from `y` first if it exists and then `x` otherwise.
|
||||
##
|
||||
## @type Attrs a b c => (String -> Any -> Any -> Bool) -> a -> b -> c
|
||||
mergeRecursiveUntil =
|
||||
predicate: x: y:
|
||||
let
|
||||
process =
|
||||
path:
|
||||
builtins.zipAttrsWith (
|
||||
name: values:
|
||||
let
|
||||
currentPath = path ++ [ name ];
|
||||
isSingleValue = builtins.length values == 1;
|
||||
isComplete = predicate currentPath (builtins.elemAt values 1) (builtins.elemAt values 0);
|
||||
in
|
||||
if isSingleValue then
|
||||
builtins.elemAt values 0
|
||||
else if isComplete then
|
||||
builtins.elemAt values 1
|
||||
else
|
||||
process currentPath values
|
||||
);
|
||||
in
|
||||
process
|
||||
[ ]
|
||||
[
|
||||
x
|
||||
y
|
||||
];
|
||||
|
||||
## Merge two attribute sets recursively. Any values that are _not_ attribute sets
|
||||
## will be overridden with the value from `y` first if it exists and then `x`
|
||||
## otherwise.
|
||||
##
|
||||
## @type Attrs a b c => a -> b -> c
|
||||
mergeRecursive = lib.attrs.mergeRecursiveUntil (
|
||||
path: x: y:
|
||||
!(builtins.isAttrs x && builtins.isAttrs y)
|
||||
);
|
||||
|
||||
## Get a value from an attribute set by a path. If the path does not exist,
|
||||
## a fallback value will be returned instead.
|
||||
##
|
||||
## @type (List String) -> a -> Attrs -> a | b
|
||||
select =
|
||||
path: fallback: target:
|
||||
let
|
||||
name = builtins.head path;
|
||||
rest = builtins.tail path;
|
||||
in
|
||||
if path == [ ] then
|
||||
target
|
||||
else if target ? ${name} then
|
||||
lib.attrs.select rest fallback target.${name}
|
||||
else
|
||||
fallback;
|
||||
|
||||
## Get a value from an attribute set by a path. If the path does not exist,
|
||||
## an error will be thrown.
|
||||
##
|
||||
## @type (List String) -> Attrs -> a
|
||||
selectOrThrow =
|
||||
path: target:
|
||||
let
|
||||
pathAsString = builtins.concatStringsSep "." path;
|
||||
error = builtins.throw "Path not found in attribute set: ${pathAsString}";
|
||||
in
|
||||
if lib.attrs.has path target then lib.attrs.select path null target else error;
|
||||
|
||||
## Zip specific attributes from a list of attribute sets.
|
||||
##
|
||||
## @type List String -> (List Any -> Any) -> List Attrs -> Attrs
|
||||
zipWithNames =
|
||||
names: f: list:
|
||||
let
|
||||
transform = name: {
|
||||
inherit name;
|
||||
value = f name (builtins.catAttrs name list);
|
||||
};
|
||||
results = builtins.map transform names;
|
||||
in
|
||||
builtins.listToAttrs results;
|
||||
|
||||
## Match an attribute set against a pattern.
|
||||
##
|
||||
## @type Attrs -> Attrs -> Bool
|
||||
match =
|
||||
pattern: value:
|
||||
let
|
||||
process =
|
||||
name: values:
|
||||
let
|
||||
first = builtins.elemAt values 0;
|
||||
second = builtins.elemAt values 1;
|
||||
in
|
||||
if builtins.length values == 1 then
|
||||
false
|
||||
else if builtins.isAttrs first then
|
||||
builtins.isAttrs second && lib.attrs.match first second
|
||||
else
|
||||
first == second;
|
||||
|
||||
result = lib.attrs.zipWithNames (builtins.attrNames pattern) process [
|
||||
pattern
|
||||
value
|
||||
];
|
||||
in
|
||||
assert lib.errors.trace (builtins.isAttrs pattern) "Pattern must be an attribute set";
|
||||
assert lib.errors.trace (builtins.isAttrs value) "Value must be an attribute set";
|
||||
builtins.all lib.fp.id (builtins.attrValues result);
|
||||
|
||||
## Create a nested attribute set with a value as the leaf node.
|
||||
##
|
||||
## @type (List String) -> a -> Attrs
|
||||
set =
|
||||
path: value:
|
||||
let
|
||||
length = builtins.length path;
|
||||
process =
|
||||
depth: if depth == length then value else { ${builtins.elemAt path depth} = process (depth + 1); };
|
||||
in
|
||||
process 0;
|
||||
|
||||
## Check if a path exists in an attribute set.
|
||||
##
|
||||
## @type (List String) -> Attrs -> Bool
|
||||
has =
|
||||
path: target:
|
||||
let
|
||||
name = builtins.head path;
|
||||
rest = builtins.tail path;
|
||||
in
|
||||
if path == [ ] then
|
||||
true
|
||||
else if target ? ${name} then
|
||||
lib.attrs.has rest target.${name}
|
||||
else
|
||||
false;
|
||||
|
||||
## Depending on a given condition, either use the given value or an empty
|
||||
## attribute set.
|
||||
##
|
||||
## @type Attrs a b => Bool -> a -> a | b
|
||||
when = condition: value: if condition then value else { };
|
||||
|
||||
## Map an attribute set's names and values to a list.
|
||||
##
|
||||
## @type Any a => (String -> Any -> a) -> Attrs -> List a
|
||||
mapToList = f: target: builtins.map (name: f name target.${name}) (builtins.attrNames target);
|
||||
|
||||
## Map an attribute set recursively. Only non-set leaf nodes will be mapped.
|
||||
##
|
||||
## @type (List String -> Any -> Any) -> Attrs -> Attrs
|
||||
mapRecursive = f: target: lib.attrs.mapRecursiveWhen (lib.fp.const true) f target;
|
||||
|
||||
## Map an attribute set recursively when a given predicate returns true.
|
||||
## Only leaf nodes according to the predicate will be mapped.
|
||||
##
|
||||
## @type (Attrs -> Bool) -> (List String -> Any -> Any) -> Attrs -> Attrs
|
||||
mapRecursiveWhen =
|
||||
predicate: f: target:
|
||||
let
|
||||
process =
|
||||
path:
|
||||
builtins.mapAttrs (
|
||||
name: value:
|
||||
if builtins.isAttrs value && predicate value then
|
||||
process (path ++ [ name ]) value
|
||||
else
|
||||
f (path ++ [ name ]) value
|
||||
);
|
||||
in
|
||||
process [ ] target;
|
||||
|
||||
## Filter an attribute set by a given predicate. The filter is only performed
|
||||
## on the base level of the attribute set.
|
||||
##
|
||||
## @type (String -> Any -> Bool) -> Attrs -> Attrs
|
||||
filter =
|
||||
predicate: target:
|
||||
let
|
||||
names = builtins.attrNames target;
|
||||
process =
|
||||
name:
|
||||
let
|
||||
value = target.${name};
|
||||
in
|
||||
if predicate name value then [ { inherit name value; } ] else [ ];
|
||||
valid = builtins.concatMap process names;
|
||||
in
|
||||
builtins.listToAttrs valid;
|
||||
|
||||
## Generate an attribute set from a list of names and a function that is
|
||||
## applied to each name.
|
||||
##
|
||||
## @type (List String) -> (String -> Any) -> Attrs
|
||||
generate =
|
||||
names: f:
|
||||
let
|
||||
pairs = builtins.map (name: {
|
||||
inherit name;
|
||||
value = f name;
|
||||
}) names;
|
||||
in
|
||||
builtins.listToAttrs pairs;
|
||||
};
|
||||
}
|
|
@ -1,342 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"merge" = {
|
||||
"merges two shallow sets" =
|
||||
let
|
||||
expected = {
|
||||
x = 1;
|
||||
y = 2;
|
||||
};
|
||||
|
||||
actual = lib.attrs.merge { x = 1; } { y = 2; };
|
||||
in
|
||||
expected == actual;
|
||||
|
||||
"overwrites values from the first set" =
|
||||
let
|
||||
expected = {
|
||||
x = 2;
|
||||
};
|
||||
actual = lib.attrs.merge { x = 1; } { x = 2; };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"does not merge nested sets" =
|
||||
let
|
||||
expected = {
|
||||
x.y = 2;
|
||||
};
|
||||
actual = lib.attrs.merge { x.z = 1; } { x.y = 2; };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"mergeRecursiveUntil" = {
|
||||
"merges with predicate" =
|
||||
let
|
||||
expected = {
|
||||
x.y.z = 1;
|
||||
};
|
||||
actual = lib.attrs.mergeRecursiveUntil (
|
||||
path: x: y:
|
||||
lib.lists.last path == "z"
|
||||
) { x.y.z = 2; } { x.y.z = 1; };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles shallow merges" =
|
||||
let
|
||||
expected = {
|
||||
x.y.z = 1;
|
||||
};
|
||||
actual =
|
||||
lib.attrs.mergeRecursiveUntil
|
||||
(
|
||||
path: x: y:
|
||||
true
|
||||
)
|
||||
{
|
||||
x = {
|
||||
y.z = 2;
|
||||
|
||||
a = false;
|
||||
};
|
||||
}
|
||||
{ x.y.z = 1; };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"mergeRecursive" = {
|
||||
"merges two sets deeply" =
|
||||
let
|
||||
expected = {
|
||||
x.y.z = 1;
|
||||
};
|
||||
actual = lib.attrs.mergeRecursive { x.y.z = 2; } { x.y.z = 1; };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"select" = {
|
||||
"selects a nested value" =
|
||||
let
|
||||
expected = "value";
|
||||
actual = lib.attrs.select [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] null { x.y.z = expected; };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles empty path" =
|
||||
let
|
||||
expected = {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
actual = lib.attrs.select [ ] null {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles fallback value" =
|
||||
let
|
||||
expected = "fallback";
|
||||
actual = lib.attrs.select [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] expected { };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"selectOrThrow" = {
|
||||
"selects a nested value" =
|
||||
let
|
||||
expected = "value";
|
||||
actual = lib.attrs.selectOrThrow [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] { x.y.z = expected; };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles empty path" =
|
||||
let
|
||||
expected = {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
actual = lib.attrs.selectOrThrow [ ] {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"throws on nonexistent path" =
|
||||
let
|
||||
actual = lib.attrs.selectOrThrow [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] { };
|
||||
|
||||
evaluated = builtins.tryEval (builtins.deepSeq actual actual);
|
||||
in
|
||||
!evaluated.success;
|
||||
};
|
||||
|
||||
"set" = {
|
||||
"creates a nested set" =
|
||||
let
|
||||
expected = {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
actual = lib.attrs.set [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] 1;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles empty path" =
|
||||
let
|
||||
expected = 1;
|
||||
actual = lib.attrs.set [ ] 1;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"has" = {
|
||||
"returns true for a nested value" =
|
||||
let
|
||||
exists = lib.attrs.has [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] { x.y.z = 1; };
|
||||
in
|
||||
exists;
|
||||
|
||||
"returns false for a nonexistent value" =
|
||||
let
|
||||
exists = lib.attrs.has [
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
] { };
|
||||
in
|
||||
!exists;
|
||||
|
||||
"handles empty path" =
|
||||
let
|
||||
exists = lib.attrs.has [ ] { };
|
||||
in
|
||||
exists;
|
||||
};
|
||||
|
||||
"when" = {
|
||||
"returns the value when condition is true" =
|
||||
let
|
||||
expected = "value";
|
||||
actual = lib.attrs.when true expected;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns an empty set when condition is false" =
|
||||
let
|
||||
expected = { };
|
||||
actual = lib.attrs.when false "value";
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"mapToList" = {
|
||||
"converts a set to a list" =
|
||||
let
|
||||
expected = [
|
||||
{
|
||||
name = "x";
|
||||
value = 1;
|
||||
}
|
||||
{
|
||||
name = "y";
|
||||
value = 2;
|
||||
}
|
||||
];
|
||||
actual = lib.attrs.mapToList (name: value: { inherit name value; }) {
|
||||
x = 1;
|
||||
y = 2;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"mapRecursiveWhen" = {
|
||||
"maps a set recursively" =
|
||||
let
|
||||
expected = {
|
||||
x = {
|
||||
y = {
|
||||
z = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
actual = lib.attrs.mapRecursiveWhen (value: true) (path: value: value + 1) {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"maps a set given a condition" =
|
||||
let
|
||||
expected = {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
actual =
|
||||
lib.attrs.mapRecursiveWhen (value: !(value ? z))
|
||||
(
|
||||
path: value:
|
||||
# We map before we get to a non-set value
|
||||
if builtins.isAttrs value then value else value + 1
|
||||
)
|
||||
{
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"mapRecursive" = {
|
||||
"maps a set recursively" =
|
||||
let
|
||||
expected = {
|
||||
x = {
|
||||
y = {
|
||||
z = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
actual = lib.attrs.mapRecursive (path: value: value + 1) {
|
||||
x = {
|
||||
y = {
|
||||
z = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"filter" = {
|
||||
"filters a set" =
|
||||
let
|
||||
expected = {
|
||||
y = 2;
|
||||
};
|
||||
actual = lib.attrs.filter (name: value: name == "y") {
|
||||
x = 1;
|
||||
y = 2;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
lib: {
|
||||
bools = {
|
||||
into = {
|
||||
## Convert a boolean value into a string.
|
||||
##
|
||||
## @type Bool -> String
|
||||
string = value: if value then "true" else "false";
|
||||
|
||||
## Convert a boolean into either the string "yes" or "no".
|
||||
##
|
||||
## @type Bool -> String
|
||||
yesno = value: if value then "yes" else "no";
|
||||
};
|
||||
|
||||
## Choose between two values based on a condition. When true, the first value
|
||||
## is returned, otherwise the second value is returned.
|
||||
##
|
||||
## @type Bool -> a -> b -> a | b
|
||||
when =
|
||||
condition: x: y:
|
||||
if condition then x else y;
|
||||
|
||||
## Perform a logical AND operation on two values.
|
||||
##
|
||||
## @type Bool -> Bool -> Bool
|
||||
and = a: b: a && b;
|
||||
|
||||
## Perform a logical AND operation on two functions being applied to a value.
|
||||
##
|
||||
## @type (a -> Bool) -> (a -> Bool) -> a -> Bool
|
||||
and' = f: g: (x: (f x) && (g x));
|
||||
|
||||
## Perform a logical OR operation on two values.
|
||||
##
|
||||
## @type Bool -> Bool -> Bool
|
||||
or = a: b: a || b;
|
||||
|
||||
## Perform a logical OR operation on two functions being applied to a value.
|
||||
##
|
||||
## @type (a -> Bool) -> (a -> Bool) -> a -> Bool
|
||||
or' = f: g: (x: (f x) || (g x));
|
||||
|
||||
## Perform a logical NOT operation on a value.
|
||||
##
|
||||
## @type Bool -> Bool
|
||||
not = a: !a;
|
||||
};
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"into" = {
|
||||
"string" = {
|
||||
"handles true" =
|
||||
let
|
||||
expected = "true";
|
||||
actual = lib.bools.into.string true;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles false" =
|
||||
let
|
||||
expected = "false";
|
||||
actual = lib.bools.into.string false;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"yesno" = {
|
||||
"handles true" =
|
||||
let
|
||||
expected = "yes";
|
||||
actual = lib.bools.into.yesno true;
|
||||
in
|
||||
actual == expected;
|
||||
"handles false" =
|
||||
let
|
||||
expected = "no";
|
||||
actual = lib.bools.into.yesno false;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"when" = {
|
||||
"returns first value when true" =
|
||||
let
|
||||
expected = "foo";
|
||||
actual = lib.bools.when true expected "bar";
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns second value when false" =
|
||||
let
|
||||
expected = "bar";
|
||||
actual = lib.bools.when false "foo" expected;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"and" = {
|
||||
"returns true when both are true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.and true true;
|
||||
in
|
||||
actual == expected;
|
||||
"returns false when first is false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.and false true;
|
||||
in
|
||||
actual == expected;
|
||||
"returns false when second is false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.and true false;
|
||||
in
|
||||
actual == expected;
|
||||
"returns false when both are false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.and false false;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"and'" =
|
||||
let
|
||||
getTrue = _: true;
|
||||
getFalse = _: false;
|
||||
in
|
||||
{
|
||||
"returns true when both are true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.and' getTrue getTrue null;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns false when first is false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.and' getFalse getTrue null;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns false when second is false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.and' getTrue getFalse null;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns false when both are false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.and' getFalse getFalse null;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"or" = {
|
||||
"returns true when both are true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.or true true;
|
||||
in
|
||||
actual == expected;
|
||||
"returns true when first is true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.or true false;
|
||||
in
|
||||
actual == expected;
|
||||
"returns true when second is true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.or false true;
|
||||
in
|
||||
actual == expected;
|
||||
"returns false when both are false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.or false false;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"or'" =
|
||||
let
|
||||
getTrue = _: true;
|
||||
getFalse = _: false;
|
||||
in
|
||||
{
|
||||
"returns true when both are true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.or' getTrue getTrue null;
|
||||
in
|
||||
actual == expected;
|
||||
"returns true when first is true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.or' getTrue getFalse null;
|
||||
in
|
||||
actual == expected;
|
||||
"returns true when second is true" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.or' getFalse getTrue null;
|
||||
in
|
||||
actual == expected;
|
||||
"returns false when both are false" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.or' getFalse getFalse null;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"not" = {
|
||||
"returns false when true" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.bools.not true;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns true when false" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.bools.not false;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
lib: {
|
||||
dag = {
|
||||
validate = {
|
||||
## Check that a value is a DAG entry.
|
||||
##
|
||||
## @type a -> Bool
|
||||
entry = value: (value ? value) && (value ? before) && (value ? after);
|
||||
|
||||
## Check that a value is a DAG.
|
||||
##
|
||||
## @type a -> Bool
|
||||
graph =
|
||||
value:
|
||||
let
|
||||
isContentsValid = builtins.all lib.dag.validate.entry (builtins.attrValues value);
|
||||
in
|
||||
builtins.isAttrs value && isContentsValid;
|
||||
};
|
||||
|
||||
sort = {
|
||||
## Apply a topological sort to a DAG.
|
||||
##
|
||||
## @type Dag a -> { result :: List a } | { cycle :: List a, loops :: List a }
|
||||
topological =
|
||||
graph:
|
||||
let
|
||||
getEntriesBefore =
|
||||
graph: name:
|
||||
let
|
||||
before = lib.attrs.filter (key: value: builtins.elem name value.before) graph;
|
||||
in
|
||||
builtins.attrNames before;
|
||||
|
||||
normalize = name: value: {
|
||||
inherit name;
|
||||
value = value.value;
|
||||
after = value.after ++ (getEntriesBefore graph name);
|
||||
};
|
||||
|
||||
normalized = builtins.mapAttrs normalize graph;
|
||||
|
||||
entries = builtins.attrValues normalized;
|
||||
|
||||
isBefore = a: b: builtins.elem a.name b.after;
|
||||
|
||||
sorted = lib.lists.sort.topological isBefore entries;
|
||||
in
|
||||
if sorted ? result then
|
||||
{
|
||||
result = builtins.map (value: {
|
||||
name = value.name;
|
||||
value = value.value;
|
||||
}) sorted.result;
|
||||
}
|
||||
else
|
||||
sorted;
|
||||
};
|
||||
|
||||
apply = {
|
||||
## Apply a set of defaults to a graph. This will ensure that missing entries are added
|
||||
## and any entries that do exist are given the appropriate `before` and `after` values.
|
||||
##
|
||||
## @type Dag a -> Dag a -> Dag a
|
||||
defaults =
|
||||
graph: defaults:
|
||||
let
|
||||
result = builtins.mapAttrs (
|
||||
name: entry:
|
||||
if defaults ? ${name} then
|
||||
if builtins.isString entry then
|
||||
{
|
||||
value = entry;
|
||||
before = defaults.${name}.before or [ ];
|
||||
after = defaults.${name}.after or [ ];
|
||||
}
|
||||
else
|
||||
entry
|
||||
// {
|
||||
before = (entry.before or [ ]) ++ (defaults.${name}.before or [ ]);
|
||||
after = (entry.after or [ ]) ++ (defaults.${name}.after or [ ]);
|
||||
}
|
||||
else
|
||||
entry
|
||||
) graph;
|
||||
in
|
||||
defaults // result;
|
||||
};
|
||||
|
||||
## Map over the entries in a DAG and modify their values.
|
||||
##
|
||||
## @type (String -> a -> b) -> Dag a -> Dag b
|
||||
map = f: builtins.mapAttrs (name: value: value // { value = f name value.value; });
|
||||
|
||||
entry = {
|
||||
## Create a new DAG entry.
|
||||
##
|
||||
## @type List String -> List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
between = after: before: value: { inherit before after value; };
|
||||
|
||||
## Create a new DAG entry with no dependencies.
|
||||
##
|
||||
## @type a -> { before :: List String, after :: List String, value :: a }
|
||||
anywhere = lib.dag.entry.between [ ] [ ];
|
||||
|
||||
## Create a new DAG entry that occurs before other entries.
|
||||
##
|
||||
## @type List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
before = before: lib.dag.entry.between [ ] before;
|
||||
|
||||
## Create a new DAG entry that occurs after other entries.
|
||||
##
|
||||
## @type List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
after = after: lib.dag.entry.between after [ ];
|
||||
};
|
||||
|
||||
entries = {
|
||||
## Create a DAG from a list of entries, prefixed with a tag.
|
||||
##
|
||||
## @type String -> List String -> List String -> List a -> Dag a
|
||||
between =
|
||||
tag:
|
||||
let
|
||||
process =
|
||||
i: after: before: entries:
|
||||
let
|
||||
name = "${tag}-${builtins.toString i}";
|
||||
entry = builtins.head entries;
|
||||
rest = builtins.tail entries;
|
||||
in
|
||||
if builtins.length entries == 0 then
|
||||
{ }
|
||||
else if builtins.length entries == 1 then
|
||||
{ "${name}" = lib.dag.entry.between after before entry; }
|
||||
else
|
||||
{ "${name}" = lib.dag.entry.after after entry; } // (process (i + 1) before [ name ] rest);
|
||||
in
|
||||
process 0;
|
||||
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that can occur anywhere.
|
||||
##
|
||||
## @type String -> List a -> Dag a
|
||||
anywhere = tag: lib.dag.entries.between tag [ ] [ ];
|
||||
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that occurs before other entries.
|
||||
##
|
||||
## @type String -> List String -> List a -> Dag a
|
||||
before = tag: before: lib.dag.entries.between tag [ ] before;
|
||||
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that occurs after other entries.
|
||||
##
|
||||
## @type String -> List String -> List a -> Dag a
|
||||
after = tag: after: lib.dag.entries.between tag after [ ];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"validate" = {
|
||||
"entry" = {
|
||||
"invalid value" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.dag.validate.entry { };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"a manually created value" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry {
|
||||
value = null;
|
||||
before = [ ];
|
||||
after = [ ];
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.between" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.between [ ] [ ] null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.anywhere" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.anywhere null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.before" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.before [ ] null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.after" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.after [ ] null);
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"graph" = {
|
||||
"invalid value" =
|
||||
let
|
||||
expected = false;
|
||||
actual = lib.dag.validate.graph { x = { }; };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"a manually created value" =
|
||||
let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.graph {
|
||||
x = {
|
||||
value = null;
|
||||
before = [ ];
|
||||
after = [ ];
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.between" =
|
||||
let
|
||||
expected = true;
|
||||
graph =
|
||||
lib.dag.entries.between "example" [ ]
|
||||
[ ]
|
||||
[
|
||||
null
|
||||
null
|
||||
];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.anywhere" =
|
||||
let
|
||||
expected = true;
|
||||
graph = lib.dag.entries.anywhere "example" [
|
||||
null
|
||||
null
|
||||
];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.before" =
|
||||
let
|
||||
expected = true;
|
||||
graph =
|
||||
lib.dag.entries.before "example"
|
||||
[ ]
|
||||
[
|
||||
null
|
||||
null
|
||||
];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.after" =
|
||||
let
|
||||
expected = true;
|
||||
graph =
|
||||
lib.dag.entries.after "example"
|
||||
[ ]
|
||||
[
|
||||
null
|
||||
null
|
||||
];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"sort" = {
|
||||
"topological" = {
|
||||
"handles an empty graph" =
|
||||
let
|
||||
expected = [ ];
|
||||
actual = lib.dag.sort.topological { };
|
||||
in
|
||||
actual.result == expected;
|
||||
|
||||
"sorts a graph" =
|
||||
let
|
||||
expected = [
|
||||
{
|
||||
name = "a";
|
||||
value = "a";
|
||||
}
|
||||
{
|
||||
name = "b";
|
||||
value = "b";
|
||||
}
|
||||
{
|
||||
name = "c";
|
||||
value = "c";
|
||||
}
|
||||
{
|
||||
name = "d";
|
||||
value = "d";
|
||||
}
|
||||
];
|
||||
actual = lib.dag.sort.topological {
|
||||
a = lib.dag.entry.anywhere "a";
|
||||
b = lib.dag.entry.between [ "a" ] [ "c" ] "b";
|
||||
c = lib.dag.entry.before [ "c" ] "c";
|
||||
d = lib.dag.entry.after [ "c" ] "d";
|
||||
};
|
||||
in
|
||||
actual.result == expected;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
let
|
||||
files = [
|
||||
./attrs
|
||||
./bools
|
||||
./dag
|
||||
./errors
|
||||
./fp
|
||||
./generators
|
||||
./importers
|
||||
./licenses
|
||||
./lists
|
||||
./math
|
||||
./modules
|
||||
./numbers
|
||||
./options
|
||||
./packages
|
||||
./paths
|
||||
./points
|
||||
./strings
|
||||
./types
|
||||
./versions
|
||||
];
|
||||
|
||||
libs = builtins.map (f: import f) files;
|
||||
|
||||
## Calculate the fixed point of a function. This will evaluate the function `f`
|
||||
## until its result settles (or Nix's recursion limit is reached). This allows
|
||||
## us to define recursive functions without worrying about the order of their
|
||||
## definitions.
|
||||
##
|
||||
## @type (a -> a) -> a
|
||||
fix =
|
||||
f:
|
||||
let
|
||||
x = f x;
|
||||
in
|
||||
x;
|
||||
|
||||
## Merge two attribute sets recursively until a given predicate returns true.
|
||||
## Any values that are _not_ attribute sets will be overridden with the value
|
||||
## from `y` first if it exists and then `x` otherwise.
|
||||
##
|
||||
## @type Attrs a b c => (String -> Any -> Any -> Bool) -> a -> b -> c
|
||||
mergeAttrsRecursiveUntil =
|
||||
predicate: x: y:
|
||||
let
|
||||
process =
|
||||
path:
|
||||
builtins.zipAttrsWith (
|
||||
name: values:
|
||||
let
|
||||
currentPath = path ++ [ name ];
|
||||
isSingleValue = builtins.length values == 1;
|
||||
isComplete = predicate currentPath (builtins.elemAt values 1) (builtins.elemAt values 0);
|
||||
in
|
||||
if isSingleValue || isComplete then builtins.elemAt values 0 else process currentPath values
|
||||
);
|
||||
in
|
||||
process
|
||||
[ ]
|
||||
[
|
||||
x
|
||||
y
|
||||
];
|
||||
|
||||
## Merge two attribute sets recursively. Any values that are _not_ attribute sets
|
||||
## will be overridden with the value from `y` first if it exists and then `x`
|
||||
## otherwise.
|
||||
##
|
||||
## @type Attrs a b c => a -> b -> c
|
||||
mergeAttrsRecursive = mergeAttrsRecursiveUntil (
|
||||
path: x: y:
|
||||
!(builtins.isAttrs x && builtins.isAttrs y)
|
||||
);
|
||||
|
||||
lib = fix (
|
||||
self:
|
||||
let
|
||||
merge = acc: create: mergeAttrsRecursive acc (create self);
|
||||
in
|
||||
builtins.foldl' merge { } libs
|
||||
);
|
||||
in
|
||||
lib.points.withExtend (lib.fp.const lib)
|
|
@ -1,22 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"extend" = {
|
||||
"lib is extensible" =
|
||||
let
|
||||
result = lib.extend (
|
||||
final: prev:
|
||||
prev
|
||||
// {
|
||||
__injected__ = true;
|
||||
|
||||
fp = prev.fp // {
|
||||
__injected__ = true;
|
||||
};
|
||||
}
|
||||
);
|
||||
in
|
||||
result ? __injected__ && result.fp ? __injected__ && result.fp ? const;
|
||||
};
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
lib: {
|
||||
errors = {
|
||||
## Prints a message if the condition is not met. The result of
|
||||
## the condition is returned.
|
||||
##
|
||||
## @notest
|
||||
## @type Bool -> String -> Bool
|
||||
trace = condition: message: if condition then true else builtins.trace message false;
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
|
@ -1,64 +0,0 @@
|
|||
lib: {
|
||||
fp = {
|
||||
## A function that returns its argument.
|
||||
##
|
||||
## @type a -> a
|
||||
id = x: x;
|
||||
|
||||
## Create a function that ignores its argument and returns a constant value.
|
||||
##
|
||||
## @type a -> b -> a
|
||||
const = x: (_: x);
|
||||
|
||||
## Compose two functions to produce a new function that applies them both
|
||||
## from right to left.
|
||||
##
|
||||
## @type Function f g => (b -> c) -> (a -> b) -> a -> c
|
||||
compose = f: g: (x: f (g x));
|
||||
|
||||
## Process a value with a series of functions. Functions are applied in the
|
||||
## order they are provided.
|
||||
##
|
||||
## @type (List (Any -> Any)) -> Any -> Any
|
||||
pipe = fs: (x: builtins.foldl' (value: f: f value) x fs);
|
||||
|
||||
## Reverse the order of arguments to a function that has two parameters.
|
||||
##
|
||||
## @type (a -> b -> c) -> b -> a -> c
|
||||
flip2 =
|
||||
f: a: b:
|
||||
f b a;
|
||||
## Reverse the order of arguments to a function that has three parameters.
|
||||
##
|
||||
## @type (a -> b -> c -> d) -> c -> b -> a -> d
|
||||
flip3 =
|
||||
f: a: b: c:
|
||||
f c b a;
|
||||
## Reverse the order of arguments to a function that has four parameters.
|
||||
##
|
||||
## @type (a -> b -> c -> d -> e) -> d -> c -> b -> a -> e
|
||||
flip4 =
|
||||
f: a: b: c: d:
|
||||
f d c b a;
|
||||
|
||||
## Get the arguments of a function or functor.
|
||||
## An attribute set is returned with the arguments as keys. The values
|
||||
## are `true` when the argument has a default value specified and `false`
|
||||
## when it does not.
|
||||
##
|
||||
## @type Function -> Attrs
|
||||
args =
|
||||
f: if f ? __functor then f.__args__ or (lib.fp.args (f.__functor f)) else builtins.functionArgs f;
|
||||
|
||||
## Create a function that is called with only the arguments it specifies.
|
||||
##
|
||||
## @type Attrs a => (a -> b) -> a -> b
|
||||
withDynamicArgs =
|
||||
f: args:
|
||||
let
|
||||
fArgs = lib.fp.args f;
|
||||
common = builtins.intersectAttrs fArgs args;
|
||||
in
|
||||
if builtins.isAttrs args then if fArgs == { } then f args else f common else f args;
|
||||
};
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"id" = {
|
||||
"returns its argument" =
|
||||
let
|
||||
expected = "foo";
|
||||
actual = lib.fp.id expected;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"const" = {
|
||||
"creates a function that returns its argument" =
|
||||
let
|
||||
expected = "foo";
|
||||
actual = lib.fp.const expected "bar";
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"compose" = {
|
||||
"composes two functions" =
|
||||
let
|
||||
f = x: x + 1;
|
||||
g = x: x * 2;
|
||||
expected = 5;
|
||||
actual = lib.fp.compose f g 2;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"pipe" = {
|
||||
"pipes two functions" =
|
||||
let
|
||||
f = x: x + 1;
|
||||
g = x: x * 2;
|
||||
expected = 5;
|
||||
actual = lib.fp.pipe [
|
||||
g
|
||||
f
|
||||
] 2;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"flip2" = {
|
||||
"flips the arguments of a binary function" =
|
||||
let
|
||||
f = a: b: a - b;
|
||||
expected = 1;
|
||||
actual = lib.fp.flip2 f 1 2;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"flip3" = {
|
||||
"flips the arguments of a ternary function" =
|
||||
let
|
||||
f =
|
||||
a: b: c:
|
||||
a - b - c;
|
||||
expected = 0;
|
||||
actual = lib.fp.flip3 f 1 2 3;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"flip4" = {
|
||||
"flips the arguments of a quaternary function" =
|
||||
let
|
||||
f =
|
||||
a: b: c: d:
|
||||
a - b - c - d;
|
||||
expected = -2;
|
||||
actual = lib.fp.flip4 f 1 2 3 4;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"args" = {
|
||||
"gets a functions attr set arguments" =
|
||||
let
|
||||
expected = {
|
||||
x = false;
|
||||
y = true;
|
||||
};
|
||||
actual = lib.fp.args (
|
||||
{
|
||||
x,
|
||||
y ? null,
|
||||
}:
|
||||
null
|
||||
);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns an empty set if the function has no attrs arguments" =
|
||||
let
|
||||
expected = { };
|
||||
actual = lib.fp.args (args: null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"supports functors" =
|
||||
let
|
||||
expected = {
|
||||
x = false;
|
||||
y = true;
|
||||
};
|
||||
|
||||
actual = lib.fp.args {
|
||||
__functor =
|
||||
self:
|
||||
{
|
||||
x,
|
||||
y ? null,
|
||||
}:
|
||||
null;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"supports cached functor arguments" =
|
||||
let
|
||||
expected = {
|
||||
x = false;
|
||||
y = true;
|
||||
};
|
||||
actual = lib.fp.args {
|
||||
__args__ = {
|
||||
x = false;
|
||||
y = true;
|
||||
};
|
||||
__functor = self: args: null;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"withDynamicArgs" = {
|
||||
"applies a function with dynamic arguments" =
|
||||
let
|
||||
expected = {
|
||||
x = true;
|
||||
};
|
||||
actual = lib.fp.withDynamicArgs (args@{ x }: args) {
|
||||
x = true;
|
||||
y = true;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"applies all arguments if none are specified" =
|
||||
let
|
||||
expected = {
|
||||
x = true;
|
||||
y = true;
|
||||
};
|
||||
actual = lib.fp.withDynamicArgs (args: args) expected;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
lib: {
|
||||
generators = {
|
||||
## Limit evaluation of a valud to a certain depth.
|
||||
##
|
||||
## @type { limit? :: Int | Null, throw? :: Bool } -> a -> a
|
||||
withRecursion =
|
||||
{
|
||||
limit ? null,
|
||||
throw ? true,
|
||||
}:
|
||||
assert builtins.isInt limit;
|
||||
let
|
||||
special = [
|
||||
"__functor"
|
||||
"__functionArgs"
|
||||
"__toString"
|
||||
"__pretty__"
|
||||
];
|
||||
attr = next: name: if builtins.elem name special then lib.fp.id else next;
|
||||
transform =
|
||||
depth:
|
||||
if limit != null && depth > limit then
|
||||
if throw then
|
||||
builtins.throw "Exceeded maximum eval-depth limit of ${builtins.toString limit} while trying to evaluate with `lib.generators.withRecursion'!"
|
||||
else
|
||||
lib.fp.const "<unevaluated>"
|
||||
else
|
||||
lib.fp.id;
|
||||
process =
|
||||
depth: value:
|
||||
let
|
||||
next = x: process (depth + 1) (transform (depth + 1) x);
|
||||
in
|
||||
if builtins.isAttrs value then
|
||||
builtins.mapAttrs (attr next) value
|
||||
else if builtins.isList value then
|
||||
builtins.map next value
|
||||
else
|
||||
transform (depth + 1) value;
|
||||
in
|
||||
process 0;
|
||||
|
||||
## Create a pretty printer for Nix values.
|
||||
##
|
||||
## @type { indent? :: String, multiline? :: Bool, allowCustomPrettifiers? :: Bool } -> a -> string
|
||||
pretty =
|
||||
{
|
||||
indent ? "",
|
||||
multiline ? true,
|
||||
allowCustomPrettifiers ? false,
|
||||
}:
|
||||
let
|
||||
process =
|
||||
indent: value:
|
||||
let
|
||||
prefix = if multiline then "\n${indent} " else " ";
|
||||
suffix = if multiline then "\n${indent}" else " ";
|
||||
|
||||
prettyNull = "null";
|
||||
prettyNumber = lib.numbers.into.string value;
|
||||
prettyBool = lib.bools.into.string value;
|
||||
prettyPath = builtins.toString value;
|
||||
|
||||
prettyString =
|
||||
let
|
||||
lines = builtins.filter (x: !builtins.isList x) (builtins.split "\n" value);
|
||||
escapeSingleline = lib.strings.escape.any [
|
||||
"\\"
|
||||
"\""
|
||||
"\${"
|
||||
];
|
||||
escapeMultiline =
|
||||
builtins.replaceStrings
|
||||
[
|
||||
"\${"
|
||||
"''"
|
||||
]
|
||||
[
|
||||
"''\${"
|
||||
"'''"
|
||||
];
|
||||
singlelineResult = "\"" + lib.strings.concatMapSep "\\n" escapeSingleline lines + "\"";
|
||||
multilineResult =
|
||||
let
|
||||
escapedLines = builtins.map escapeMultiline lines;
|
||||
# The last line gets a special treatment: if it's empty, '' is on its own line at the "outer"
|
||||
# indentation level. Otherwise, '' is appended to the last line.
|
||||
lastLine = lib.lists.last escapedLines;
|
||||
contents = builtins.concatStringsSep prefix (lib.lists.init escapedLines);
|
||||
contentsSuffix = if lastLine == "" then suffix else prefix + lastLine;
|
||||
in
|
||||
"''" + prefix + contents + contentsSuffix + "''";
|
||||
in
|
||||
if multiline && builtins.length lines > 1 then multilineResult else singlelineResult;
|
||||
|
||||
prettyList =
|
||||
let
|
||||
contents = lib.strings.concatMapSep prefix (process (indent + " ")) value;
|
||||
in
|
||||
if builtins.length value == 0 then "[ ]" else "[${prefix}${contents}${suffix}]";
|
||||
|
||||
prettyFunction =
|
||||
let
|
||||
args = lib.fp.args value;
|
||||
markArgOptional = name: default: if default then name + "?" else name;
|
||||
argsWithDefaults = lib.attrs.mapToList markArgOptional args;
|
||||
serializedArgs = builtins.concatStringsSep ", " argsWithDefaults;
|
||||
in
|
||||
if args == { } then "<function>" else "<function, args: {${serializedArgs}}>";
|
||||
|
||||
prettyAttrs =
|
||||
let
|
||||
contents = builtins.concatStringsSep prefix (
|
||||
lib.attrs.mapToList (
|
||||
name: value:
|
||||
"${lib.strings.escape.nix.identifier name} = ${
|
||||
builtins.addErrorContext "while evaluating an attribute `${name}`" (process (indent + " ") value)
|
||||
};"
|
||||
) value
|
||||
);
|
||||
in
|
||||
if allowCustomPrettifiers && value ? __pretty__ && value ? value then
|
||||
value.__pretty__ value.value
|
||||
else if value == { } then
|
||||
"{ }"
|
||||
else if lib.packages.isDerivation value then
|
||||
"<derivation ${value.name or "???"}>"
|
||||
else
|
||||
"{${prefix}${contents}${suffix}}";
|
||||
in
|
||||
if null == value then
|
||||
prettyNull
|
||||
else if builtins.isInt value || builtins.isFloat value then
|
||||
prettyNumber
|
||||
else if builtins.isBool value then
|
||||
prettyBool
|
||||
else if builtins.isString value then
|
||||
prettyString
|
||||
else if builtins.isPath value then
|
||||
prettyPath
|
||||
else if builtins.isList value then
|
||||
prettyList
|
||||
else if builtins.isFunction value then
|
||||
prettyFunction
|
||||
else if builtins.isAttrs value then
|
||||
prettyAttrs
|
||||
else
|
||||
builtins.abort "lib.generators.pretty: should never happen (value = ${value})";
|
||||
in
|
||||
process indent;
|
||||
};
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"withRecursion" = {
|
||||
"evaluates within a given limit" =
|
||||
let
|
||||
expected = {
|
||||
x = 1;
|
||||
};
|
||||
actual = lib.generators.withRecursion { limit = 100; } expected;
|
||||
in
|
||||
expected == actual;
|
||||
|
||||
"fails when the limit is reached" =
|
||||
let
|
||||
expected = {
|
||||
x = 1;
|
||||
};
|
||||
actual = lib.generators.withRecursion { limit = -1; } expected;
|
||||
evaluated = builtins.tryEval (builtins.deepSeq actual actual);
|
||||
in
|
||||
!evaluated.success;
|
||||
|
||||
"does not fail when throw is disabled" =
|
||||
let
|
||||
expected = {
|
||||
x = "<unevaluated>";
|
||||
};
|
||||
actual = lib.generators.withRecursion {
|
||||
limit = -1;
|
||||
throw = false;
|
||||
} { x = 1; };
|
||||
evaluated = builtins.tryEval (builtins.deepSeq actual actual);
|
||||
in
|
||||
evaluated.success && evaluated.value == expected;
|
||||
};
|
||||
|
||||
"pretty" = {
|
||||
"formats with defaults" =
|
||||
let
|
||||
expected = ''
|
||||
{
|
||||
attrs = { };
|
||||
bool = true;
|
||||
float = 0.0;
|
||||
function = <function>;
|
||||
int = 0;
|
||||
list = [ ];
|
||||
string = "string";
|
||||
}'';
|
||||
actual = lib.generators.pretty { } {
|
||||
attrs = { };
|
||||
bool = true;
|
||||
float = 0.0;
|
||||
function = x: x;
|
||||
int = 0;
|
||||
list = [ ];
|
||||
string = "string";
|
||||
# NOTE: We are not testing `path` types because they can return out of store
|
||||
# values which are not deterministic.
|
||||
# path = ./.;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"formats with custom prettifiers" =
|
||||
let
|
||||
expected = ''
|
||||
{
|
||||
attrs = { };
|
||||
bool = true;
|
||||
custom = <custom>;
|
||||
float = 0.0;
|
||||
function = <function>;
|
||||
int = 0;
|
||||
list = [ ];
|
||||
string = "string";
|
||||
}'';
|
||||
actual = lib.generators.pretty { allowCustomPrettifiers = true; } {
|
||||
attrs = { };
|
||||
bool = true;
|
||||
float = 0.0;
|
||||
function = x: x;
|
||||
int = 0;
|
||||
list = [ ];
|
||||
string = "string";
|
||||
custom = {
|
||||
value = 0;
|
||||
__pretty__ = value: "<custom>";
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"formats with multiline disabled" =
|
||||
let
|
||||
expected = "{ attrs = { }; bool = true; float = 0.0; function = <function>; int = 0; list = [ ]; string = \"string\"; }";
|
||||
actual = lib.generators.pretty { multiline = false; } {
|
||||
attrs = { };
|
||||
bool = true;
|
||||
float = 0.0;
|
||||
function = x: x;
|
||||
int = 0;
|
||||
list = [ ];
|
||||
string = "string";
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
lib: {
|
||||
importers = {
|
||||
## Import a JSON file as a Nix value.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> a
|
||||
json = file: builtins.fromJSON (builtins.readFile file);
|
||||
|
||||
## Import a TOML file as a Nix value.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> a
|
||||
toml = file: builtins.fromTOML (builtins.readFile file);
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
File diff suppressed because it is too large
Load diff
|
@ -1,29 +0,0 @@
|
|||
lib: {
|
||||
licenses =
|
||||
let
|
||||
raw = import ./all.nix;
|
||||
|
||||
defaults = name: {
|
||||
inherit name;
|
||||
free = true;
|
||||
};
|
||||
|
||||
withDefaults = name: license: (defaults name) // license;
|
||||
withSpdx =
|
||||
license:
|
||||
if license ? spdx then
|
||||
license // { url = "https://spdx.org/licenses/${license.spdx}.html"; }
|
||||
else
|
||||
license;
|
||||
withRedistributable = license: { redistributable = license.free; } // license;
|
||||
|
||||
normalize =
|
||||
name:
|
||||
lib.fp.pipe [
|
||||
(withDefaults name)
|
||||
withSpdx
|
||||
withRedistributable
|
||||
];
|
||||
in
|
||||
builtins.mapAttrs normalize raw;
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
lib: {
|
||||
lists = {
|
||||
from = {
|
||||
## Convert a value to a list. If the value is already a list,
|
||||
## it will be returned as-is. If the value is not a list, it
|
||||
## will be wrapped in a list.
|
||||
##
|
||||
## @type a | (List a) -> List a
|
||||
any = value: if builtins.isList value then value else [ value ];
|
||||
};
|
||||
|
||||
sort = {
|
||||
## Perform a natural sort on a list of strings.
|
||||
##
|
||||
## @type List String -> List String
|
||||
natural =
|
||||
list:
|
||||
let
|
||||
vectorize =
|
||||
string:
|
||||
let
|
||||
serialize = part: if builtins.isList part then lib.strings.into.int (builtins.head part) else part;
|
||||
parts = lib.strings.split "(0|[1-9][0-9]*)" string;
|
||||
in
|
||||
builtins.map serialize parts;
|
||||
prepared = builtins.map (value: [
|
||||
(vectorize value)
|
||||
value
|
||||
]) list;
|
||||
isLess = a: b: (lib.lists.compare lib.numbers.compare (builtins.head a) (builtins.head b)) < 0;
|
||||
in
|
||||
builtins.map (x: builtins.elemAt x 1) (builtins.sort isLess prepared);
|
||||
|
||||
## Perform a topological sort on a list of items. The predicate function determines whether
|
||||
## its first argument comes before the second argument.
|
||||
##
|
||||
## @type (a -> a -> Bool) -> List a -> List a
|
||||
topological =
|
||||
predicate: list:
|
||||
let
|
||||
searched = lib.lists.search.depthFirst true predicate list;
|
||||
results = lib.lists.sort.topological predicate (searched.visited ++ searched.rest);
|
||||
in
|
||||
if builtins.length list < 2 then
|
||||
{ result = list; }
|
||||
else if searched ? cycle then
|
||||
{
|
||||
loops = searched.loops;
|
||||
cycle = lib.lists.reverse ([ searched.cycle ] ++ searched.visited);
|
||||
}
|
||||
else if results ? cycle then
|
||||
results
|
||||
else
|
||||
{ result = [ searched.minimal ] ++ results.result; };
|
||||
};
|
||||
|
||||
search = {
|
||||
## Perform a depth first search on a list. The supplied predicate function determines whether
|
||||
## its first argument comes before the second argument.
|
||||
##
|
||||
## @type Bool -> (a -> a -> Bool) -> List a
|
||||
depthFirst =
|
||||
isAcyclical: predicate: list:
|
||||
let
|
||||
process =
|
||||
current: visited: rest:
|
||||
let
|
||||
loops = builtins.filter (value: predicate value current) visited;
|
||||
partitioned = builtins.partition (value: predicate value current) rest;
|
||||
in
|
||||
if isAcyclical && (builtins.length loops > 0) then
|
||||
{
|
||||
cycle = current;
|
||||
inherit loops visited rest;
|
||||
}
|
||||
else if builtins.length partitioned.right == 0 then
|
||||
{
|
||||
minimal = current;
|
||||
inherit visited rest;
|
||||
}
|
||||
else
|
||||
process (builtins.head partitioned.right) ([ current ] ++ visited) (
|
||||
builtins.tail partitioned.right ++ partitioned.wrong
|
||||
);
|
||||
in
|
||||
process (builtins.head list) [ ] (builtins.tail list);
|
||||
};
|
||||
|
||||
## Map a list using both the index and value of each item. The
|
||||
## index starts at 0.
|
||||
##
|
||||
## @type (Int -> a -> b) -> List a -> List b
|
||||
mapWithIndex = f: list: builtins.genList (i: f i (builtins.elemAt list i)) (builtins.length list);
|
||||
|
||||
## Map a list using both the index and value of each item. The
|
||||
## index starts at 1.
|
||||
##
|
||||
## @type (Int -> a -> b) -> List a -> List b
|
||||
mapWithIndex1 =
|
||||
f: list: builtins.genList (i: f (i + 1) (builtins.elemAt list i)) (builtins.length list);
|
||||
|
||||
## Compare two lists using a custom compare function. The compare
|
||||
## function is called for each element in the lists that need to
|
||||
## be compared.
|
||||
##
|
||||
## @type (a -> b -> -1 | 0 | 1) -> List a -> List b -> Int
|
||||
compare =
|
||||
compare: a: b:
|
||||
let
|
||||
result = compare (builtins.head a) (builtins.head b);
|
||||
in
|
||||
if a == [ ] then
|
||||
if b == [ ] then 0 else -1
|
||||
else if b == [ ] then
|
||||
1
|
||||
else if result == 0 then
|
||||
lib.lists.compare compare (builtins.tail a) (builtins.tail b)
|
||||
else
|
||||
result;
|
||||
|
||||
## Get the last element of a list.
|
||||
##
|
||||
## @type List a -> a
|
||||
last =
|
||||
list:
|
||||
assert lib.errors.trace (list != [ ]) "List cannot be empty";
|
||||
builtins.elemAt list (builtins.length list - 1);
|
||||
|
||||
## Slice part of a list to create a new list.
|
||||
##
|
||||
## @type Int -> Int -> List -> List
|
||||
slice =
|
||||
start: count: list:
|
||||
let
|
||||
listLength = builtins.length list;
|
||||
resultLength =
|
||||
if start >= listLength then
|
||||
0
|
||||
else if start + count > listLength then
|
||||
listLength - start
|
||||
else
|
||||
count;
|
||||
in
|
||||
builtins.genList (i: builtins.elemAt list (start + i)) resultLength;
|
||||
|
||||
## Take the first n elements of a list.
|
||||
##
|
||||
## @type Int -> List -> List
|
||||
take = lib.lists.slice 0;
|
||||
|
||||
## Drop the first n elements of a list.
|
||||
##
|
||||
## @type Int -> List -> List
|
||||
drop =
|
||||
count: list:
|
||||
let
|
||||
listLength = builtins.length list;
|
||||
in
|
||||
lib.lists.slice count listLength list;
|
||||
|
||||
## Take all but the last element of a list.
|
||||
##
|
||||
## @type List -> List
|
||||
init =
|
||||
list:
|
||||
assert lib.errors.trace (builtins.length list != 0) "lib.lists.init: list must not be empty.";
|
||||
lib.lists.take (builtins.length list - 1) list;
|
||||
|
||||
## Reverse a list.
|
||||
##
|
||||
## @type List -> List
|
||||
reverse =
|
||||
list:
|
||||
let
|
||||
length = builtins.length list;
|
||||
create = i: builtins.elemAt list (length - i - 1);
|
||||
in
|
||||
builtins.genList create length;
|
||||
|
||||
## Interleave a list with a separator.
|
||||
##
|
||||
## @type Separator -> List -> List
|
||||
intersperse =
|
||||
separator: list:
|
||||
let
|
||||
length = builtins.length list;
|
||||
in
|
||||
if length < 2 then
|
||||
list
|
||||
else
|
||||
builtins.tail (
|
||||
builtins.concatMap (part: [
|
||||
separator
|
||||
part
|
||||
]) list
|
||||
);
|
||||
|
||||
## Create a list of integers from a starting number to an ending
|
||||
## number. This *includes* the ending number as well.
|
||||
##
|
||||
## @type Int -> Int -> List
|
||||
range = start: end: if start > end then [ ] else builtins.genList (i: start + i) (end - start + 1);
|
||||
|
||||
## Depending on a given condition, either use the given value (as
|
||||
## a list) or an empty list.
|
||||
##
|
||||
## @type Attrs a b => Bool -> a -> a | b
|
||||
when =
|
||||
condition: value: if condition then if builtins.isList value then value else [ value ] else [ ];
|
||||
|
||||
## Count the number of items in a list that satisfy a given predicate.
|
||||
##
|
||||
## @type (a -> Bool) -> List a -> Int
|
||||
count =
|
||||
predicate: list:
|
||||
builtins.foldl' (total: value: if predicate value then total + 1 else total) 0 list;
|
||||
|
||||
## Remove duplicate items from a list.
|
||||
##
|
||||
## @type List -> List
|
||||
unique =
|
||||
list:
|
||||
let
|
||||
filter = result: value: if builtins.elem value result then result else result ++ [ value ];
|
||||
in
|
||||
builtins.foldl' filter [ ] list;
|
||||
|
||||
## Flatten a list of lists into a single list.
|
||||
##
|
||||
## @type List (List a) -> List a
|
||||
flatten =
|
||||
value: if builtins.isList value then builtins.concatMap lib.lists.flatten value else [ value ];
|
||||
};
|
||||
}
|
|
@ -1,345 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"from" = {
|
||||
"any" = {
|
||||
"returns a list containing the value" =
|
||||
let
|
||||
expected = [ 1 ];
|
||||
actual = lib.lists.from.any 1;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns the value if the value was already a list" =
|
||||
let
|
||||
expected = [ 1 ];
|
||||
actual = lib.lists.from.any expected;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"sort" = {
|
||||
"natural" = {
|
||||
"sorts a list of strings" =
|
||||
let
|
||||
expected = [
|
||||
"1"
|
||||
"a"
|
||||
"a0"
|
||||
"a1"
|
||||
"b"
|
||||
"c"
|
||||
];
|
||||
actual = lib.lists.sort.natural [
|
||||
"c"
|
||||
"a"
|
||||
"b"
|
||||
"a1"
|
||||
"a0"
|
||||
"1"
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"mapWithIndex" = {
|
||||
"maps a list using index 0" =
|
||||
let
|
||||
expected = [
|
||||
"0: a"
|
||||
"1: b"
|
||||
"2: c"
|
||||
];
|
||||
actual = lib.lists.mapWithIndex (i: v: "${builtins.toString i}: ${v}") [
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"mapWithIndex1" = {
|
||||
"maps a list using index 1" =
|
||||
let
|
||||
expected = [
|
||||
"1: a"
|
||||
"2: b"
|
||||
"3: c"
|
||||
];
|
||||
actual = lib.lists.mapWithIndex1 (i: v: "${builtins.toString i}: ${v}") [
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"compare" = {
|
||||
"compares two lists" = {
|
||||
"returns -1 if the first list is smaller" =
|
||||
let
|
||||
expected = -1;
|
||||
actual =
|
||||
lib.lists.compare lib.numbers.compare
|
||||
[
|
||||
1
|
||||
2
|
||||
3
|
||||
]
|
||||
[
|
||||
1
|
||||
2
|
||||
4
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
"returns 1 if the first list is larger" =
|
||||
let
|
||||
expected = 1;
|
||||
actual =
|
||||
lib.lists.compare lib.numbers.compare
|
||||
[
|
||||
1
|
||||
2
|
||||
4
|
||||
]
|
||||
[
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
"returns 0 if the lists are equal" =
|
||||
let
|
||||
expected = 0;
|
||||
actual =
|
||||
lib.lists.compare lib.numbers.compare
|
||||
[
|
||||
1
|
||||
2
|
||||
3
|
||||
]
|
||||
[
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"last" = {
|
||||
"returns the last element of a list" =
|
||||
let
|
||||
expected = 3;
|
||||
actual = lib.lists.last [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"fails if the list is empty" =
|
||||
let
|
||||
actual = lib.lists.last [ ];
|
||||
evaluated = builtins.tryEval actual;
|
||||
in
|
||||
!evaluated.success;
|
||||
};
|
||||
|
||||
"slice" = {
|
||||
"slices a list" = {
|
||||
"slices a list from the start" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
2
|
||||
];
|
||||
actual = lib.lists.slice 0 2 [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
"slices a list from the end" =
|
||||
let
|
||||
expected = [
|
||||
2
|
||||
3
|
||||
];
|
||||
actual = lib.lists.slice 1 3 [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
"slices a list from the middle" =
|
||||
let
|
||||
expected = [ 2 ];
|
||||
actual = lib.lists.slice 1 1 [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"take" = {
|
||||
"takes the first n elements" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
2
|
||||
];
|
||||
actual = lib.lists.take 2 [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"drop" = {
|
||||
"drops the first n elements" =
|
||||
let
|
||||
expected = [ 3 ];
|
||||
actual = lib.lists.drop 2 [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"reverse" = {
|
||||
"reverses a list" =
|
||||
let
|
||||
expected = [
|
||||
3
|
||||
2
|
||||
1
|
||||
];
|
||||
actual = lib.lists.reverse [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"intersperse" = {
|
||||
"intersperses a list with a separator" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
"-"
|
||||
2
|
||||
"-"
|
||||
3
|
||||
];
|
||||
actual = lib.lists.intersperse "-" [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles lists with less than 2 elements" =
|
||||
let
|
||||
expected = [ 1 ];
|
||||
actual = lib.lists.intersperse "-" [ 1 ];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"range" = {
|
||||
"returns a range of numbers" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
];
|
||||
actual = lib.lists.range 1 5;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"when" = {
|
||||
"returns the list if the condition is true" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
actual = lib.lists.when true [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
"returns an empty list if the condition is false" =
|
||||
let
|
||||
expected = [ ];
|
||||
actual = lib.lists.when false [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"count" = {
|
||||
"counts the number of elements in a list" =
|
||||
let
|
||||
expected = 2;
|
||||
actual = lib.lists.count (value: value < 3) [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"unique" = {
|
||||
"removes duplicate elements" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
actual = lib.lists.unique [
|
||||
1
|
||||
2
|
||||
3
|
||||
1
|
||||
2
|
||||
3
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
lib: {
|
||||
math = {
|
||||
## Return the smaller of two numbers.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
min = x: y: if x < y then x else y;
|
||||
|
||||
## Return the larger of two numbers.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
max = x: y: if x > y then x else y;
|
||||
};
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"min" = {
|
||||
"returns the smaller number" =
|
||||
let
|
||||
expected = 1;
|
||||
actual = lib.math.min 1 2;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"max" = {
|
||||
"returns the larger number" =
|
||||
let
|
||||
expected = 2;
|
||||
actual = lib.math.max 1 2;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,702 +0,0 @@
|
|||
lib: {
|
||||
modules = {
|
||||
from = {
|
||||
## Create a module from a JSON file.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> Module
|
||||
json = file: {
|
||||
__file__ = file;
|
||||
config = lib.importers.json file;
|
||||
};
|
||||
|
||||
## Create a module from a TOML file.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> Module
|
||||
toml = file: {
|
||||
__file__ = file;
|
||||
config = lib.importers.toml file;
|
||||
};
|
||||
};
|
||||
|
||||
apply = {
|
||||
## Apply custom definitions such as `merge` and `when` to a definition.
|
||||
## Note that this function does not perform actiosn like `merge`, but
|
||||
## instead pulls out the merge contents to be processed by the module
|
||||
## system.
|
||||
##
|
||||
## @type Definition -> List (Definition | (List Definition))
|
||||
properties =
|
||||
definition:
|
||||
if lib.types.is "merge" definition then
|
||||
builtins.concatMap lib.modules.apply.properties definition.content
|
||||
else if lib.types.is "when" definition then
|
||||
if !(builtins.isBool definition.condition) then
|
||||
builtins.throw "lib.modules.when called with a non-boolean condition"
|
||||
else if definition.condition then
|
||||
lib.modules.apply.properties definition.content
|
||||
else
|
||||
[ ]
|
||||
else
|
||||
[ definition ];
|
||||
|
||||
## Apply overrides for a definition. This uses the priority system
|
||||
## to determine which definition to use. The most important (lowest
|
||||
## priority) choice will be used.
|
||||
##
|
||||
## @type List Definition -> { highestPriority :: Int, values :: List (Definition | (List Definition)) }
|
||||
overrides =
|
||||
definitions:
|
||||
let
|
||||
getPriority =
|
||||
definition:
|
||||
if lib.types.is "override" definition.value then
|
||||
definition.value.priority
|
||||
else
|
||||
lib.modules.DEFAULT_PRIORITY;
|
||||
normalize =
|
||||
definition:
|
||||
if lib.types.is "override" definition.value then
|
||||
definition // { value = definition.value.content; }
|
||||
else
|
||||
definition;
|
||||
highestPriority = builtins.foldl' (
|
||||
priority: definition: lib.math.min priority (getPriority definition)
|
||||
) 9999 definitions;
|
||||
in
|
||||
{
|
||||
inherit highestPriority;
|
||||
values = builtins.concatMap (
|
||||
definition: if getPriority definition == highestPriority then [ (normalize definition) ] else [ ]
|
||||
) definitions;
|
||||
};
|
||||
|
||||
## Apply ordering for prioritized definitions.
|
||||
##
|
||||
## @type List Definition -> List Definition
|
||||
order =
|
||||
definitions:
|
||||
let
|
||||
normalize =
|
||||
definition:
|
||||
if lib.types.is "order" definition then
|
||||
definition
|
||||
// {
|
||||
value = definition.value.content;
|
||||
priority = definition.value.priority;
|
||||
}
|
||||
else
|
||||
definition;
|
||||
normalized = builtins.map normalize definitions;
|
||||
compare =
|
||||
a: b: (a.priority or lib.modules.DEFAULT_PRIORITY) < (b.priority or lib.modules.DEFAULT_PRIORITY);
|
||||
in
|
||||
builtins.sort compare normalized;
|
||||
|
||||
## Normalize the type of an option. This will set a default type if none
|
||||
## was provided.
|
||||
##
|
||||
## @type List String -> Option -> Option
|
||||
fixup =
|
||||
location: option:
|
||||
if option.type.submodules or null == null then
|
||||
option // { type = option.type or lib.types.unspecified; }
|
||||
else
|
||||
option
|
||||
// {
|
||||
type = option.type.withSubModules option.options;
|
||||
options = [ ];
|
||||
};
|
||||
|
||||
## Invert the structure of `merge`, `when`, and `override` definitions so
|
||||
## that they apply to each individual attribute in their respective sets.
|
||||
## Note that this function _only_ supports attribute sets within specialized
|
||||
## definitions such as `when` and `override`. Other values like lists will
|
||||
## throw a type error.
|
||||
##
|
||||
## @type Definition -> List Definition
|
||||
invert =
|
||||
config:
|
||||
if lib.types.is "merge" config then
|
||||
builtins.concatMap lib.modules.apply.invert config.content
|
||||
else if lib.types.is "when" config then
|
||||
builtins.map (builtins.mapAttrs (name: value: lib.modules.when config.condition value)) (
|
||||
lib.modules.apply.invert config.content
|
||||
)
|
||||
else if lib.types.is "override" config then
|
||||
builtins.map (builtins.mapAttrs (name: value: lib.modules.override config.priority value)) (
|
||||
lib.modules.apply.invert config.content
|
||||
)
|
||||
else
|
||||
[ config ];
|
||||
};
|
||||
|
||||
validate = {
|
||||
## Check that a module only specifies supported attributes.
|
||||
##
|
||||
## @type Attrs -> Bool
|
||||
keys =
|
||||
module:
|
||||
let
|
||||
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
|
||||
in
|
||||
invalid == { };
|
||||
};
|
||||
|
||||
## Modules only support certain keys at the root level. This list determines
|
||||
## the valid attributes that users can supply.
|
||||
##
|
||||
## @type List String
|
||||
VALID_KEYS = [
|
||||
"__file__"
|
||||
"__key__"
|
||||
"includes"
|
||||
"excludes"
|
||||
"options"
|
||||
"config"
|
||||
"freeform"
|
||||
"meta"
|
||||
];
|
||||
|
||||
## Normalize a module to a standard structure. All other information will be
|
||||
## lost in the conversion.
|
||||
##
|
||||
## @type String -> String -> Attrs -> Module
|
||||
normalize =
|
||||
file: key: module:
|
||||
let
|
||||
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
|
||||
invalidKeys = builtins.concatStringsSep ", " (builtins.attrNames invalid);
|
||||
in
|
||||
if lib.modules.validate.keys module then
|
||||
{
|
||||
__file__ = builtins.toString module.__file__ or file;
|
||||
__key__ = builtins.toString module.__key__ or key;
|
||||
includes = module.includes or [ ];
|
||||
excludes = module.excludes or [ ];
|
||||
options = module.options or { };
|
||||
config =
|
||||
let
|
||||
base = module.config or { };
|
||||
withMeta =
|
||||
config:
|
||||
if module ? meta then
|
||||
lib.modules.merge [
|
||||
config
|
||||
{ meta = module.meta; }
|
||||
]
|
||||
else
|
||||
config;
|
||||
withFreeform =
|
||||
config:
|
||||
if module ? freeform then
|
||||
lib.modules.merge [
|
||||
config
|
||||
{ __module__.freeform = module.freeform; }
|
||||
]
|
||||
else
|
||||
config;
|
||||
in
|
||||
withFreeform (withMeta base);
|
||||
}
|
||||
else
|
||||
builtins.throw "Module `${key}` (${file}) has unsupported attribute(s): ${invalidKeys}";
|
||||
|
||||
## Convert a module that is either a function or an attribute set into
|
||||
## a resolved attribute set. If the module was a function then it will
|
||||
## be evaluated and the result will be returned.
|
||||
##
|
||||
## @type String -> Attrs | (Attrs -> Attrs) -> Attrs -> Attrs
|
||||
resolve =
|
||||
key: module: args:
|
||||
let
|
||||
dynamicArgs = builtins.mapAttrs (
|
||||
name: value:
|
||||
builtins.addErrorContext "while evaluating the module argument `${name}` in `${key}`" (
|
||||
# The base case here is the set of static arguments supplied in the
|
||||
# call to `lib.modules.run`.
|
||||
args.${name}
|
||||
# Then, any other arguments must be created dynamically from within
|
||||
# the modules being evaluated.
|
||||
or args.config.__module__.args.dynamic.${name}
|
||||
)
|
||||
) (lib.fp.args module);
|
||||
in
|
||||
if builtins.isFunction module then lib.fp.withDynamicArgs module (args // dynamicArgs) else module;
|
||||
|
||||
## The default priority to set for values that do not have one provided.
|
||||
##
|
||||
## @type Int
|
||||
DEFAULT_PRIORITY = 100;
|
||||
|
||||
## Allow for sorting the values provided to a module by priority. The
|
||||
## most important value will be used.
|
||||
##
|
||||
## @notest
|
||||
## @type Int -> a -> Attrs
|
||||
order = priority: value: {
|
||||
__type__ = "order";
|
||||
inherit priority value;
|
||||
};
|
||||
|
||||
orders = {
|
||||
## Order a value before others.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
before = lib.modules.order 500;
|
||||
|
||||
## Use the default ordering for a value.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
default = lib.modules.order 1000;
|
||||
|
||||
## Order a value after others.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
after = lib.modules.order 1500;
|
||||
};
|
||||
|
||||
## Extract a list of files from a list of modules.
|
||||
##
|
||||
## @type List Module -> List (String | Path)
|
||||
getFiles = builtins.map (module: module.__file__);
|
||||
|
||||
## Create a conditional value which is only used when the condition's
|
||||
## value is `true`.
|
||||
##
|
||||
## @notest
|
||||
## @type Bool -> a -> When a
|
||||
when = condition: content: {
|
||||
__type__ = "when";
|
||||
inherit condition content;
|
||||
};
|
||||
|
||||
## Merge module attribute sets when evaluated in the module system.
|
||||
##
|
||||
## @notest
|
||||
## @type List Attrs -> Attrs
|
||||
merge = content: {
|
||||
__type__ = "merge";
|
||||
inherit content;
|
||||
};
|
||||
|
||||
## Create a value override which can replace other definitions if it
|
||||
## has the higher priority.
|
||||
##
|
||||
## @notest
|
||||
## @type Int -> a -> Attrs
|
||||
override = priority: content: {
|
||||
__type__ = "override";
|
||||
inherit priority content;
|
||||
};
|
||||
|
||||
overrides = {
|
||||
## Create an override used for setting the `default.value` from an
|
||||
## option. This uses the lowest priority of all predefined overrides.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
option = lib.modules.override 1500;
|
||||
|
||||
## Create a default override for a value. This uses a very low priority
|
||||
## so that it can easily be overridden.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
default = lib.modules.override 1000;
|
||||
|
||||
## Create a high priority override for a value. This is not the highest
|
||||
## priority possible, but it will override nearly everything else except
|
||||
## for very explicit cases.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
force = lib.modules.override 50;
|
||||
|
||||
## Create a high priority override intended to be used only for VM targets.
|
||||
## This allows for forcing certain values even if a user has otherwise
|
||||
## specified `lib.modules.overrides.force`.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
vm = lib.modules.override 10;
|
||||
};
|
||||
|
||||
## Create alias definitions for a given option.
|
||||
##
|
||||
## @type Option -> Attrs
|
||||
alias = lib.modules.aliasWith lib.fp.id;
|
||||
|
||||
## Create alias definitions for a given option.
|
||||
##
|
||||
## @type (Attrs -> Attrs) -> Option -> Attrs
|
||||
aliasWith =
|
||||
f: option:
|
||||
let
|
||||
exists = lib.types.is "option" option && option.isDefined;
|
||||
value = f (lib.modules.merge option.definitions);
|
||||
in
|
||||
lib.modules.when exists value;
|
||||
|
||||
## Combine multiple modules together.
|
||||
##
|
||||
## @type List String -> List Module -> { matched :: Attrs, unmatched :: List Definition }
|
||||
combine =
|
||||
prefix: modules:
|
||||
let
|
||||
getConfig =
|
||||
module:
|
||||
builtins.map (config: {
|
||||
__file__ = module.__file__;
|
||||
inherit config;
|
||||
}) (lib.modules.apply.invert module.config);
|
||||
|
||||
configs = builtins.concatMap getConfig modules;
|
||||
|
||||
process =
|
||||
prefix: options: configs:
|
||||
let
|
||||
byName =
|
||||
attr: f: modules:
|
||||
builtins.zipAttrsWith (key: value: builtins.concatLists value) (
|
||||
builtins.map (
|
||||
module:
|
||||
let
|
||||
subtree = module.${attr};
|
||||
in
|
||||
if builtins.isAttrs subtree then
|
||||
builtins.mapAttrs (name: f module) subtree
|
||||
else
|
||||
builtins.throw "Value for `${builtins.concatStringsSep "." prefix}` is of type `${builtins.typeOf subtree}` but an attribute set was expected."
|
||||
) modules
|
||||
);
|
||||
|
||||
declarationsByName = byName "options" (module: option: [
|
||||
{
|
||||
__file__ = module.__file__;
|
||||
options = option;
|
||||
}
|
||||
]) options;
|
||||
|
||||
definitionsByName = byName "config" (
|
||||
module: value:
|
||||
builtins.map (config: {
|
||||
__file__ = module.__file__;
|
||||
inherit config;
|
||||
}) (lib.modules.apply.invert value)
|
||||
) configs;
|
||||
|
||||
definitionsByName' = byName "config" (module: value: [
|
||||
{
|
||||
__file__ = module.__file__;
|
||||
inherit value;
|
||||
}
|
||||
]) configs;
|
||||
|
||||
getOptionFromDeclaration =
|
||||
declaration:
|
||||
if lib.types.is "option" declaration.options then
|
||||
declaration
|
||||
else
|
||||
declaration
|
||||
// {
|
||||
options = lib.options.create { type = lib.types.submodule [ { options = declaration.options; } ]; };
|
||||
};
|
||||
|
||||
resultsByName = builtins.mapAttrs (
|
||||
name: declarations:
|
||||
let
|
||||
location = prefix ++ [ name ];
|
||||
definitions = definitionsByName.${name} or [ ];
|
||||
definitions' = definitionsByName'.${name} or [ ];
|
||||
optionDeclarations = builtins.filter (
|
||||
declaration: lib.types.is "option" declaration.options
|
||||
) declarations;
|
||||
in
|
||||
if builtins.length optionDeclarations == builtins.length declarations then
|
||||
let
|
||||
option = lib.modules.apply.fixup location (lib.options.merge.declarations location declarations);
|
||||
in
|
||||
{
|
||||
matched = lib.options.run location option definitions';
|
||||
unmatched = [ ];
|
||||
}
|
||||
else if optionDeclarations != [ ] then
|
||||
if builtins.all (declaration: declaration.options.type.name == "Submodule") optionDeclarations then
|
||||
let
|
||||
option = lib.modules.apply.fixup location (
|
||||
lib.options.merge.declarations location (builtins.map getOptionFromDeclaration declarations)
|
||||
);
|
||||
in
|
||||
{
|
||||
matched = lib.options.run location option definitions';
|
||||
unmatched = [ ];
|
||||
}
|
||||
else
|
||||
builtins.throw "The option `${lib.options.getIdentifier location}` in module `${(builtins.head optionDeclarations).__file__}` would be a parent of the following options, but its type `${
|
||||
(builtins.head optionDeclarations).options.type.description or "<no description>"
|
||||
}` does not support nested options."
|
||||
else
|
||||
process location declarations definitions
|
||||
) declarationsByName;
|
||||
|
||||
matched = builtins.mapAttrs (key: value: value.matched) resultsByName;
|
||||
|
||||
unmatched =
|
||||
builtins.mapAttrs (name: value: value.unmatched) resultsByName
|
||||
// builtins.removeAttrs definitionsByName' (builtins.attrNames matched);
|
||||
in
|
||||
{
|
||||
inherit matched;
|
||||
|
||||
unmatched =
|
||||
if configs == [ ] then
|
||||
[ ]
|
||||
else
|
||||
builtins.concatLists (
|
||||
lib.attrs.mapToList (
|
||||
name: definitions:
|
||||
builtins.map (
|
||||
definition: definition // { prefix = [ name ] ++ (definition.prefix or [ ]); }
|
||||
) definitions
|
||||
) unmatched
|
||||
);
|
||||
};
|
||||
in
|
||||
process prefix modules configs;
|
||||
|
||||
## Run a set of modules. Custom arguments can also be supplied which will
|
||||
## be provided to all modules statically as they are not modifiable.
|
||||
##
|
||||
## @type { modules? :: List (Attrs | (Attrs -> Attrs)), args? :: Attrs, prefix? :: List String } -> { type :: Module, extend :: lib.modules.run, options :: Attrs, config :: Attrs, __module__ :: Attrs }
|
||||
run =
|
||||
settings@{
|
||||
modules ? [ ],
|
||||
args ? { },
|
||||
prefix ? [ ],
|
||||
}:
|
||||
let
|
||||
type = lib.types.submodules.of { inherit modules args; };
|
||||
|
||||
extend =
|
||||
extensions@{
|
||||
modules ? [ ],
|
||||
args ? { },
|
||||
prefix ? [ ],
|
||||
}:
|
||||
lib.modules.run {
|
||||
modules = (settings.modules or [ ]) ++ (extensions.modules or [ ]);
|
||||
args = (settings.args or { }) // (extensions.args or { });
|
||||
prefix = extensions.prefix or settings.prefix or [ ];
|
||||
};
|
||||
|
||||
collect =
|
||||
let
|
||||
load =
|
||||
args: file: key: module:
|
||||
let
|
||||
moduleFromValue = lib.modules.normalize file key (lib.modules.resolve key module args);
|
||||
moduleFromPath = lib.modules.normalize (builtins.toString module) (builtins.toString module) (
|
||||
lib.modules.resolve (builtins.toString module) (import module) args
|
||||
);
|
||||
in
|
||||
if builtins.isAttrs module || builtins.isFunction module then
|
||||
moduleFromValue
|
||||
else if builtins.isString module || builtins.isPath module then
|
||||
moduleFromPath
|
||||
else
|
||||
builtins.throw "The provided module must be either an attribute set, function, or path but got ${builtins.typeOf module}";
|
||||
|
||||
normalize =
|
||||
parentFile: parentKey: modules: args:
|
||||
let
|
||||
normalized = lib.lists.mapWithIndex1 (
|
||||
i: value:
|
||||
let
|
||||
module = load args parentFile "${parentKey}:unknown-${builtins.toString i}" value;
|
||||
tree = normalize module.__file__ module.__key__ module.includes args;
|
||||
in
|
||||
{
|
||||
inherit module;
|
||||
key = module.__key__;
|
||||
modules = tree.modules;
|
||||
excludes = module.excludes ++ tree.excludes;
|
||||
}
|
||||
) modules;
|
||||
in
|
||||
{
|
||||
modules = normalized;
|
||||
excludes = builtins.concatLists (builtins.catAttrs "excludes" normalized);
|
||||
};
|
||||
|
||||
withExclusions =
|
||||
path:
|
||||
{ modules, excludes }:
|
||||
let
|
||||
getKey =
|
||||
module:
|
||||
if builtins.isString module && (builtins.substring 0 1 module != "/") then
|
||||
(builtins.toString path) + "/" + module
|
||||
else
|
||||
builtins.toString module;
|
||||
excludedKeys = builtins.map getKey excludes;
|
||||
removeExcludes = builtins.filter (value: !(builtins.elem value.key excludedKeys));
|
||||
in
|
||||
builtins.map (value: value.module) (
|
||||
builtins.genericClosure {
|
||||
startSet = removeExcludes modules;
|
||||
operator = value: removeExcludes value.modules;
|
||||
}
|
||||
);
|
||||
|
||||
process =
|
||||
path: modules: args:
|
||||
withExclusions path (normalize "<unknown>" "" modules args);
|
||||
in
|
||||
process;
|
||||
|
||||
internal = {
|
||||
__file__ = "virtual:aux/internal";
|
||||
__key__ = "virtual:aux/internal";
|
||||
|
||||
options = {
|
||||
__module__ = {
|
||||
args = {
|
||||
static = lib.options.create {
|
||||
type = lib.types.attrs.lazy lib.types.raw;
|
||||
writable = false;
|
||||
internal = false;
|
||||
description = "Static arguments provided to lib.modules.run which cannot be changed.";
|
||||
};
|
||||
|
||||
dynamic = lib.options.create {
|
||||
type = lib.types.attrs.lazy lib.types.raw;
|
||||
|
||||
${if prefix == [ ] then null else "internal"} = true;
|
||||
|
||||
visible = false;
|
||||
|
||||
description = "Additional arguments pased to each module.";
|
||||
};
|
||||
};
|
||||
|
||||
check = lib.options.create {
|
||||
type = lib.types.bool;
|
||||
default.value = true;
|
||||
internal = true;
|
||||
description = "Whether to perform checks on option definitions.";
|
||||
};
|
||||
|
||||
freeform = lib.options.create {
|
||||
type = lib.types.nullish lib.types.type;
|
||||
default.value = null;
|
||||
internal = true;
|
||||
description = "If set, all options that don't have a declared type will be merged using this type.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
__module__ = {
|
||||
args = {
|
||||
static = args;
|
||||
|
||||
dynamic = {
|
||||
meta = {
|
||||
inherit extend type;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
merged =
|
||||
let
|
||||
collected = collect (args.path or "") ([ internal ] ++ modules) (
|
||||
{
|
||||
inherit options config;
|
||||
|
||||
# The library does not include `extend` out of the box to avoid infinite recursion
|
||||
# in most cases. Instead, we supply an estensible version here for modules to consume.
|
||||
# This is only something that affects internal use of `lib`, the properly exported
|
||||
# `lib` is augmented with an `extend` method.
|
||||
lib = lib.points.withExtend (lib.fp.const lib);
|
||||
}
|
||||
// args
|
||||
);
|
||||
in
|
||||
lib.modules.combine prefix (lib.lists.reverse collected);
|
||||
|
||||
options = merged.matched;
|
||||
|
||||
config =
|
||||
let
|
||||
declared = lib.attrs.mapRecursiveWhen (value: !(lib.types.is "option" value)) (
|
||||
name: value: value.value
|
||||
) options;
|
||||
|
||||
freeform =
|
||||
let
|
||||
definitions = builtins.map (definition: {
|
||||
__file__ = definition.__file__;
|
||||
value = lib.attrs.set definition.prefix definition.value;
|
||||
}) merged.unmatched;
|
||||
in
|
||||
if definitions == [ ] then { } else declared.__module__.freeform.merge prefix definitions;
|
||||
in
|
||||
if declared.__module__.freeform == null then
|
||||
declared
|
||||
else
|
||||
lib.attrs.mergeRecursive freeform declared;
|
||||
|
||||
checked =
|
||||
if config.__module__.check && config.__module__.freeform == null && merged.unmatched != [ ] then
|
||||
let
|
||||
first = builtins.head merged.unmatched;
|
||||
|
||||
identifier = lib.options.getIdentifier (prefix ++ first.prefix);
|
||||
definitions =
|
||||
builtins.addErrorContext
|
||||
"while evaluating the error message for definitions of non-existent option `${identifier}`"
|
||||
(
|
||||
builtins.addErrorContext "while evaluating a definition from `${first.__file__}`" (
|
||||
lib.options.getDefinitions [ first ]
|
||||
)
|
||||
);
|
||||
|
||||
message = "The option `${identifier}` does not exist. Definitions:${definitions}";
|
||||
in
|
||||
if builtins.attrNames options == [ "__module__" ] then
|
||||
if lib.options.getIdentifier prefix == "" then
|
||||
builtins.throw ''
|
||||
${message}
|
||||
|
||||
You are trying to declare options in `config` rather than `options`.
|
||||
''
|
||||
else
|
||||
builtins.throw ''
|
||||
${message}
|
||||
|
||||
There are no options defined in `${lib.options.getIdentifier prefix}`.
|
||||
Are you sure you declared your options correctly?
|
||||
''
|
||||
else
|
||||
builtins.throw message
|
||||
else
|
||||
null;
|
||||
|
||||
withCheck = builtins.seq checked;
|
||||
in
|
||||
{
|
||||
inherit type extend;
|
||||
options = withCheck options;
|
||||
config = withCheck (builtins.removeAttrs config [ "__module__" ]);
|
||||
__module__ = withCheck config.__module__;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,709 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"apply" = {
|
||||
"properties" = {
|
||||
"handles normal values" =
|
||||
let
|
||||
expected = [ { } ];
|
||||
actual = lib.modules.apply.properties { };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles merge" =
|
||||
let
|
||||
expected = [
|
||||
{ x = 1; }
|
||||
{ x = 2; }
|
||||
];
|
||||
actual = lib.modules.apply.properties (
|
||||
lib.modules.merge [
|
||||
{ x = 1; }
|
||||
{ x = 2; }
|
||||
]
|
||||
);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles when" =
|
||||
let
|
||||
expected = [ [ { x = 1; } ] ];
|
||||
actual = lib.modules.apply.properties (lib.modules.when true [ { x = 1; } ]);
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"overrides" = {
|
||||
"handles normal values" =
|
||||
let
|
||||
expected = {
|
||||
highestPriority = 100;
|
||||
values = [ { value = 1; } ];
|
||||
};
|
||||
actual = lib.modules.apply.overrides [ { value = 1; } ];
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles overrides" =
|
||||
let
|
||||
expected = {
|
||||
highestPriority = 100;
|
||||
values = [ { value = "world"; } ];
|
||||
};
|
||||
actual = lib.modules.apply.overrides [
|
||||
{ value = "world"; }
|
||||
{ value = lib.modules.override 101 "hello"; }
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"order" = {
|
||||
"handles normal values" =
|
||||
let
|
||||
expected = [ { } ];
|
||||
actual = lib.modules.apply.order [ { } ];
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles priority" =
|
||||
let
|
||||
expected = [
|
||||
{
|
||||
value = 1;
|
||||
priority = 10;
|
||||
}
|
||||
{
|
||||
value = 3;
|
||||
priority = 50;
|
||||
}
|
||||
{ value = 2; }
|
||||
];
|
||||
actual = lib.modules.apply.order [
|
||||
{
|
||||
value = 1;
|
||||
priority = 10;
|
||||
}
|
||||
{ value = 2; }
|
||||
{
|
||||
value = 3;
|
||||
priority = 50;
|
||||
}
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"fixup" = {
|
||||
"sets default type for option" =
|
||||
let
|
||||
actual = lib.modules.apply.fixup [ ] (lib.options.create { });
|
||||
in
|
||||
actual.type.name == "Unspecified";
|
||||
};
|
||||
|
||||
"invert" = {
|
||||
"inverts merge" =
|
||||
let
|
||||
expected = [
|
||||
{ x = 1; }
|
||||
{ x = 2; }
|
||||
];
|
||||
actual = lib.modules.apply.invert (
|
||||
lib.modules.merge [
|
||||
{ x = 1; }
|
||||
{ x = 2; }
|
||||
]
|
||||
);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"inverts when" =
|
||||
let
|
||||
expected = [
|
||||
{
|
||||
x = {
|
||||
__type__ = "when";
|
||||
condition = true;
|
||||
content = 1;
|
||||
};
|
||||
y = {
|
||||
__type__ = "when";
|
||||
condition = true;
|
||||
content = 2;
|
||||
};
|
||||
}
|
||||
];
|
||||
actual = lib.modules.apply.invert (
|
||||
lib.modules.when true {
|
||||
x = 1;
|
||||
y = 2;
|
||||
}
|
||||
);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"inverts overrides" =
|
||||
let
|
||||
expected = [
|
||||
{
|
||||
x = {
|
||||
__type__ = "override";
|
||||
priority = 100;
|
||||
content = 1;
|
||||
};
|
||||
y = {
|
||||
__type__ = "override";
|
||||
priority = 100;
|
||||
content = 2;
|
||||
};
|
||||
}
|
||||
];
|
||||
actual = lib.modules.apply.invert (
|
||||
lib.modules.override 100 {
|
||||
x = 1;
|
||||
y = 2;
|
||||
}
|
||||
);
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"validate" = {
|
||||
"keys" = {
|
||||
"handles an empty set" =
|
||||
let
|
||||
value = lib.modules.validate.keys { };
|
||||
in
|
||||
value;
|
||||
|
||||
"handles a valid module" =
|
||||
let
|
||||
value = lib.modules.validate.keys {
|
||||
__file__ = "virtual:aux/example";
|
||||
__key__ = "aux/example";
|
||||
includes = [ ];
|
||||
excludes = [ ];
|
||||
options = { };
|
||||
config = { };
|
||||
freeform = null;
|
||||
meta = { };
|
||||
};
|
||||
in
|
||||
value;
|
||||
|
||||
"handles an invalid module" =
|
||||
let
|
||||
value = lib.modules.validate.keys { invalid = null; };
|
||||
in
|
||||
!value;
|
||||
};
|
||||
};
|
||||
|
||||
"normalize" = {
|
||||
"handles an empty set" =
|
||||
let
|
||||
expected = {
|
||||
__file__ = "/aux/example.nix";
|
||||
__key__ = "example";
|
||||
config = { };
|
||||
excludes = [ ];
|
||||
includes = [ ];
|
||||
options = { };
|
||||
};
|
||||
actual = lib.modules.normalize "/aux/example.nix" "example" { };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles an example module" =
|
||||
let
|
||||
expected = {
|
||||
__file__ = "myfile.nix";
|
||||
__key__ = "mykey";
|
||||
config = {
|
||||
x = true;
|
||||
};
|
||||
excludes = [ ];
|
||||
includes = [ ];
|
||||
options = { };
|
||||
};
|
||||
actual = lib.modules.normalize "/aux/example.nix" "example" {
|
||||
__file__ = "myfile.nix";
|
||||
__key__ = "mykey";
|
||||
config.x = true;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"resolve" = {
|
||||
"handles an attribute set" =
|
||||
let
|
||||
expected = {
|
||||
config.x = 1;
|
||||
};
|
||||
actual = lib.modules.resolve "example" { config.x = 1; } { };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles a function" =
|
||||
let
|
||||
expected = {
|
||||
config.x = 1;
|
||||
};
|
||||
actual = lib.modules.resolve "example" (lib.fp.const { config.x = 1; }) { };
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles a function with arguments" =
|
||||
let
|
||||
expected = {
|
||||
config.x = 1;
|
||||
};
|
||||
actual = lib.modules.resolve "example" (args: { config.x = args.x; }) { x = 1; };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"getFiles" = {
|
||||
"gets the files for a list of modules" =
|
||||
let
|
||||
expected = [ "/aux/example.nix" ];
|
||||
actual = lib.modules.getFiles [ { __file__ = "/aux/example.nix"; } ];
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"combine" = {
|
||||
"handles empty modules" =
|
||||
let
|
||||
expected = {
|
||||
matched = { };
|
||||
unmatched = [ ];
|
||||
};
|
||||
actual = lib.modules.combine [ ] [ (lib.modules.normalize "/aux/example.nix" "example" { }) ];
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles a single module" =
|
||||
let
|
||||
expected = {
|
||||
matched = { };
|
||||
unmatched = [
|
||||
{
|
||||
__file__ = "/aux/example.nix";
|
||||
prefix = [ "x" ];
|
||||
value = 1;
|
||||
}
|
||||
];
|
||||
};
|
||||
actual =
|
||||
lib.modules.combine
|
||||
[ ]
|
||||
[
|
||||
(lib.modules.normalize "/aux/example.nix" "example" {
|
||||
config = {
|
||||
x = 1;
|
||||
};
|
||||
})
|
||||
];
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"handles multiple modules" =
|
||||
let
|
||||
unmatched = [
|
||||
{
|
||||
__file__ = "/aux/example2.nix";
|
||||
prefix = [ "y" ];
|
||||
value = 2;
|
||||
}
|
||||
];
|
||||
actual =
|
||||
lib.modules.combine
|
||||
[ ]
|
||||
[
|
||||
(lib.modules.normalize "/aux/example1.nix" "example2" {
|
||||
options = {
|
||||
x = lib.options.create { };
|
||||
};
|
||||
|
||||
config = {
|
||||
x = 1;
|
||||
};
|
||||
})
|
||||
(lib.modules.normalize "/aux/example2.nix" "example2" {
|
||||
config = {
|
||||
y = 2;
|
||||
};
|
||||
})
|
||||
];
|
||||
in
|
||||
(actual.unmatched == unmatched) && actual.matched ? x;
|
||||
};
|
||||
|
||||
"run" = {
|
||||
"empty" =
|
||||
let
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options.aux = {
|
||||
message = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
|
||||
config = {
|
||||
aux.message = "Hello, World!";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated ? config;
|
||||
|
||||
"hello world" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options.aux = {
|
||||
message = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
|
||||
config = {
|
||||
aux.message = "Hello, World!";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
actual = evaluated.config.aux.message;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"recursive" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
(
|
||||
{ config }:
|
||||
{
|
||||
options.aux = {
|
||||
message = lib.options.create { type = lib.types.string; };
|
||||
|
||||
proxy = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
|
||||
config = {
|
||||
aux = {
|
||||
proxy = "Hello, World!";
|
||||
message = config.aux.proxy;
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
actual = evaluated.config.aux.message;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"conditional" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options.aux = {
|
||||
message = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
config = {
|
||||
aux = {
|
||||
message = lib.modules.when true expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == expected;
|
||||
|
||||
"conditional list" =
|
||||
let
|
||||
expected = [ "Hello, World!" ];
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options.aux = {
|
||||
message = lib.options.create { type = lib.types.list.of lib.types.string; };
|
||||
};
|
||||
config = {
|
||||
aux = {
|
||||
message = lib.modules.when true expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == expected;
|
||||
|
||||
"submodules" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
default.value = {
|
||||
message = "hi";
|
||||
};
|
||||
type = lib.types.attrs.of (
|
||||
lib.types.submodule (
|
||||
{ name }:
|
||||
{
|
||||
# freeform = lib.types.any;
|
||||
options.message = lib.options.create { type = lib.types.string; };
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = {
|
||||
x = {
|
||||
message = "Hello, World!";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.x.message == expected;
|
||||
|
||||
"submodules without shorthand" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodules.of {
|
||||
shorthand = false;
|
||||
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = {
|
||||
options = {
|
||||
message2 = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
|
||||
config = {
|
||||
message = expected;
|
||||
message2 = expected;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == evaluated.config.aux.message2;
|
||||
|
||||
"function submodules" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodules.of {
|
||||
shorthand = false;
|
||||
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = args: { config.message = expected; };
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == expected;
|
||||
|
||||
"merges submodules" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodules.of {
|
||||
shorthand = false;
|
||||
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = args: {
|
||||
options.message2 = lib.options.create { type = lib.types.string; };
|
||||
|
||||
config.message = expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
config = {
|
||||
aux.config.message2 = expected;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == evaluated.config.aux.message2;
|
||||
|
||||
"flexible shorthand" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodules.of {
|
||||
shorthand = true;
|
||||
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = args: {
|
||||
options.message2 = lib.options.create { type = lib.types.string; };
|
||||
|
||||
config.message = expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
config = {
|
||||
aux.message2 = expected;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == evaluated.config.aux.message2;
|
||||
|
||||
"base level submodule" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodule { options.message = lib.options.create { type = lib.types.string; }; };
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = args: {
|
||||
options.message2 = lib.options.create { type = lib.types.string; };
|
||||
|
||||
config.message = expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
config = {
|
||||
aux.message2 = expected;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
evaluated.config.aux.message == evaluated.config.aux.message2;
|
||||
|
||||
"base level submodule (freeform)" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodule {
|
||||
freeform = lib.types.any;
|
||||
|
||||
options.message = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = args: {
|
||||
options.message2 = lib.options.create { type = lib.types.string; };
|
||||
|
||||
config.message = expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
config = {
|
||||
aux.exists = true;
|
||||
aux.message2 = expected;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
(evaluated.config.aux.message == evaluated.config.aux.message2) && evaluated.config.aux.exists;
|
||||
|
||||
"nested submodules" =
|
||||
let
|
||||
expected = "Hello, World!";
|
||||
evaluated = lib.modules.run {
|
||||
modules = [
|
||||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
type = lib.types.submodule {
|
||||
freeform = lib.types.any;
|
||||
|
||||
options.message = lib.options.create { type = lib.types.string; };
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
aux = args: {
|
||||
options.message2 = lib.options.create { type = lib.types.string; };
|
||||
|
||||
config.message = expected;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
config = {
|
||||
aux.exists = true;
|
||||
aux.message2 = expected;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
(evaluated.config.aux.message == evaluated.config.aux.message2) && evaluated.config.aux.exists;
|
||||
};
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
lib: {
|
||||
numbers = {
|
||||
into = {
|
||||
## Convert a number into a string.
|
||||
##
|
||||
## @type Int | Float -> String
|
||||
string = value: if builtins.isInt value then builtins.toString value else builtins.toJSON value;
|
||||
|
||||
## Convert a number into a list of digits in the given base.
|
||||
##
|
||||
## @type Int -> Int -> List Int
|
||||
base =
|
||||
base: target:
|
||||
let
|
||||
process =
|
||||
value:
|
||||
let
|
||||
r = value - ((value / base) * base);
|
||||
q = (value - r) / base;
|
||||
in
|
||||
if value < base then [ value ] else [ r ] ++ process q;
|
||||
result = process target;
|
||||
in
|
||||
assert lib.errors.trace (builtins.isInt base) "Base must be an integer.";
|
||||
assert lib.errors.trace (builtins.isInt target) "Target must be an integer.";
|
||||
assert lib.errors.trace (base >= 2) "Base must be at least 2.";
|
||||
assert lib.errors.trace (target >= 0) "Target cannot be negative.";
|
||||
lib.lists.reverse result;
|
||||
|
||||
## Convert a number into a hexadecimal string.
|
||||
##
|
||||
## @type Int -> String
|
||||
hex =
|
||||
value:
|
||||
let
|
||||
serialize =
|
||||
part:
|
||||
if part < 10 then
|
||||
builtins.toString part
|
||||
else if part == 10 then
|
||||
"A"
|
||||
else if part == 11 then
|
||||
"B"
|
||||
else if part == 12 then
|
||||
"C"
|
||||
else if part == 13 then
|
||||
"D"
|
||||
else if part == 14 then
|
||||
"E"
|
||||
else if part == 15 then
|
||||
"F"
|
||||
else
|
||||
builtins.throw "Invalid hex digit.";
|
||||
in
|
||||
lib.strings.concatMap serialize (lib.numbers.into.base 16 value);
|
||||
};
|
||||
|
||||
## Compare two numbers. When the first number is less than the second, -1
|
||||
## is returned. When the first number is greater than the second, 1 is
|
||||
## returned. When the numbers are equal, 0 is returned.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
compare =
|
||||
a: b:
|
||||
if a < b then
|
||||
-1
|
||||
else if a > b then
|
||||
1
|
||||
else
|
||||
0;
|
||||
};
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"into" = {
|
||||
"string" = {
|
||||
"converts an int into a string" =
|
||||
let
|
||||
expected = "1";
|
||||
actual = lib.numbers.into.string 1;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"converts a float into a string" =
|
||||
let
|
||||
expected = "1.0";
|
||||
actual = lib.numbers.into.string 1.0;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"base" = {
|
||||
"converts a number into a given base" =
|
||||
let
|
||||
expected = [
|
||||
1
|
||||
0
|
||||
0
|
||||
];
|
||||
actual = lib.numbers.into.base 2 4;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"hex" = {
|
||||
"converts a number into a hex string" =
|
||||
let
|
||||
expected = "64";
|
||||
actual = lib.numbers.into.hex 100;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"compare" = {
|
||||
"returns -1 when first is less than second" =
|
||||
let
|
||||
expected = -1;
|
||||
actual = lib.numbers.compare 1 2;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns 0 when first is equal to second" =
|
||||
let
|
||||
expected = 0;
|
||||
actual = lib.numbers.compare 1 1;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"returns 1 when first is greater than second" =
|
||||
let
|
||||
expected = 1;
|
||||
actual = lib.numbers.compare 2 1;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,348 +0,0 @@
|
|||
lib: {
|
||||
options = {
|
||||
merge = {
|
||||
## Merge a list of option definitions into a single value.
|
||||
##
|
||||
## @type Location -> List Definition -> Any
|
||||
default =
|
||||
location: definitions:
|
||||
let
|
||||
values = lib.options.getDefinitionValues definitions;
|
||||
first = builtins.elemAt values 0;
|
||||
mergedFunctions = x: lib.options.mergeDefault location (builtins.map (f: f x) values);
|
||||
mergedLists = builtins.concatLists values;
|
||||
mergedAttrs = builtins.foldl' lib.attrs.merge { } values;
|
||||
mergedBools = builtins.any lib.bools.or false values;
|
||||
mergedStrings = lib.strings.concat values;
|
||||
in
|
||||
if builtins.length values == 1 then
|
||||
builtins.elemAt values 0
|
||||
else if builtins.all builtins.isFunction values then
|
||||
mergedFunctions
|
||||
else if builtins.all builtins.isList values then
|
||||
mergedLists
|
||||
else if builtins.all builtins.isAttrs values then
|
||||
mergedAttrs
|
||||
else if builtins.all builtins.isBool values then
|
||||
mergedBools
|
||||
else if builtins.all lib.strings.isString values then
|
||||
mergedStrings
|
||||
else if builtins.all builtins.isInt values && builtins.all (x: x == first) values then
|
||||
first
|
||||
# TODO: Improve this error message to show the location and definitions for the option.
|
||||
else
|
||||
builtins.throw "Cannot merge definitions.";
|
||||
|
||||
## Merge multiple option definitions together.
|
||||
##
|
||||
## @type Location -> Type -> List Definition
|
||||
definitions =
|
||||
location: type: definitions:
|
||||
let
|
||||
identifier = lib.options.getIdentifier location;
|
||||
resolve =
|
||||
definition:
|
||||
let
|
||||
properties = builtins.addErrorContext "while evaluating definitions from `${definition.__file__ or "<unknown>"}`:" (
|
||||
lib.modules.apply.properties definition.value
|
||||
);
|
||||
normalize = value: {
|
||||
__file__ = definition.__file__ or "<unknown>";
|
||||
inherit value;
|
||||
};
|
||||
in
|
||||
builtins.map normalize properties;
|
||||
|
||||
resolved = builtins.concatMap resolve definitions;
|
||||
overridden = lib.modules.apply.overrides resolved;
|
||||
|
||||
values =
|
||||
if builtins.any (definition: lib.types.is "order" definition.value) overridden.values then
|
||||
lib.modules.apply.order overridden.values
|
||||
else
|
||||
overridden.values;
|
||||
|
||||
isDefined = values != [ ];
|
||||
|
||||
invalid = builtins.filter (definition: !(type.check definition.value)) values;
|
||||
|
||||
merged =
|
||||
if isDefined then
|
||||
if builtins.all (definition: type.check definition.value) values then
|
||||
type.merge location values
|
||||
else
|
||||
builtins.throw "A definition for `${identifier}` is not of type `${type.description}`. Definition values:${lib.options.getDefinitions invalid}"
|
||||
else
|
||||
builtins.throw "The option `${identifier}` is used but not defined.";
|
||||
|
||||
optional = if isDefined then { value = merged; } else { };
|
||||
in
|
||||
{
|
||||
inherit
|
||||
isDefined
|
||||
values
|
||||
merged
|
||||
optional
|
||||
;
|
||||
|
||||
raw = {
|
||||
inherit values;
|
||||
inherit (overridden) highestPriority;
|
||||
};
|
||||
};
|
||||
|
||||
## Merge multiple option declarations together.
|
||||
##
|
||||
## @type Location -> List Option
|
||||
declarations =
|
||||
location: options:
|
||||
let
|
||||
merge =
|
||||
result: option:
|
||||
let
|
||||
mergedType = result.type.mergeType option.options.type.functor;
|
||||
isTypeMergeable = mergedType != null;
|
||||
shared = name: option.options ? ${name} && result ? ${name};
|
||||
typeSet = lib.attrs.when ((shared "type") && isTypeMergeable) { type = mergedType; };
|
||||
files = result.declarations;
|
||||
serializedFiles = builtins.concatStringsSep " and " files;
|
||||
typeSubmodules = option.options.type.submodules or null;
|
||||
submodules =
|
||||
if typeSubmodules != null then
|
||||
builtins.map (module: {
|
||||
__file__ = option.__file__;
|
||||
includes = [ module ];
|
||||
}) typeSubmodules
|
||||
++ result.options
|
||||
else
|
||||
result.options;
|
||||
in
|
||||
if
|
||||
shared "default"
|
||||
|| shared "example"
|
||||
|| shared "description"
|
||||
|| shared "apply"
|
||||
|| (shared "type" && !isTypeMergeable)
|
||||
then
|
||||
builtins.throw "The option `${lib.options.getIdentifier location}` in `${option.__file__}` is already declared in ${serializedFiles}"
|
||||
else
|
||||
option.options
|
||||
// result
|
||||
// {
|
||||
declarations = result.declarations ++ [ option.__file__ ];
|
||||
options = submodules;
|
||||
}
|
||||
// typeSet;
|
||||
in
|
||||
builtins.foldl' merge {
|
||||
inherit location;
|
||||
declarations = [ ];
|
||||
options = [ ];
|
||||
} options;
|
||||
|
||||
## Merge an option, only supporting a single unique definition.
|
||||
##
|
||||
## @type String -> Location -> List Definition -> Any
|
||||
unique =
|
||||
message: location: definitions:
|
||||
let
|
||||
identifier = lib.options.getIdentifier location;
|
||||
total = builtins.length definitions;
|
||||
first = builtins.elemAt definitions 0;
|
||||
in
|
||||
if total == 1 then
|
||||
first.value
|
||||
else if total == 0 then
|
||||
builtins.throw "Cannot merge unused option `${identifier}`.\n${message}"
|
||||
else
|
||||
builtins.throw "The option `${identifier}` is defined multiple times, but must be unique.\n${message}\nDefinitions:${lib.options.getDefinitions definitions}";
|
||||
|
||||
## Merge a single instance of an option.
|
||||
##
|
||||
## @type Location -> List Definition -> Any
|
||||
one = lib.options.merge.unique "";
|
||||
|
||||
equal =
|
||||
location: definitions:
|
||||
let
|
||||
identifier = lib.options.getIdentifier location;
|
||||
first = builtins.elemAt definitions 0;
|
||||
rest = builtins.tail definitions;
|
||||
merge =
|
||||
x: y:
|
||||
if x != y then
|
||||
builtins.throw "The option `${identifier}` has conflicting definitions:${lib.options.getDefinitions definitions}"
|
||||
else
|
||||
x;
|
||||
merged = builtins.foldl' merge first rest;
|
||||
in
|
||||
if builtins.length definitions == 0 then
|
||||
builtins.throw "Cannot merge unused option `${identifier}`."
|
||||
else if builtins.length definitions == 1 then
|
||||
first.value
|
||||
else
|
||||
merged.value;
|
||||
};
|
||||
|
||||
## Check whether a value is an option.
|
||||
##
|
||||
## @type Attrs -> Bool
|
||||
is = lib.types.is "option";
|
||||
|
||||
## Create an option.
|
||||
##
|
||||
## @type { type? :: String | Null, apply? :: (a -> b) | Null, default? :: { value :: a, text :: String }, example? :: String | Null, visible? :: Bool | Null, internal? :: Bool | Null, writable? :: Bool | Null, description? :: String | Null } -> Option a
|
||||
create =
|
||||
settings@{
|
||||
type ? lib.types.unspecified,
|
||||
apply ? null,
|
||||
default ? { },
|
||||
example ? null,
|
||||
visible ? null,
|
||||
internal ? null,
|
||||
writable ? null,
|
||||
description ? null,
|
||||
}:
|
||||
{
|
||||
__type__ = "option";
|
||||
inherit
|
||||
type
|
||||
apply
|
||||
default
|
||||
example
|
||||
visible
|
||||
internal
|
||||
writable
|
||||
description
|
||||
;
|
||||
};
|
||||
|
||||
## Create a sink option.
|
||||
##
|
||||
## @type @alias lib.options.create
|
||||
sink =
|
||||
settings:
|
||||
let
|
||||
defaults = {
|
||||
internal = true;
|
||||
visible = false;
|
||||
default = false;
|
||||
description = "A sink option for unused definitions";
|
||||
type = lib.types.create {
|
||||
name = "sink";
|
||||
check = lib.fp.const true;
|
||||
merge = lib.fp.const (lib.fp.const false);
|
||||
};
|
||||
apply = value: builtins.throw "Cannot read the value of a Sink option.";
|
||||
};
|
||||
in
|
||||
lib.options.create (defaults // settings);
|
||||
|
||||
## Get the definition values from a list of options definitions.
|
||||
##
|
||||
## @type List Definition -> Any
|
||||
getDefinitionValues = definitions: builtins.map (definition: definition.value) definitions;
|
||||
|
||||
## Convert a list of option identifiers into a single identifier.
|
||||
##
|
||||
## @type List String -> String
|
||||
getIdentifier =
|
||||
location:
|
||||
let
|
||||
special = [
|
||||
# lib.types.attrs.of (lib.types.submodule {})
|
||||
"<name>"
|
||||
# lib.types.list.of (submodule {})
|
||||
"*"
|
||||
# lib.types.function
|
||||
"<function body>"
|
||||
];
|
||||
escape = part: if builtins.elem part special then part else lib.strings.escape.nix.identifier part;
|
||||
in
|
||||
lib.strings.concatMapSep "." escape location;
|
||||
|
||||
## Get a string message of the definitions for an option.
|
||||
##
|
||||
## @type List Definition -> String
|
||||
getDefinitions =
|
||||
definitions:
|
||||
let
|
||||
serialize =
|
||||
definition:
|
||||
let
|
||||
valueWithRecursionLimit = lib.generators.withRecursion {
|
||||
limit = 10;
|
||||
throw = false;
|
||||
} definition.value;
|
||||
|
||||
eval = builtins.tryEval (lib.generators.pretty { } valueWithRecursionLimit);
|
||||
|
||||
lines = lib.strings.split "\n" eval.value;
|
||||
linesLength = builtins.length lines;
|
||||
firstFiveLines = lib.lists.take 5 lines;
|
||||
|
||||
ellipsis = lib.lists.when (linesLength > 5) "...";
|
||||
|
||||
value = builtins.concatStringsSep "\n " (firstFiveLines ++ ellipsis);
|
||||
|
||||
result =
|
||||
if !eval.success then
|
||||
""
|
||||
else if linesLength > 1 then
|
||||
":\n " + value
|
||||
else
|
||||
": " + value;
|
||||
in
|
||||
"\n- In `${definition.__file__}`${result}";
|
||||
in
|
||||
lib.strings.concatMap serialize definitions;
|
||||
|
||||
## Run a set of definitions, calculating the resolved value and associated information.
|
||||
##
|
||||
## @type Location -> Option -> List Definition -> String & { value :: Any, highestPriority :: Int, isDefined :: Bool, files :: List String, definitions :: List Any, definitionsWithLocations :: List Definition }
|
||||
run =
|
||||
location: option: definitions:
|
||||
let
|
||||
identifier = lib.options.getIdentifier location;
|
||||
|
||||
definitionsWithDefault =
|
||||
if option ? default && option.default ? value then
|
||||
[
|
||||
{
|
||||
__file__ = builtins.head option.declarations;
|
||||
value = lib.modules.overrides.option option.default.value;
|
||||
}
|
||||
]
|
||||
++ definitions
|
||||
else
|
||||
definitions;
|
||||
|
||||
merged =
|
||||
if option.writable or null == false && builtins.length definitionsWithDefault > 1 then
|
||||
let
|
||||
separatedDefinitions = builtins.map (
|
||||
definition:
|
||||
definition
|
||||
// {
|
||||
value = (lib.options.merge.definitions location option.type [ definition ]).merged;
|
||||
}
|
||||
) definitionsWithDefault;
|
||||
in
|
||||
builtins.throw "The option `${identifier}` is not writable, but is set more than once:${lib.options.getDefinitions separatedDefinitions}"
|
||||
else
|
||||
lib.options.merge.definitions location option.type definitionsWithDefault;
|
||||
|
||||
value = if option.apply or null != null then option.apply merged.merged else merged.merged;
|
||||
in
|
||||
option
|
||||
// {
|
||||
value = builtins.addErrorContext "while evaluating the option `${identifier}`:" value;
|
||||
highestPriority = merged.raw.highestPriority;
|
||||
isDefined = merged.isDefined;
|
||||
files = builtins.map (definition: definition.__file__) merged.values;
|
||||
definitions = lib.options.getDefinitionValues merged.values;
|
||||
definitionsWithLocations = merged.values;
|
||||
__toString = identifier;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
|
@ -1,52 +0,0 @@
|
|||
lib: {
|
||||
packages = {
|
||||
## Check whether a value is a derivation. Note that this will also return true
|
||||
## for "fake" derivations which are constructed by helpers such as
|
||||
## `lib.paths.into.drv` for convenience.
|
||||
##
|
||||
## @type a -> Bool
|
||||
isDerivation = value: value.type or null == "derivation";
|
||||
|
||||
## Sanitize a string to produce a valid name for a derivation.
|
||||
##
|
||||
## @type String -> String
|
||||
sanitizeDerivationName =
|
||||
let
|
||||
validate = builtins.match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
|
||||
in
|
||||
value:
|
||||
# First detect the common case of already valid strings, to speed those up
|
||||
if builtins.stringLength value <= 207 && validate value != null then
|
||||
builtins.unsafeDiscardStringContext value
|
||||
else
|
||||
lib.fp.pipe [
|
||||
# Get rid of string context. This is safe under the assumption that the
|
||||
# resulting string is only used as a derivation name
|
||||
builtins.unsafeDiscardStringContext
|
||||
|
||||
# Strip all leading "."
|
||||
(x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0)
|
||||
|
||||
(lib.strings.split "[^[:alnum:]+._?=-]+")
|
||||
|
||||
# Replace invalid character ranges with a "-"
|
||||
(lib.strings.concatMap (x: if builtins.isList x then "-" else x))
|
||||
|
||||
# Limit to 211 characters (minus 4 chars for ".drv")
|
||||
(x: builtins.substring (lib.math.max (builtins.stringLength x - 207) 0) (-1) x)
|
||||
|
||||
# If the result is empty, replace it with "unknown"
|
||||
(x: if builtins.stringLength x == 0 then "unknown" else x)
|
||||
] value;
|
||||
|
||||
## Get an output of a derivation.
|
||||
##
|
||||
## @type String -> Derivation -> String
|
||||
getOutput =
|
||||
output: package:
|
||||
if !package ? outputSpecified || !package.outputSpecified then
|
||||
package.${output} or package.out or package
|
||||
else
|
||||
package;
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
|
@ -1,67 +0,0 @@
|
|||
lib: {
|
||||
paths = {
|
||||
into = {
|
||||
## Convert a path into a derivation.
|
||||
##
|
||||
## @type Path -> Derivation
|
||||
drv =
|
||||
value:
|
||||
let
|
||||
path = builtins.storePath value;
|
||||
result = {
|
||||
type = "derivation";
|
||||
name = lib.packages.sanitizeDerivationName (builtins.substring 33 (-1) (builtins.baseNameOf path));
|
||||
outPath = path;
|
||||
outputs = [ "out" ];
|
||||
outputName = "out";
|
||||
out = result;
|
||||
};
|
||||
in
|
||||
result;
|
||||
};
|
||||
|
||||
validate = {
|
||||
## Check whether a path is contained within the Nix store.
|
||||
##
|
||||
## @type Path -> Bool
|
||||
store =
|
||||
value:
|
||||
if lib.strings.validate.stringifiable value then
|
||||
builtins.substring 0 1 (builtins.toString value) == "/"
|
||||
&& builtins.dirOf (builtins.toString value) == builtins.storeDir
|
||||
else
|
||||
false;
|
||||
};
|
||||
|
||||
## Create a search path from a list of paths.
|
||||
##
|
||||
## @type String -> [String] -> String
|
||||
search =
|
||||
target: paths:
|
||||
lib.strings.concatMapSep ":" (path: path + "/" + target) (
|
||||
builtins.filter (value: value != null) paths
|
||||
);
|
||||
|
||||
## Create a search path from a list of packages.
|
||||
##
|
||||
## @type String -> [Derivation] -> String
|
||||
searchFromOutput =
|
||||
output: target: packages:
|
||||
lib.paths.search target (builtins.map (lib.packages.getOutput output) packages);
|
||||
|
||||
## Create a search path for the binary output of a package.
|
||||
##
|
||||
## @type [Derivation] -> String
|
||||
bin = lib.paths.searchFromOutput "bin" "bin";
|
||||
|
||||
## Create a search path for the library output of a package.
|
||||
##
|
||||
## @type [Derivation] -> String
|
||||
lib = lib.paths.searchFromOutput "lib" "lib";
|
||||
|
||||
## Create a search path for the include output of a package.
|
||||
##
|
||||
## @type [Derivation] -> String
|
||||
include = lib.paths.searchFromOutput "dev" "include";
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
|
@ -1,54 +0,0 @@
|
|||
lib: {
|
||||
points = {
|
||||
## Calculate the fixed point of a function. This will evaluate the function `f`
|
||||
## until its result settles (or Nix's recursion limit is reached). This allows
|
||||
## us to define recursive functions without worrying about the order of their
|
||||
## definitions.
|
||||
##
|
||||
## @type (a -> a) -> a
|
||||
fix =
|
||||
f:
|
||||
let
|
||||
x = f x;
|
||||
in
|
||||
x;
|
||||
|
||||
## Calculate the fixed point of a function. This will evaluate the function `f`
|
||||
## until its result settles (or Nix's recursion limit is reached). This allows
|
||||
## us to define recursive functions without worrying about the order of their
|
||||
## definitions. Unlike `fix`, the resulting value is also given a `__unfix__`
|
||||
## attribute that is set to the original function passed to `fix'`.
|
||||
##
|
||||
## @type (a -> a) -> a & { __unfix__ :: (a -> a) }
|
||||
fix' =
|
||||
f:
|
||||
let
|
||||
x = f x // {
|
||||
__unfix__ = f;
|
||||
};
|
||||
in
|
||||
x;
|
||||
|
||||
## Extend a function's output with an additional function. This is the basis for
|
||||
## features like overlays.
|
||||
##
|
||||
## @type (a -> b -> c) -> (a -> b) -> a -> b & c
|
||||
extends =
|
||||
g: f: self:
|
||||
let
|
||||
previous = f self;
|
||||
next = g self previous;
|
||||
in
|
||||
previous // next;
|
||||
|
||||
## Add an `extend` method to the result of a function.
|
||||
##
|
||||
## @type (a -> b) -> b & { extend :: (a -> b -> c) -> b & c }
|
||||
withExtend =
|
||||
f:
|
||||
let
|
||||
create = self: (f self) // { extend = g: lib.points.withExtend (lib.points.extends g f); };
|
||||
in
|
||||
lib.points.fix' create;
|
||||
};
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"fix" = {
|
||||
"calculates a fixed point" =
|
||||
let
|
||||
expected = {
|
||||
original = 0;
|
||||
calculated = 1;
|
||||
};
|
||||
actual = lib.points.fix (self: {
|
||||
original = 0;
|
||||
calculated = self.original + 1;
|
||||
});
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"fix'" = {
|
||||
"allows unfixing a fixed point" =
|
||||
let
|
||||
expected = {
|
||||
original = 0;
|
||||
calculated = 6;
|
||||
};
|
||||
result = lib.points.fix' (self: {
|
||||
original = 0;
|
||||
calculated = self.original + 1;
|
||||
});
|
||||
actual = result.__unfix__ { original = 5; };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"extends" = {
|
||||
"overlays two functions' return values" =
|
||||
let
|
||||
first = self: previous: { z = 3; };
|
||||
|
||||
second = self: { y = 2; };
|
||||
|
||||
expected = {
|
||||
y = 2;
|
||||
z = 3;
|
||||
};
|
||||
|
||||
actual = lib.points.extends first second { x = 1; };
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
upper = [
|
||||
"A"
|
||||
"B"
|
||||
"C"
|
||||
"D"
|
||||
"E"
|
||||
"F"
|
||||
"G"
|
||||
"H"
|
||||
"I"
|
||||
"J"
|
||||
"K"
|
||||
"L"
|
||||
"M"
|
||||
"N"
|
||||
"O"
|
||||
"P"
|
||||
"Q"
|
||||
"R"
|
||||
"S"
|
||||
"T"
|
||||
"U"
|
||||
"V"
|
||||
"W"
|
||||
"X"
|
||||
"Y"
|
||||
"Z"
|
||||
];
|
||||
|
||||
lower = [
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
"d"
|
||||
"e"
|
||||
"f"
|
||||
"g"
|
||||
"h"
|
||||
"i"
|
||||
"j"
|
||||
"k"
|
||||
"l"
|
||||
"m"
|
||||
"n"
|
||||
"o"
|
||||
"p"
|
||||
"q"
|
||||
"r"
|
||||
"s"
|
||||
"t"
|
||||
"u"
|
||||
"v"
|
||||
"w"
|
||||
"x"
|
||||
"y"
|
||||
"z"
|
||||
];
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
{
|
||||
" " = 32;
|
||||
"!" = 33;
|
||||
"\"" = 34;
|
||||
"#" = 35;
|
||||
"$" = 36;
|
||||
"%" = 37;
|
||||
"&" = 38;
|
||||
"'" = 39;
|
||||
"(" = 40;
|
||||
")" = 41;
|
||||
"*" = 42;
|
||||
"+" = 43;
|
||||
"," = 44;
|
||||
"-" = 45;
|
||||
"." = 46;
|
||||
"/" = 47;
|
||||
"0" = 48;
|
||||
"1" = 49;
|
||||
"2" = 50;
|
||||
"3" = 51;
|
||||
"4" = 52;
|
||||
"5" = 53;
|
||||
"6" = 54;
|
||||
"7" = 55;
|
||||
"8" = 56;
|
||||
"9" = 57;
|
||||
":" = 58;
|
||||
";" = 59;
|
||||
"<" = 60;
|
||||
"=" = 61;
|
||||
">" = 62;
|
||||
"?" = 63;
|
||||
"@" = 64;
|
||||
"A" = 65;
|
||||
"B" = 66;
|
||||
"C" = 67;
|
||||
"D" = 68;
|
||||
"E" = 69;
|
||||
"F" = 70;
|
||||
"G" = 71;
|
||||
"H" = 72;
|
||||
"I" = 73;
|
||||
"J" = 74;
|
||||
"K" = 75;
|
||||
"L" = 76;
|
||||
"M" = 77;
|
||||
"N" = 78;
|
||||
"O" = 79;
|
||||
"P" = 80;
|
||||
"Q" = 81;
|
||||
"R" = 82;
|
||||
"S" = 83;
|
||||
"T" = 84;
|
||||
"U" = 85;
|
||||
"V" = 86;
|
||||
"W" = 87;
|
||||
"X" = 88;
|
||||
"Y" = 89;
|
||||
"Z" = 90;
|
||||
"[" = 91;
|
||||
"\\" = 92;
|
||||
"]" = 93;
|
||||
"^" = 94;
|
||||
"_" = 95;
|
||||
"`" = 96;
|
||||
"a" = 97;
|
||||
"b" = 98;
|
||||
"c" = 99;
|
||||
"d" = 100;
|
||||
"e" = 101;
|
||||
"f" = 102;
|
||||
"g" = 103;
|
||||
"h" = 104;
|
||||
"i" = 105;
|
||||
"j" = 106;
|
||||
"k" = 107;
|
||||
"l" = 108;
|
||||
"m" = 109;
|
||||
"n" = 110;
|
||||
"o" = 111;
|
||||
"p" = 112;
|
||||
"q" = 113;
|
||||
"r" = 114;
|
||||
"s" = 115;
|
||||
"t" = 116;
|
||||
"u" = 117;
|
||||
"v" = 118;
|
||||
"w" = 119;
|
||||
"x" = 120;
|
||||
"y" = 121;
|
||||
"z" = 122;
|
||||
"{" = 123;
|
||||
"|" = 124;
|
||||
"}" = 125;
|
||||
"~" = 126;
|
||||
}
|
|
@ -1,342 +0,0 @@
|
|||
lib: {
|
||||
strings = {
|
||||
into = {
|
||||
## Convert a character to an integer.
|
||||
##
|
||||
## @type String -> Integer
|
||||
int = char: builtins.getAttr char lib.strings.ascii;
|
||||
|
||||
## Convert a string into a list of characters.
|
||||
##
|
||||
## @type String -> List String
|
||||
chars =
|
||||
value:
|
||||
let
|
||||
range = lib.lists.range 0 (builtins.stringLength value - 1);
|
||||
pick = index: builtins.substring index 1 value;
|
||||
in
|
||||
builtins.map pick range;
|
||||
|
||||
shell = {
|
||||
## Convert a value into a shell variable.
|
||||
##
|
||||
## @type String -> Any -> String
|
||||
var =
|
||||
name: target:
|
||||
let
|
||||
baseVar = "${name}=${lib.strings.escape.shell.arg target}";
|
||||
listVar = "declare -a ${name}=(${lib.strings.escape.shell.args target})";
|
||||
attrsVar = "declare -A ${name}=(${
|
||||
builtins.concatStringsSep " " (
|
||||
lib.attrs.mapToList (
|
||||
k: v: "[${lib.strings.escape.shell.arg k}]=${lib.strings.escape.shell.arg v}"
|
||||
) target
|
||||
)
|
||||
})";
|
||||
in
|
||||
assert lib.errors.trace (lib.strings.validate.posix name) "Invalid shell variable name: ${name}";
|
||||
if builtins.isAttrs target && !lib.strings.validate.stringifiable target then
|
||||
attrsVar
|
||||
else if builtins.isList target then
|
||||
listVar
|
||||
else
|
||||
baseVar;
|
||||
|
||||
## Create shell variables for a map of values.
|
||||
##
|
||||
## @type Attrs -> String
|
||||
vars =
|
||||
target: builtins.concatStringsSep "\n" (lib.attrs.mapToList lib.strings.into.shell.var target);
|
||||
};
|
||||
};
|
||||
|
||||
escape = {
|
||||
## Escape parts of a string.
|
||||
##
|
||||
## @type List String -> String -> String
|
||||
any =
|
||||
patterns: source:
|
||||
let
|
||||
escaped = builtins.map (x: "\\${x}") patterns;
|
||||
replacer = builtins.replaceStrings patterns escaped;
|
||||
in
|
||||
replacer source;
|
||||
|
||||
## Escape a given set of characters in a string using their
|
||||
## ASCII code prefixed with "\x".
|
||||
##
|
||||
## @type List String -> String
|
||||
c =
|
||||
list:
|
||||
let
|
||||
serialize =
|
||||
char:
|
||||
let
|
||||
hex = lib.numbers.into.hex (lib.strings.into.int char);
|
||||
in
|
||||
"\\x${lib.strings.lower hex}";
|
||||
in
|
||||
builtins.replaceStrings list (builtins.map serialize list);
|
||||
|
||||
nix = {
|
||||
## Escape a string of Nix code.
|
||||
##
|
||||
## @type String -> String
|
||||
value = value: lib.strings.escape.any [ "$" ] (builtins.toJSON value);
|
||||
|
||||
## Escape a string for use as a Nix identifier.
|
||||
##
|
||||
## @type String -> String
|
||||
identifier =
|
||||
value:
|
||||
if builtins.match "[a-zA-Z_][a-zA-Z0-9_'-]*" value != null then
|
||||
value
|
||||
else
|
||||
lib.strings.escape.nix.value value;
|
||||
};
|
||||
|
||||
## Escape a string for use in a regular expression.
|
||||
##
|
||||
## @type String -> String
|
||||
regex = lib.strings.escape.any (lib.strings.into.chars "\\[{()^$?*+|.");
|
||||
|
||||
## Escape a string for use in XML.
|
||||
##
|
||||
## @type String -> String
|
||||
xml =
|
||||
builtins.replaceStrings
|
||||
[
|
||||
"\""
|
||||
"'"
|
||||
"<"
|
||||
">"
|
||||
"&"
|
||||
]
|
||||
[
|
||||
"""
|
||||
"'"
|
||||
"<"
|
||||
">"
|
||||
"&"
|
||||
];
|
||||
|
||||
shell = {
|
||||
## Escape a string for use as a shell argument.
|
||||
##
|
||||
## @type String -> String
|
||||
arg = value: "'${builtins.replaceStrings [ "'" ] [ "'\\''" ] (builtins.toString value)}'";
|
||||
|
||||
## Escape multiple strings for use as shell arguments.
|
||||
##
|
||||
## @type List String -> String
|
||||
args = lib.strings.concatMapSep " " lib.strings.escape.shell.arg;
|
||||
};
|
||||
};
|
||||
|
||||
validate = {
|
||||
## Check if a string is a valid POSIX identifier.
|
||||
##
|
||||
## @type String -> Bool
|
||||
posix = name: builtins.match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
|
||||
|
||||
## Check if a value can be used as a string.
|
||||
##
|
||||
## @type Any -> Bool
|
||||
stringifiable =
|
||||
value: builtins.isString value || builtins.isPath value || value ? outPath || value ? __toString;
|
||||
|
||||
## Check whether a string is empty. This includes strings that
|
||||
## only contain whitespace.
|
||||
##
|
||||
## @type String -> Bool
|
||||
empty = value: builtins.match "[ \t\n]*" value != null;
|
||||
};
|
||||
|
||||
order = {
|
||||
## Create an ordered string that will be placed anywhere.
|
||||
##
|
||||
## @type String -> OrderedString
|
||||
anywhere = value: {
|
||||
inherit value;
|
||||
deps = [ ];
|
||||
};
|
||||
|
||||
## Create an ordered string that will be placed before its dependencies.
|
||||
##
|
||||
## @type String -> List String -> OrderedString
|
||||
after = deps: value: { inherit deps value; };
|
||||
|
||||
## Apply the order for a list of ordered strings. This function also supports
|
||||
## plain strings in the list so long as they have an accompanying static definition
|
||||
## provided.
|
||||
##
|
||||
## @type Attrs -> List (OrderedString | String)
|
||||
apply =
|
||||
static: items:
|
||||
let
|
||||
process =
|
||||
complete: remaining:
|
||||
let
|
||||
next = builtins.head remaining;
|
||||
|
||||
before = process complete next.deps;
|
||||
after = process before.complete (builtins.tail remaining);
|
||||
in
|
||||
if builtins.length remaining == 0 then
|
||||
{
|
||||
inherit complete;
|
||||
result = [ ];
|
||||
}
|
||||
else if builtins.isAttrs next then
|
||||
{
|
||||
inherit complete;
|
||||
result = before.result ++ [ next.value ] ++ after.result;
|
||||
}
|
||||
else
|
||||
process (complete // { "${next}" = true; }) ([ static.${next} ] ++ builtins.tail remaining);
|
||||
|
||||
processed = process { } items;
|
||||
in
|
||||
processed.result;
|
||||
};
|
||||
|
||||
## Return a given string if a condition is true, otherwise return
|
||||
## an empty string.
|
||||
##
|
||||
## @type Bool -> String -> String
|
||||
when = condition: value: if condition then value else "";
|
||||
|
||||
## A table of ASCII characters mapped to their integer character code.
|
||||
##
|
||||
## @type Attrs
|
||||
ascii = import ./ascii.nix;
|
||||
|
||||
## Lists of both upper and lower case ASCII characters.
|
||||
##
|
||||
## @type { upper :: List String, lower :: List String }
|
||||
alphabet = import ./alphabet.nix;
|
||||
|
||||
## Concatenate a list of strings together.
|
||||
##
|
||||
## @type List String -> String
|
||||
concat = builtins.concatStringsSep "";
|
||||
|
||||
## Concatenate and map a list of strings together.
|
||||
##
|
||||
## @type List String -> String
|
||||
concatMap = lib.strings.concatMapSep "";
|
||||
|
||||
## Concatenate and map a list of strings together with a separator.
|
||||
##
|
||||
## @type String -> (a -> String) -> List a -> String
|
||||
concatMapSep =
|
||||
separator: f: list:
|
||||
builtins.concatStringsSep separator (builtins.map f list);
|
||||
|
||||
## Change a string to uppercase.
|
||||
##
|
||||
## @type String -> String
|
||||
upper = builtins.replaceStrings lib.strings.alphabet.lower lib.strings.alphabet.upper;
|
||||
|
||||
## Change a string to lowercase.
|
||||
##
|
||||
## @type String -> String
|
||||
lower = builtins.replaceStrings lib.strings.alphabet.upper lib.strings.alphabet.lower;
|
||||
|
||||
## Add the context of one string to another.
|
||||
##
|
||||
## @type String -> String -> String
|
||||
withContext = context: value: builtins.substring 0 0 context + value;
|
||||
|
||||
## Split a string by a separator.
|
||||
##
|
||||
## @type String -> String -> List String
|
||||
split =
|
||||
separator: value:
|
||||
let
|
||||
escaped = lib.strings.escape.regex (builtins.toString separator);
|
||||
raw = builtins.split escaped (builtins.toString value);
|
||||
parts = builtins.filter builtins.isString raw;
|
||||
in
|
||||
builtins.map (lib.strings.withContext value) parts;
|
||||
|
||||
## Check if a string starts with a given prefix.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
hasPrefix =
|
||||
prefix: value:
|
||||
let
|
||||
text = builtins.substring 0 (builtins.stringLength prefix) value;
|
||||
in
|
||||
text == prefix;
|
||||
|
||||
## Check if a string ends with a given suffix.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
hasSuffix =
|
||||
suffix: value:
|
||||
let
|
||||
valueLength = builtins.stringLength value;
|
||||
suffixLength = builtins.stringLength suffix;
|
||||
text = builtins.substring (valueLength - suffixLength) valueLength value;
|
||||
in
|
||||
(valueLength >= suffixLength) && text == suffix;
|
||||
|
||||
## Check if a string contains a given infix.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
hasInfix = infix: value: builtins.match ".*${lib.strings.escape.regex infix}.*" "${value}" != null;
|
||||
|
||||
## Remove a prefix from a string if it exists.
|
||||
##
|
||||
## @type String -> String -> String
|
||||
removePrefix =
|
||||
prefix: value:
|
||||
let
|
||||
prefixLength = builtins.stringLength prefix;
|
||||
valueLength = builtins.stringLength value;
|
||||
in
|
||||
if lib.strings.hasPrefix prefix value then
|
||||
builtins.substring prefixLength (valueLength - prefixLength) value
|
||||
else
|
||||
value;
|
||||
|
||||
## Remove a suffix from a string if it exists.
|
||||
##
|
||||
## @type String -> String -> String
|
||||
removeSuffix =
|
||||
suffix: value:
|
||||
let
|
||||
suffixLength = builtins.stringLength suffix;
|
||||
valueLength = builtins.stringLength value;
|
||||
in
|
||||
if lib.strings.hasSuffix suffix value then
|
||||
builtins.substring 0 (valueLength - suffixLength) value
|
||||
else
|
||||
value;
|
||||
|
||||
## Pad the start of a string with a character until it reaches
|
||||
## a given length.
|
||||
##
|
||||
## @type Integer -> String -> String
|
||||
padStart =
|
||||
length: char: value:
|
||||
let
|
||||
valueLength = builtins.stringLength value;
|
||||
padding = builtins.genList (_: char) (length - valueLength);
|
||||
in
|
||||
if valueLength < length then (builtins.concatStringsSep "" padding) + value else value;
|
||||
|
||||
## Pad the end of a string with a character until it reaches
|
||||
## a given length.
|
||||
##
|
||||
## @type Integer -> String -> String
|
||||
padEnd =
|
||||
length: char: value:
|
||||
let
|
||||
valueLength = builtins.stringLength value;
|
||||
padding = builtins.genList (_: char) (length - valueLength);
|
||||
in
|
||||
if valueLength < length then value + (builtins.concatStringsSep "" padding) else value;
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
File diff suppressed because it is too large
Load diff
|
@ -1,55 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{
|
||||
"PortableSubmodule" = {
|
||||
"is portable" =
|
||||
let
|
||||
submodule =
|
||||
{ config }:
|
||||
{
|
||||
options = {
|
||||
primitive = lib.options.create {
|
||||
type = lib.types.string;
|
||||
default.value = "<default>";
|
||||
};
|
||||
|
||||
computed = lib.options.create {
|
||||
type = lib.types.string;
|
||||
default.value = "computed: ${config.primitive}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
option = lib.options.create {
|
||||
default.value = { };
|
||||
type = lib.types.submodules.portable {
|
||||
module = submodule;
|
||||
};
|
||||
};
|
||||
|
||||
moduleA = {
|
||||
options.a = option;
|
||||
};
|
||||
moduleB =
|
||||
{ config }:
|
||||
{
|
||||
options.b = option;
|
||||
|
||||
config.b = config.a;
|
||||
};
|
||||
moduleC = {
|
||||
config.b.primitive = "custom";
|
||||
};
|
||||
|
||||
result = lib.modules.run {
|
||||
modules = [
|
||||
moduleA
|
||||
moduleB
|
||||
moduleC
|
||||
];
|
||||
};
|
||||
in
|
||||
result.config.b.computed == "computed: custom";
|
||||
};
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
lib: {
|
||||
versions = {
|
||||
## Check if a version is greater than another.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
gt = first: second: builtins.compareVersions first second == -1;
|
||||
|
||||
## Check if a version is greater than or equal to another.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
gte = first: second: builtins.compareVersions first second != 1;
|
||||
|
||||
## Check if a version is less than another.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
lt = first: second: builtins.compareVersions first second == 1;
|
||||
|
||||
## Check if a version is less than or equal to another.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
lte = first: second: builtins.compareVersions first second != -1;
|
||||
|
||||
## Check if a version is equal to another.
|
||||
##
|
||||
## @type String -> String -> Bool
|
||||
eq = first: second: builtins.compareVersions first second == 0;
|
||||
|
||||
## Get the major version from a version string.
|
||||
##
|
||||
## @type String -> String
|
||||
major =
|
||||
version:
|
||||
let
|
||||
parts = builtins.splitVersion version;
|
||||
in
|
||||
builtins.elemAt parts 0;
|
||||
|
||||
## Get the minor version from a version string.
|
||||
##
|
||||
## @type String -> String
|
||||
minor =
|
||||
version:
|
||||
let
|
||||
parts = builtins.splitVersion version;
|
||||
in
|
||||
builtins.elemAt parts 1;
|
||||
|
||||
## Get the patch version from a version string.
|
||||
##
|
||||
## @type String -> String
|
||||
patch =
|
||||
version:
|
||||
let
|
||||
parts = builtins.splitVersion version;
|
||||
in
|
||||
builtins.elemAt parts 2;
|
||||
};
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in
|
||||
{ }
|
18
lib/test.sh
18
lib/test.sh
|
@ -1,18 +0,0 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p jq
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
namespace=${1:-}
|
||||
|
||||
if [ -z "$namespace" ]; then
|
||||
nix eval -f ./default.test.nix --show-trace --raw
|
||||
else
|
||||
if [ -d "./src/$namespace" ]; then
|
||||
nix eval -f "./src/$namespace/default.test.nix" --show-trace --json | jq
|
||||
else
|
||||
echo "Namespace $namespace not found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
lib ? import ./../lib,
|
||||
foundation ? import ./../foundation { system = "i686-linux"; },
|
||||
}:
|
||||
let
|
||||
pins = import ./npins;
|
||||
lib = import pins.lib;
|
||||
|
||||
modules = import ./src/modules.nix;
|
||||
|
||||
result = lib.modules.run {
|
||||
|
|
34
tidepool/flake.lock
generated
34
tidepool/flake.lock
generated
|
@ -1,18 +1,12 @@
|
|||
{
|
||||
"nodes": {
|
||||
"foundation": {
|
||||
"inputs": {
|
||||
"lib": [
|
||||
"lib"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"dir": "foundation",
|
||||
"lastModified": 1724190751,
|
||||
"narHash": "sha256-e8sOmeXS9YWuQqjW6gvtS3PIueazkf4S1iiJ/94eXv4=",
|
||||
"ref": "master",
|
||||
"rev": "4b36a5a0133f5481840bdfaf693c971215a7ef1f",
|
||||
"revCount": 84,
|
||||
"dirtyRev": "c30f94972c84c5c758b705e4363f624e75560916-dirty",
|
||||
"dirtyShortRev": "c30f949-dirty",
|
||||
"lastModified": 1742785479,
|
||||
"narHash": "sha256-jYjvytd4Ut1z3izUuGCfq2KKjHLgWYvhMtPtyRh4FR8=",
|
||||
"type": "git",
|
||||
"url": "file:../"
|
||||
},
|
||||
|
@ -22,27 +16,9 @@
|
|||
"url": "file:../"
|
||||
}
|
||||
},
|
||||
"lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1724190751,
|
||||
"narHash": "sha256-e8sOmeXS9YWuQqjW6gvtS3PIueazkf4S1iiJ/94eXv4=",
|
||||
"ref": "master",
|
||||
"rev": "4b36a5a0133f5481840bdfaf693c971215a7ef1f",
|
||||
"revCount": 84,
|
||||
"type": "git",
|
||||
"url": "file:../"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"type": "git",
|
||||
"url": "file:../"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"foundation": "foundation",
|
||||
"lib": "lib"
|
||||
"foundation": "foundation"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
{
|
||||
inputs = {
|
||||
lib = {
|
||||
url = "git+file:../?dir=lib";
|
||||
};
|
||||
foundation = {
|
||||
url = "git+file:../?dir=foundation";
|
||||
inputs.lib.follows = "lib";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -13,7 +9,6 @@
|
|||
inputs:
|
||||
let
|
||||
exports = import ./default.nix {
|
||||
lib = inputs.lib.lib;
|
||||
foundation = inputs.foundation.packages.i686-linux;
|
||||
};
|
||||
in
|
||||
|
|
80
tidepool/npins/default.nix
Normal file
80
tidepool/npins/default.nix
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Generated by npins. Do not modify; will be overwritten regularly
|
||||
let
|
||||
data = builtins.fromJSON (builtins.readFile ./sources.json);
|
||||
version = data.version;
|
||||
|
||||
mkSource =
|
||||
spec:
|
||||
assert spec ? type;
|
||||
let
|
||||
path =
|
||||
if spec.type == "Git" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "GitRelease" then
|
||||
mkGitSource spec
|
||||
else if spec.type == "PyPi" then
|
||||
mkPyPiSource spec
|
||||
else if spec.type == "Channel" then
|
||||
mkChannelSource spec
|
||||
else
|
||||
builtins.throw "Unknown source type ${spec.type}";
|
||||
in
|
||||
spec // { outPath = path; };
|
||||
|
||||
mkGitSource =
|
||||
{
|
||||
repository,
|
||||
revision,
|
||||
url ? null,
|
||||
hash,
|
||||
branch ? null,
|
||||
...
|
||||
}:
|
||||
assert repository ? type;
|
||||
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
|
||||
# In the latter case, there we will always be an url to the tarball
|
||||
if url != null then
|
||||
(builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash; # FIXME: check nix version & use SRI hashes
|
||||
})
|
||||
else
|
||||
assert repository.type == "Git";
|
||||
let
|
||||
urlToName =
|
||||
url: rev:
|
||||
let
|
||||
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
|
||||
|
||||
short = builtins.substring 0 7 rev;
|
||||
|
||||
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
|
||||
in
|
||||
"${if matched == null then "source" else builtins.head matched}${appendShort}";
|
||||
name = urlToName repository.url revision;
|
||||
in
|
||||
builtins.fetchGit {
|
||||
url = repository.url;
|
||||
rev = revision;
|
||||
inherit name;
|
||||
# hash = hash;
|
||||
};
|
||||
|
||||
mkPyPiSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchurl {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
|
||||
mkChannelSource =
|
||||
{ url, hash, ... }:
|
||||
builtins.fetchTarball {
|
||||
inherit url;
|
||||
sha256 = hash;
|
||||
};
|
||||
in
|
||||
if version == 3 then
|
||||
builtins.mapAttrs (_: mkSource) data.pins
|
||||
else
|
||||
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
|
16
tidepool/npins/sources.json
Normal file
16
tidepool/npins/sources.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"pins": {
|
||||
"lib": {
|
||||
"type": "Git",
|
||||
"repository": {
|
||||
"type": "Git",
|
||||
"url": "git+ssh://forgejo@git.auxolotl.org/auxolotl/lib.git"
|
||||
},
|
||||
"branch": "main",
|
||||
"revision": "7552ab48bb394d59d2bf1f7a558d28ce59da524d",
|
||||
"url": null,
|
||||
"hash": "0705fm00k9f95b6idf5qnfvqm4qf1a0cv966ghgd48kd1qy4il5c"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
Loading…
Add table
Reference in a new issue