chore: initial commit
This commit is contained in:
parent
b7456195bd
commit
f7d6c846f6
34 changed files with 1760 additions and 105 deletions
lib
README.mddefault.test.nixtest.sh
src
attrs
bools
default.nixerrors
fp
generators
importers
lists
math
modules
numbers
options
packages
paths
points
strings
types
versions
|
@ -75,12 +75,40 @@ in
|
|||
}
|
||||
```
|
||||
|
||||
Successful tests will return `true` while failing test will resolve with `false`.
|
||||
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`.
|
||||
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
|
||||
|
||||
|
|
107
lib/default.test.nix
Normal file
107
lib/default.test.nix
Normal file
|
@ -0,0 +1,107 @@
|
|||
let
|
||||
lib = import ./default.nix;
|
||||
|
||||
root = ./.;
|
||||
|
||||
files = [
|
||||
./src/attrs/default.test.nix
|
||||
./src/bools/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}
|
||||
''
|
|
@ -13,16 +13,18 @@ lib: {
|
|||
mergeRecursiveUntil = predicate: x: y: let
|
||||
process = path:
|
||||
builtins.zipAttrsWith (
|
||||
key: values: let
|
||||
currentPath = path ++ [key];
|
||||
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
|
||||
if isSingleValue
|
||||
then builtins.elemAt values 0
|
||||
else if isComplete
|
||||
then builtins.elemAt values 1
|
||||
else process currentPath values
|
||||
);
|
||||
in
|
||||
|
@ -43,13 +45,13 @@ lib: {
|
|||
##
|
||||
## @type (List String) -> a -> Attrs -> a | b
|
||||
select = path: fallback: target: let
|
||||
key = builtins.head path;
|
||||
name = builtins.head path;
|
||||
rest = builtins.tail path;
|
||||
in
|
||||
if path == []
|
||||
then target
|
||||
else if target ? ${key}
|
||||
then lib.attrs.select rest fallback target.${key}
|
||||
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,
|
||||
|
@ -61,10 +63,12 @@ lib: {
|
|||
error = builtins.throw "Path not found in attribute set: ${pathAsString}";
|
||||
in
|
||||
if lib.attrs.has path target
|
||||
then lib.attrs.select path target
|
||||
then lib.attrs.select path null target
|
||||
else error;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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:
|
||||
|
@ -80,13 +84,13 @@ lib: {
|
|||
##
|
||||
## @type (List String) -> Attrs -> Bool
|
||||
has = path: target: let
|
||||
key = builtins.head path;
|
||||
name = builtins.head path;
|
||||
rest = builtins.tail path;
|
||||
in
|
||||
if path == []
|
||||
then true
|
||||
else if target ? ${key}
|
||||
then lib.attrs.has rest target.${key}
|
||||
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
|
||||
|
@ -98,43 +102,46 @@ lib: {
|
|||
then value
|
||||
else {};
|
||||
|
||||
## Map an attribute set's keys and values to a list.
|
||||
## 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 (key: f key target.${key}) (builtins.attrNames target);
|
||||
builtins.map (name: f name target.${name}) (builtins.attrNames target);
|
||||
|
||||
# TODO: Document this.
|
||||
## 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;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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 (
|
||||
key: value:
|
||||
name: value:
|
||||
if builtins.isAttrs value && predicate value
|
||||
then process (path ++ [key]) value
|
||||
else f (path ++ [key]) value
|
||||
then process (path ++ [name]) value
|
||||
else f (path ++ [name]) value
|
||||
);
|
||||
in
|
||||
process [] target;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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
|
||||
keys = builtins.attrNames target;
|
||||
process = key: let
|
||||
value = target.${key};
|
||||
names = builtins.attrNames target;
|
||||
process = name: let
|
||||
value = target.${name};
|
||||
in
|
||||
if predicate key value
|
||||
then [
|
||||
{
|
||||
name = key;
|
||||
value = value;
|
||||
}
|
||||
]
|
||||
if predicate name value
|
||||
then [{inherit name value;}]
|
||||
else [];
|
||||
valid = builtins.concatMap process keys;
|
||||
valid = builtins.concatMap process names;
|
||||
in
|
||||
builtins.listToAttrs valid;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,79 @@
|
|||
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";
|
||||
|
@ -13,5 +86,244 @@ in {
|
|||
};
|
||||
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,10 +1,21 @@
|
|||
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
|
||||
|
|
161
lib/src/bools/default.test.nix
Normal file
161
lib/src/bools/default.test.nix
Normal file
|
@ -0,0 +1,161 @@
|
|||
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;
|
||||
};
|
||||
}
|
|
@ -40,8 +40,8 @@ let
|
|||
mergeAttrsRecursiveUntil = predicate: x: y: let
|
||||
process = path:
|
||||
builtins.zipAttrsWith (
|
||||
key: values: let
|
||||
currentPath = path ++ [key];
|
||||
name: values: let
|
||||
currentPath = path ++ [name];
|
||||
isSingleValue = builtins.length values == 1;
|
||||
isComplete =
|
||||
predicate currentPath
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
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
|
||||
|
|
3
lib/src/errors/default.test.nix
Normal file
3
lib/src/errors/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
|
@ -21,7 +21,7 @@ lib: {
|
|||
##
|
||||
## @type (List (Any -> Any)) -> Any -> Any
|
||||
pipe = fs: (
|
||||
x: builtins.foldl' (value: f: f x) x fs
|
||||
x: builtins.foldl' (value: f: f value) x fs
|
||||
);
|
||||
|
||||
## Reverse the order of arguments to a function that has two parameters.
|
||||
|
@ -37,13 +37,20 @@ lib: {
|
|||
## @type (a -> b -> c -> d -> e) -> d -> c -> b -> a -> e
|
||||
flip4 = f: a: b: c: d: f d c b a;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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)
|
||||
then f.__args__ or (lib.fp.args (f.__functor f))
|
||||
else builtins.functionArgs f;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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;
|
||||
|
|
139
lib/src/fp/default.test.nix
Normal file
139
lib/src/fp/default.test.nix
Normal file
|
@ -0,0 +1,139 @@
|
|||
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;
|
||||
};
|
||||
}
|
113
lib/src/generators/default.test.nix
Normal file
113
lib/src/generators/default.test.nix
Normal file
|
@ -0,0 +1,113 @@
|
|||
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;
|
||||
};
|
||||
}
|
|
@ -2,11 +2,13 @@ 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);
|
||||
};
|
||||
|
|
3
lib/src/importers/default.test.nix
Normal file
3
lib/src/importers/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
|
@ -1,7 +1,11 @@
|
|||
lib: {
|
||||
lists = {
|
||||
from = {
|
||||
# TODO: Document this.
|
||||
## 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
|
||||
|
@ -11,7 +15,7 @@ lib: {
|
|||
sort = {
|
||||
## Perform a natural sort on a list of strings.
|
||||
##
|
||||
## @type List -> List
|
||||
## @type List String -> List String
|
||||
natural = list: let
|
||||
vectorize = string: let
|
||||
serialize = part:
|
||||
|
@ -27,21 +31,29 @@ lib: {
|
|||
builtins.map (x: builtins.elemAt x 1) (builtins.sort isLess prepared);
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## 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);
|
||||
|
||||
# TODO: Document this.
|
||||
## 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.
|
||||
## 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 -> Int) -> List a -> List b -> Int
|
||||
## @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
|
||||
|
@ -60,7 +72,7 @@ lib: {
|
|||
##
|
||||
## @type List a -> a
|
||||
last = list:
|
||||
assert lib.assertMsg (list != []) "List cannot be empty";
|
||||
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.
|
||||
|
@ -137,7 +149,9 @@ lib: {
|
|||
else [value]
|
||||
else [];
|
||||
|
||||
# TODO: Document this.
|
||||
## 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:
|
||||
|
@ -148,7 +162,9 @@ lib: {
|
|||
0
|
||||
list;
|
||||
|
||||
# TODO: Document this.
|
||||
## Remove duplicate items from a list.
|
||||
##
|
||||
## @type List -> List
|
||||
unique = list: let
|
||||
filter = result: value:
|
||||
if builtins.elem value result
|
||||
|
|
174
lib/src/lists/default.test.nix
Normal file
174
lib/src/lists/default.test.nix
Normal file
|
@ -0,0 +1,174 @@
|
|||
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,12 +1,16 @@
|
|||
lib: {
|
||||
math = {
|
||||
# TODO: Document this.
|
||||
## Return the smaller of two numbers.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
min = x: y:
|
||||
if x < y
|
||||
then x
|
||||
else y;
|
||||
|
||||
# TODO: Document this.
|
||||
## Return the larger of two numbers.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
max = x: y:
|
||||
if x > y
|
||||
then x
|
||||
|
|
19
lib/src/math/default.test.nix
Normal file
19
lib/src/math/default.test.nix
Normal file
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
};
|
||||
}
|
|
@ -3,6 +3,7 @@ lib: {
|
|||
from = {
|
||||
## Create a module from a JSON file.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> Module
|
||||
json = file: {
|
||||
__file__ = file;
|
||||
|
@ -11,6 +12,7 @@ lib: {
|
|||
|
||||
## Create a module from a TOML file.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> Module
|
||||
toml = file: {
|
||||
__file__ = file;
|
||||
|
@ -19,7 +21,12 @@ lib: {
|
|||
};
|
||||
|
||||
apply = {
|
||||
# TODO: Document this.
|
||||
## 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
|
||||
|
@ -32,7 +39,11 @@ lib: {
|
|||
else []
|
||||
else [definition];
|
||||
|
||||
# TODO: Document this.
|
||||
## 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
|
||||
|
@ -64,7 +75,9 @@ lib: {
|
|||
definitions;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## Apply ordering for prioritized definitions.
|
||||
##
|
||||
## @type List Definition -> List Definition
|
||||
order = definitions: let
|
||||
normalize = definition:
|
||||
if lib.types.is "order" definition
|
||||
|
@ -80,7 +93,10 @@ lib: {
|
|||
in
|
||||
builtins.sort compare normalized;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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.getSubModules or null == null
|
||||
then
|
||||
|
@ -95,32 +111,43 @@ lib: {
|
|||
options = [];
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## 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 (key: value: lib.modules.when config.condition value))
|
||||
(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 (key: value: lib.modules.override config.priority value))
|
||||
(builtins.mapAttrs (name: value: lib.modules.override config.priority value))
|
||||
(lib.modules.apply.invert config.content)
|
||||
else [config];
|
||||
};
|
||||
|
||||
validate = {
|
||||
# TODO: Document this.
|
||||
## Check that a module only specifies supported attributes.
|
||||
##
|
||||
## @type Attrs -> Bool
|
||||
keys = module: let
|
||||
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
|
||||
in
|
||||
invalid == {};
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## 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__"
|
||||
|
@ -132,7 +159,10 @@ lib: {
|
|||
"meta"
|
||||
];
|
||||
|
||||
# TODO: Document this.
|
||||
## 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);
|
||||
|
@ -159,7 +189,11 @@ lib: {
|
|||
}
|
||||
else builtins.throw "Module `${key}` has unsupported attribute(s): ${invalidKeys}";
|
||||
|
||||
# TODO: Document this.
|
||||
## 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
|
||||
|
@ -175,24 +209,38 @@ lib: {
|
|||
then lib.fp.withDynamicArgs module (args // dynamicArgs)
|
||||
else module;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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.
|
||||
##
|
||||
## @type Int -> a -> Priority a
|
||||
## @notest
|
||||
## @type Int -> a -> Attrs
|
||||
order = priority: value: {
|
||||
__type__ = "order";
|
||||
inherit priority value;
|
||||
};
|
||||
|
||||
orders = {
|
||||
# TODO: Document this.
|
||||
## Order a value before others.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
before = lib.modules.order 500;
|
||||
# TODO: Document this.
|
||||
|
||||
## Use the default ordering for a value.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
default = lib.modules.order 1000;
|
||||
# TODO: Document this.
|
||||
|
||||
## Order a value after others.
|
||||
##
|
||||
## @notest
|
||||
## @type a -> Attrs
|
||||
after = lib.modules.order 1500;
|
||||
};
|
||||
|
||||
|
@ -201,36 +249,70 @@ lib: {
|
|||
## @type List Module -> List (String | Path)
|
||||
getFiles = builtins.map (module: module.__file__);
|
||||
|
||||
# TODO: Document this.
|
||||
## 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;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## Merge module attribute sets when evaluated in the module system.
|
||||
##
|
||||
## @notest
|
||||
## @type List Attrs -> Attrs
|
||||
merge = content: {
|
||||
__type__ = "merge";
|
||||
inherit content;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## 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 = {
|
||||
# TODO: Document this.
|
||||
## 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;
|
||||
# TODO: Document this.
|
||||
|
||||
## 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;
|
||||
# TODO: Document this.
|
||||
|
||||
## 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;
|
||||
# TODO: Document this.
|
||||
|
||||
## 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;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
## Combine multiple modules together.
|
||||
##
|
||||
## @type List String -> List Module -> { matched :: Attrs, unmatched :: List Definition }
|
||||
combine = prefix: modules: let
|
||||
getConfig = module:
|
||||
builtins.map
|
||||
|
@ -246,16 +328,15 @@ lib: {
|
|||
modules;
|
||||
|
||||
process = prefix: options: configs: let
|
||||
# TODO: Document this.
|
||||
byName = attr: f: modules:
|
||||
builtins.zipAttrsWith
|
||||
(lib.fp.const builtins.concatLists)
|
||||
(key: value: builtins.concatLists value)
|
||||
(builtins.map (
|
||||
module: let
|
||||
subtree = module.${attr};
|
||||
in
|
||||
if builtins.isAttrs subtree
|
||||
then builtins.mapAttrs (key: f module) 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);
|
||||
|
@ -348,7 +429,7 @@ lib: {
|
|||
matched = builtins.mapAttrs (key: value: value.matched) resultsByName;
|
||||
|
||||
unmatched =
|
||||
builtins.mapAttrs (key: value: value.unmatched) resultsByName
|
||||
builtins.mapAttrs (name: value: value.unmatched) resultsByName
|
||||
// builtins.removeAttrs definitionsByName' (builtins.attrNames matched);
|
||||
in {
|
||||
inherit matched;
|
||||
|
@ -374,7 +455,10 @@ lib: {
|
|||
in
|
||||
process prefix modules configs;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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 ? {},
|
||||
|
@ -395,7 +479,6 @@ lib: {
|
|||
prefix = extensions.prefix or settings.prefix or [];
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
collect = let
|
||||
load = args: file: key: module: let
|
||||
moduleFromValue = lib.modules.normalize file key (lib.modules.resolve key module args);
|
||||
|
@ -532,7 +615,7 @@ lib: {
|
|||
declared =
|
||||
lib.attrs.mapRecursiveWhen
|
||||
(value: !(lib.types.is "option" value))
|
||||
(key: value: value.value)
|
||||
(name: value: value.value)
|
||||
options;
|
||||
|
||||
freeform = let
|
||||
|
|
|
@ -1,7 +1,315 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {
|
||||
examples = {
|
||||
"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 = [
|
||||
|
@ -73,5 +381,47 @@ in {
|
|||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ lib: {
|
|||
then "F"
|
||||
else builtins.throw "Invalid hex digit.";
|
||||
in
|
||||
lib.strings.concatMapSep
|
||||
lib.strings.concatMap
|
||||
serialize
|
||||
(lib.numbers.into.base 16 value);
|
||||
};
|
||||
|
|
57
lib/src/numbers/default.test.nix
Normal file
57
lib/src/numbers/default.test.nix
Normal file
|
@ -0,0 +1,57 @@
|
|||
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
|
||||
(builtins.trace actual)
|
||||
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;
|
||||
};
|
||||
}
|
|
@ -33,7 +33,9 @@ lib: {
|
|||
# TODO: Improve this error message to show the location and definitions for the option.
|
||||
else builtins.throw "Cannot merge definitions.";
|
||||
|
||||
# TODO: Document this.
|
||||
## Merge multiple option definitions together.
|
||||
##
|
||||
## @type Location -> Type -> List Definition
|
||||
definitions = location: type: definitions: let
|
||||
identifier = lib.options.getIdentifier location;
|
||||
resolve = definition: let
|
||||
|
@ -81,11 +83,14 @@ lib: {
|
|||
};
|
||||
};
|
||||
|
||||
## 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 = key: option.options ? ${key} && result ? ${key};
|
||||
shared = name: option.options ? ${name} && result ? ${name};
|
||||
typeSet = lib.attrs.when ((shared "type") && isTypeMergeable) {
|
||||
type = mergedType;
|
||||
};
|
||||
|
@ -212,9 +217,12 @@ lib: {
|
|||
## @type List String -> String
|
||||
getIdentifier = location: let
|
||||
special = [
|
||||
"<name>" # attrsOf (submodule {})
|
||||
"*" # listOf (submodule {})
|
||||
"<function body>" # functionTo
|
||||
# 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
|
||||
|
@ -258,7 +266,9 @@ lib: {
|
|||
in
|
||||
lib.strings.concatMap serialize definitions;
|
||||
|
||||
# TODO: Document this.
|
||||
## 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;
|
||||
|
||||
|
|
3
lib/src/options/default.test.nix
Normal file
3
lib/src/options/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
|
@ -12,7 +12,7 @@ lib: {
|
|||
if builtins.stringLength value <= 207 && validate value != null
|
||||
then builtins.unsafeDiscardStringContext value
|
||||
else
|
||||
lib.fp.pipe value [
|
||||
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
|
||||
|
@ -36,6 +36,7 @@ lib: {
|
|||
if builtins.stringLength x == 0
|
||||
then "unknown"
|
||||
else x)
|
||||
];
|
||||
]
|
||||
value;
|
||||
};
|
||||
}
|
||||
|
|
3
lib/src/packages/default.test.nix
Normal file
3
lib/src/packages/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
3
lib/src/paths/default.test.nix
Normal file
3
lib/src/paths/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
|
@ -17,10 +17,7 @@ lib: {
|
|||
## definitions. Unlike `fix`, the resulting value is also given a `__unfix__`
|
||||
## attribute that is set to the original function passed to `fix'`.
|
||||
##
|
||||
## FIXME: The below type annotation should include a mention of the `__unfix__`
|
||||
## value.
|
||||
##
|
||||
## @type (a -> a) -> a
|
||||
## @type (a -> a) -> a & { __unfix__ :: (a -> a) }
|
||||
fix' = f: let
|
||||
x =
|
||||
f x
|
||||
|
|
3
lib/src/points/default.test.nix
Normal file
3
lib/src/points/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
3
lib/src/strings/default.test.nix
Normal file
3
lib/src/strings/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
|
@ -151,7 +151,7 @@ lib: {
|
|||
# TODO: Document this.
|
||||
unspecified = lib.types.create {
|
||||
name = "Unspecified";
|
||||
description = "unspecified value";
|
||||
description = "unspecified type";
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
|
@ -388,20 +388,20 @@ lib: {
|
|||
merge = location: definitions: let
|
||||
normalize = definition:
|
||||
builtins.mapAttrs
|
||||
(key: value: {
|
||||
(name: value: {
|
||||
__file__ = definition.__file__;
|
||||
value = value;
|
||||
})
|
||||
definition.value;
|
||||
normalized = builtins.map normalize definitions;
|
||||
zipper = key: definitions:
|
||||
(lib.options.merge.definitions (location ++ [key]) type definitions).optional;
|
||||
zipper = name: definitions:
|
||||
(lib.options.merge.definitions (location ++ [name]) type definitions).optional;
|
||||
filtered =
|
||||
lib.attrs.filter
|
||||
(key: value: value ? value)
|
||||
(name: value: value ? value)
|
||||
(builtins.zipAttrsWith zipper normalized);
|
||||
in
|
||||
builtins.mapAttrs (key: value: value.value) filtered;
|
||||
builtins.mapAttrs (name: value: value.value) filtered;
|
||||
getSubOptions = prefix: type.getSubOptions (prefix ++ ["<name>"]);
|
||||
getSubModules = type.getSubModules;
|
||||
withSubModules = modules: lib.types.attrs.of (type.withSubModules modules);
|
||||
|
@ -421,14 +421,14 @@ lib: {
|
|||
merge = location: definitions: let
|
||||
normalize = definition:
|
||||
builtins.mapAttrs
|
||||
(key: value: {
|
||||
(name: value: {
|
||||
__file__ = definition.__file__;
|
||||
value = value;
|
||||
})
|
||||
definition.value;
|
||||
normalized = builtins.map normalize definitions;
|
||||
zipper = key: definitions: let
|
||||
merged = lib.options.merge.definitions (location ++ [key]) type definitions;
|
||||
zipper = name: definitions: let
|
||||
merged = lib.options.merge.definitions (location ++ [name]) type definitions;
|
||||
in
|
||||
merged.optional.value or type.fallback.value or merged.merged;
|
||||
in
|
||||
|
@ -494,7 +494,14 @@ lib: {
|
|||
j: value: let
|
||||
resolved =
|
||||
lib.options.merge.definitions
|
||||
(location ++ ["[definition ${builtins.toString i}-entry ${j}]"]);
|
||||
(location ++ ["[definition ${builtins.toString i}-entry ${j}]"])
|
||||
type
|
||||
[
|
||||
{
|
||||
file = definition.file;
|
||||
value = value;
|
||||
}
|
||||
];
|
||||
in
|
||||
resolved.optional
|
||||
)
|
||||
|
@ -503,7 +510,7 @@ lib: {
|
|||
definitions;
|
||||
merged = builtins.concatLists result;
|
||||
filtered = builtins.filter (definition: definition ? value) merged;
|
||||
values = lib.optiosn.getDefinitionValues filtered;
|
||||
values = lib.options.getDefinitionValues filtered;
|
||||
in
|
||||
values;
|
||||
getSubOptions = prefix: type.getSubOptions (prefix ++ ["*"]);
|
||||
|
|
3
lib/src/types/default.test.nix
Normal file
3
lib/src/types/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
3
lib/src/versions/default.test.nix
Normal file
3
lib/src/versions/default.test.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {}
|
18
lib/test.sh
Executable file
18
lib/test.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/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
|
||||
|
Loading…
Add table
Reference in a new issue