chore: initial commit
This commit is contained in:
parent
b7456195bd
commit
f7d6c846f6
|
@ -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…
Reference in a new issue