chore(lib): remove lib from labs

This commit is contained in:
Jake Hamilton 2025-03-30 04:24:37 -07:00
parent c30f94972c
commit 5bec48ef8a
Signed by: jakehamilton
GPG key ID: 9762169A1B35EA68
59 changed files with 208 additions and 8096 deletions

View file

@ -27,6 +27,5 @@ may collaborate.
| Name | Phase | Description |
| ------------------------------ | --------- | ------------------------------------------------------------------------------------ |
| [Aux Lib](./lib) | Iteration | A library of common functions used in the Aux ecosystem. |
| [Aux Foundation](./foundation) | Iteration | Foundational packages which allow for bootstrapping a greater package set. |
| [Aux Tidepool](./tidepool) | Idea | An initial package set built on top of Aux Foundation using Aux Lib's module system. |

View file

@ -2,7 +2,8 @@
system ? builtins.currentSystem,
}:
let
lib = import ./../lib;
pins = import ./npins;
lib = import pins.lib;
modules = import ./src;

23
foundation/flake.lock generated
View file

@ -1,27 +1,6 @@
{
"nodes": {
"lib": {
"locked": {
"dir": "lib",
"lastModified": 1723737980,
"narHash": "sha256-1WnFatW5kSuO2jjt62hvSbH84TSYyO+VmvkJ0d5e/ZY=",
"ref": "master",
"rev": "cadfaabc853d20f2bc20bad794fcbe520ea48f13",
"revCount": 82,
"type": "git",
"url": "file:../"
},
"original": {
"dir": "lib",
"type": "git",
"url": "file:../"
}
},
"root": {
"inputs": {
"lib": "lib"
}
}
"root": {}
},
"root": "root",
"version": 7

View file

@ -1,16 +1,13 @@
{
description = "A set of foundational packages required for bootstrapping a larger package set.";
inputs = {
lib = {
url = "git+file:../?dir=lib";
};
};
inputs = { };
outputs =
inputs:
_:
let
inherit (inputs.lib) lib;
pins = import ./npins;
lib = import pins.lib;
modules = import ./src;
@ -27,7 +24,7 @@
system:
let
result = lib.modules.run {
modules = (builtins.attrValues modules) ++ [ { config.aux.system = system; } ];
modules = (builtins.attrValues modules) ++ [{ config.aux.system = system; }];
};
in
result.config.exports.resolved.packages

View file

@ -0,0 +1,80 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource =
{
repository,
revision,
url ? null,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else
assert repository.type == "Git";
let
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName repository.url revision;
in
builtins.fetchGit {
url = repository.url;
rev = revision;
inherit name;
# hash = hash;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

View file

@ -0,0 +1,16 @@
{
"pins": {
"lib": {
"type": "Git",
"repository": {
"type": "Git",
"url": "git+ssh://forgejo@git.auxolotl.org/auxolotl/lib.git"
},
"branch": "main",
"revision": "7552ab48bb394d59d2bf1f7a558d28ce59da524d",
"url": null,
"hash": "0705fm00k9f95b6idf5qnfvqm4qf1a0cv966ghgd48kd1qy4il5c"
}
},
"version": 3
}

View file

@ -1,24 +0,0 @@
MIT License
Copyright (c) 2003-2023 Eelco Dolstra and the Nixpkgs/NixOS contributors
Copyright (c) 2017-2023 Home Manager contributors
Copyright (c) 2024 Aux Contributors
Permission is hereby granted, free
of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice
(including the next paragraph) shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,132 +0,0 @@
# Aux Lib
Aux Lib is intended to be a replacement for NixPkg's `lib` with stronger constraints around naming,
organization, and inclusion of functions. In addition to replacing library functions, Aux Lib also
defines a revamped version of the NixOS Module system intended to make it easier, more approachable,
and intuitive.
## Usage
The library can be imported both with and without Nix Flakes. To import the library using Nix Flakes,
add this repository as an input.
```nix
inputs.lib.url = "https://git.auxolotl.org/auxolotl/labs/archive/main.tar.gz?dir=lib";
```
To import the library without using Nix Flakes, you will need to use `fetchTarball` and import the
library entrypoint.
```nix
let
labs = builtins.fetchTarball {
url = "https://git.auxolotl.org/auxolotl/labs/archive/main.tar.gz";
sha256 = "<sha256>";
};
lib = import "${labs}/lib";
in
# ...
```
## Development
To contribute to the project, we accept pull requests to this repository. Please see the following
sections for information on the appropriate practices and required steps for working on Aux Lib.
### Documentation
We want our code to survive in the world, but without proper documentation that won't happen. In
order to not lose knowledge and also make it easier for others to begin participating in the
project we require that every function have an appropriate documentation comment. The format for
these comments is as follows:
```nix
let
## This is a description of what the function does. Any necessary information can be added
## here. After this point comes a gap before a Hindley-Milner style type signature. Note
## that these types are not actually checked, but serve as a helpful addition for the user
## in addition to being provided in generated documentation.
##
## @type Int -> String
func = x: builtins.toString x;
in
# ...
```
### Testing
All functions that are added to the project should include tests. The test suites are located
next to their implementation in files ending in `.test.nix`. These tests should ensure that
the library behaves as expected. The typical structure of these test suites is:
```nix
let
lib = import ./../default.nix;
in
{
"my function" = {
"test 1" = let
expected = 1;
input = {};
actual = lib.myFunction input;
in
actual == expected;
};
}
```
Successful tests will return `true` while failing test will resolve with `false`. You can run
all tests with the following command:
```shell
./test.sh
```
If you want to run a specific test suite, you can run the command, specifying the directory
to the tests file:
```shell
./test.sh $namespace
```
For example, to run the tests for only `attrs`, use the following command:
```shell
./test.sh attrs
```
### Formatting
> **Note:** To keep this flake light and keep its inputs empty we do not include a package
> set which would provide a formatter. Instead please run `nix run nixpkgs#nixfmt-rfc-style`
> until an improved solution is available.
All code in this project must be formatted using the provided formatter in the `flake.nix`
file. You can run this formatter using the command `nix fmt` (not currently available).
### Code Quality
In order to keep the project approachable and easy to maintain, certain patterns are not allowed.
In particular, the use of `with` and `rec` are not allowed. Additionally, you should prefer the
fully qualified name of a variable rather than creating intermediate ones using `inherit`.
### Adding Functionality
Before adding new features to the library, submit an issue or talk the idea over with one or
more of the project maintainers. We want to make sure that the library does not become bloated
with tools that aren't used. Some features may be better handled in a separate project. If you
do get the go-ahead to begin working on your feature, please place it in the library structure
similarly to how existing features are. For example, things dealing with strings should go in
`src/strings/default.nix`.
Additionally, you should prefer to group things in attribute sets for like-functionality. More
broad categories such as `strings` and `lists` are helpful, but scoped groups for things like
`into`, `from`, and `validate` also make the library more discoverable. Having all of the
different parts of the library mirroring this organizational structure makes building intuition
for working with the library much easier. To know when to group new things, consider the
following:
- Would your function name be multiple words like `fromString`?
- Are there multiple variants of this function?
- Would it be easier to find in a group?
- Would grouping help avoid name collisions or confusion?

View file

@ -1 +0,0 @@
import ./src

View file

@ -1,100 +0,0 @@
let
lib = import ./default.nix;
root = ./.;
files = [
./src/default.test.nix
./src/attrs/default.test.nix
./src/bools/default.test.nix
./src/dag/default.test.nix
./src/errors/default.test.nix
./src/fp/default.test.nix
./src/generators/default.test.nix
./src/importers/default.test.nix
./src/lists/default.test.nix
./src/math/default.test.nix
./src/modules/default.test.nix
./src/numbers/default.test.nix
./src/options/default.test.nix
./src/packages/default.test.nix
./src/paths/default.test.nix
./src/points/default.test.nix
./src/strings/default.test.nix
./src/types/default.test.nix
./src/versions/default.test.nix
];
resolve =
file:
let
imported = import file;
value = if builtins.isFunction imported then imported { inherit lib; } else imported;
relative = lib.strings.removePrefix (builtins.toString root) (builtins.toString file);
in
{
inherit file value;
relative = if lib.strings.hasPrefix "/" relative then "." + relative else relative;
namespace = getNamespace file;
};
resolved = builtins.map resolve files;
getNamespace =
path:
let
relative = lib.strings.removePrefix (builtins.toString root) (builtins.toString path);
parts = lib.strings.split "/" relative;
in
if builtins.length parts > 2 then builtins.elemAt parts 2 else relative;
results =
let
getTests =
file: prefix: suite:
let
nested = lib.attrs.mapToList (name: value: getTests file (prefix ++ [ name ]) value) suite;
relative = lib.strings.removePrefix (builtins.toString root) (builtins.toString file);
in
if builtins.isAttrs suite then
builtins.concatLists nested
else
[
{
inherit prefix file;
name = builtins.concatStringsSep " > " prefix;
value = suite;
relative = if lib.strings.hasPrefix "/" relative then "." + relative else relative;
}
];
base = builtins.map (entry: getTests entry.file [ entry.namespace ] entry.value) resolved;
in
builtins.concatLists base;
successes = builtins.filter (test: test.value) results;
failures = builtins.filter (test: !test.value) results;
total = "${builtins.toString (builtins.length successes)} / ${builtins.toString (builtins.length results)}";
in
if failures == [ ] then
let
message = lib.strings.concatMapSep "\n" (test: " ${test.name}") successes;
in
''
SUCCESS (${total})
${message}
''
else
let
successMessage = lib.strings.concatMapSep "\n" (test: " ${test.name}") successes;
failureMessage = lib.strings.concatMapSep "\n\n" (
test: " ${test.name}\n" + " -> ${test.relative}"
) failures;
in
''
FAILURE (${total})
${failureMessage}
''

View file

@ -1,5 +0,0 @@
{
description = "A NixPkgs library replacement containing helper functions and a module system.";
outputs = _: { lib = import ./src; };
}

View file

@ -1,216 +0,0 @@
lib: {
attrs = {
## Merge two attribute sets at the base level.
##
## @type Attrs a b c => a -> b -> c
merge = x: y: x // y;
## Merge two attribute sets recursively until a given predicate returns true.
## Any values that are _not_ attribute sets will be overridden with the value
## from `y` first if it exists and then `x` otherwise.
##
## @type Attrs a b c => (String -> Any -> Any -> Bool) -> a -> b -> c
mergeRecursiveUntil =
predicate: x: y:
let
process =
path:
builtins.zipAttrsWith (
name: values:
let
currentPath = path ++ [ name ];
isSingleValue = builtins.length values == 1;
isComplete = predicate currentPath (builtins.elemAt values 1) (builtins.elemAt values 0);
in
if isSingleValue then
builtins.elemAt values 0
else if isComplete then
builtins.elemAt values 1
else
process currentPath values
);
in
process
[ ]
[
x
y
];
## Merge two attribute sets recursively. Any values that are _not_ attribute sets
## will be overridden with the value from `y` first if it exists and then `x`
## otherwise.
##
## @type Attrs a b c => a -> b -> c
mergeRecursive = lib.attrs.mergeRecursiveUntil (
path: x: y:
!(builtins.isAttrs x && builtins.isAttrs y)
);
## Get a value from an attribute set by a path. If the path does not exist,
## a fallback value will be returned instead.
##
## @type (List String) -> a -> Attrs -> a | b
select =
path: fallback: target:
let
name = builtins.head path;
rest = builtins.tail path;
in
if path == [ ] then
target
else if target ? ${name} then
lib.attrs.select rest fallback target.${name}
else
fallback;
## Get a value from an attribute set by a path. If the path does not exist,
## an error will be thrown.
##
## @type (List String) -> Attrs -> a
selectOrThrow =
path: target:
let
pathAsString = builtins.concatStringsSep "." path;
error = builtins.throw "Path not found in attribute set: ${pathAsString}";
in
if lib.attrs.has path target then lib.attrs.select path null target else error;
## Zip specific attributes from a list of attribute sets.
##
## @type List String -> (List Any -> Any) -> List Attrs -> Attrs
zipWithNames =
names: f: list:
let
transform = name: {
inherit name;
value = f name (builtins.catAttrs name list);
};
results = builtins.map transform names;
in
builtins.listToAttrs results;
## Match an attribute set against a pattern.
##
## @type Attrs -> Attrs -> Bool
match =
pattern: value:
let
process =
name: values:
let
first = builtins.elemAt values 0;
second = builtins.elemAt values 1;
in
if builtins.length values == 1 then
false
else if builtins.isAttrs first then
builtins.isAttrs second && lib.attrs.match first second
else
first == second;
result = lib.attrs.zipWithNames (builtins.attrNames pattern) process [
pattern
value
];
in
assert lib.errors.trace (builtins.isAttrs pattern) "Pattern must be an attribute set";
assert lib.errors.trace (builtins.isAttrs value) "Value must be an attribute set";
builtins.all lib.fp.id (builtins.attrValues result);
## Create a nested attribute set with a value as the leaf node.
##
## @type (List String) -> a -> Attrs
set =
path: value:
let
length = builtins.length path;
process =
depth: if depth == length then value else { ${builtins.elemAt path depth} = process (depth + 1); };
in
process 0;
## Check if a path exists in an attribute set.
##
## @type (List String) -> Attrs -> Bool
has =
path: target:
let
name = builtins.head path;
rest = builtins.tail path;
in
if path == [ ] then
true
else if target ? ${name} then
lib.attrs.has rest target.${name}
else
false;
## Depending on a given condition, either use the given value or an empty
## attribute set.
##
## @type Attrs a b => Bool -> a -> a | b
when = condition: value: if condition then value else { };
## Map an attribute set's names and values to a list.
##
## @type Any a => (String -> Any -> a) -> Attrs -> List a
mapToList = f: target: builtins.map (name: f name target.${name}) (builtins.attrNames target);
## Map an attribute set recursively. Only non-set leaf nodes will be mapped.
##
## @type (List String -> Any -> Any) -> Attrs -> Attrs
mapRecursive = f: target: lib.attrs.mapRecursiveWhen (lib.fp.const true) f target;
## Map an attribute set recursively when a given predicate returns true.
## Only leaf nodes according to the predicate will be mapped.
##
## @type (Attrs -> Bool) -> (List String -> Any -> Any) -> Attrs -> Attrs
mapRecursiveWhen =
predicate: f: target:
let
process =
path:
builtins.mapAttrs (
name: value:
if builtins.isAttrs value && predicate value then
process (path ++ [ name ]) value
else
f (path ++ [ name ]) value
);
in
process [ ] target;
## Filter an attribute set by a given predicate. The filter is only performed
## on the base level of the attribute set.
##
## @type (String -> Any -> Bool) -> Attrs -> Attrs
filter =
predicate: target:
let
names = builtins.attrNames target;
process =
name:
let
value = target.${name};
in
if predicate name value then [ { inherit name value; } ] else [ ];
valid = builtins.concatMap process names;
in
builtins.listToAttrs valid;
## Generate an attribute set from a list of names and a function that is
## applied to each name.
##
## @type (List String) -> (String -> Any) -> Attrs
generate =
names: f:
let
pairs = builtins.map (name: {
inherit name;
value = f name;
}) names;
in
builtins.listToAttrs pairs;
};
}

View file

@ -1,342 +0,0 @@
let
lib = import ./../default.nix;
in
{
"merge" = {
"merges two shallow sets" =
let
expected = {
x = 1;
y = 2;
};
actual = lib.attrs.merge { x = 1; } { y = 2; };
in
expected == actual;
"overwrites values from the first set" =
let
expected = {
x = 2;
};
actual = lib.attrs.merge { x = 1; } { x = 2; };
in
actual == expected;
"does not merge nested sets" =
let
expected = {
x.y = 2;
};
actual = lib.attrs.merge { x.z = 1; } { x.y = 2; };
in
actual == expected;
};
"mergeRecursiveUntil" = {
"merges with predicate" =
let
expected = {
x.y.z = 1;
};
actual = lib.attrs.mergeRecursiveUntil (
path: x: y:
lib.lists.last path == "z"
) { x.y.z = 2; } { x.y.z = 1; };
in
actual == expected;
"handles shallow merges" =
let
expected = {
x.y.z = 1;
};
actual =
lib.attrs.mergeRecursiveUntil
(
path: x: y:
true
)
{
x = {
y.z = 2;
a = false;
};
}
{ x.y.z = 1; };
in
actual == expected;
};
"mergeRecursive" = {
"merges two sets deeply" =
let
expected = {
x.y.z = 1;
};
actual = lib.attrs.mergeRecursive { x.y.z = 2; } { x.y.z = 1; };
in
actual == expected;
};
"select" = {
"selects a nested value" =
let
expected = "value";
actual = lib.attrs.select [
"x"
"y"
"z"
] null { x.y.z = expected; };
in
actual == expected;
"handles empty path" =
let
expected = {
x = {
y = {
z = 1;
};
};
};
actual = lib.attrs.select [ ] null {
x = {
y = {
z = 1;
};
};
};
in
actual == expected;
"handles fallback value" =
let
expected = "fallback";
actual = lib.attrs.select [
"x"
"y"
"z"
] expected { };
in
actual == expected;
};
"selectOrThrow" = {
"selects a nested value" =
let
expected = "value";
actual = lib.attrs.selectOrThrow [
"x"
"y"
"z"
] { x.y.z = expected; };
in
actual == expected;
"handles empty path" =
let
expected = {
x = {
y = {
z = 1;
};
};
};
actual = lib.attrs.selectOrThrow [ ] {
x = {
y = {
z = 1;
};
};
};
in
actual == expected;
"throws on nonexistent path" =
let
actual = lib.attrs.selectOrThrow [
"x"
"y"
"z"
] { };
evaluated = builtins.tryEval (builtins.deepSeq actual actual);
in
!evaluated.success;
};
"set" = {
"creates a nested set" =
let
expected = {
x = {
y = {
z = 1;
};
};
};
actual = lib.attrs.set [
"x"
"y"
"z"
] 1;
in
actual == expected;
"handles empty path" =
let
expected = 1;
actual = lib.attrs.set [ ] 1;
in
actual == expected;
};
"has" = {
"returns true for a nested value" =
let
exists = lib.attrs.has [
"x"
"y"
"z"
] { x.y.z = 1; };
in
exists;
"returns false for a nonexistent value" =
let
exists = lib.attrs.has [
"x"
"y"
"z"
] { };
in
!exists;
"handles empty path" =
let
exists = lib.attrs.has [ ] { };
in
exists;
};
"when" = {
"returns the value when condition is true" =
let
expected = "value";
actual = lib.attrs.when true expected;
in
actual == expected;
"returns an empty set when condition is false" =
let
expected = { };
actual = lib.attrs.when false "value";
in
actual == expected;
};
"mapToList" = {
"converts a set to a list" =
let
expected = [
{
name = "x";
value = 1;
}
{
name = "y";
value = 2;
}
];
actual = lib.attrs.mapToList (name: value: { inherit name value; }) {
x = 1;
y = 2;
};
in
actual == expected;
};
"mapRecursiveWhen" = {
"maps a set recursively" =
let
expected = {
x = {
y = {
z = 2;
};
};
};
actual = lib.attrs.mapRecursiveWhen (value: true) (path: value: value + 1) {
x = {
y = {
z = 1;
};
};
};
in
actual == expected;
"maps a set given a condition" =
let
expected = {
x = {
y = {
z = 1;
};
};
};
actual =
lib.attrs.mapRecursiveWhen (value: !(value ? z))
(
path: value:
# We map before we get to a non-set value
if builtins.isAttrs value then value else value + 1
)
{
x = {
y = {
z = 1;
};
};
};
in
actual == expected;
};
"mapRecursive" = {
"maps a set recursively" =
let
expected = {
x = {
y = {
z = 2;
};
};
};
actual = lib.attrs.mapRecursive (path: value: value + 1) {
x = {
y = {
z = 1;
};
};
};
in
actual == expected;
};
"filter" = {
"filters a set" =
let
expected = {
y = 2;
};
actual = lib.attrs.filter (name: value: name == "y") {
x = 1;
y = 2;
};
in
actual == expected;
};
}

View file

@ -1,48 +0,0 @@
lib: {
bools = {
into = {
## Convert a boolean value into a string.
##
## @type Bool -> String
string = value: if value then "true" else "false";
## Convert a boolean into either the string "yes" or "no".
##
## @type Bool -> String
yesno = value: if value then "yes" else "no";
};
## Choose between two values based on a condition. When true, the first value
## is returned, otherwise the second value is returned.
##
## @type Bool -> a -> b -> a | b
when =
condition: x: y:
if condition then x else y;
## Perform a logical AND operation on two values.
##
## @type Bool -> Bool -> Bool
and = a: b: a && b;
## Perform a logical AND operation on two functions being applied to a value.
##
## @type (a -> Bool) -> (a -> Bool) -> a -> Bool
and' = f: g: (x: (f x) && (g x));
## Perform a logical OR operation on two values.
##
## @type Bool -> Bool -> Bool
or = a: b: a || b;
## Perform a logical OR operation on two functions being applied to a value.
##
## @type (a -> Bool) -> (a -> Bool) -> a -> Bool
or' = f: g: (x: (f x) || (g x));
## Perform a logical NOT operation on a value.
##
## @type Bool -> Bool
not = a: !a;
};
}

View file

@ -1,190 +0,0 @@
let
lib = import ./../default.nix;
in
{
"into" = {
"string" = {
"handles true" =
let
expected = "true";
actual = lib.bools.into.string true;
in
actual == expected;
"handles false" =
let
expected = "false";
actual = lib.bools.into.string false;
in
actual == expected;
};
"yesno" = {
"handles true" =
let
expected = "yes";
actual = lib.bools.into.yesno true;
in
actual == expected;
"handles false" =
let
expected = "no";
actual = lib.bools.into.yesno false;
in
actual == expected;
};
};
"when" = {
"returns first value when true" =
let
expected = "foo";
actual = lib.bools.when true expected "bar";
in
actual == expected;
"returns second value when false" =
let
expected = "bar";
actual = lib.bools.when false "foo" expected;
in
actual == expected;
};
"and" = {
"returns true when both are true" =
let
expected = true;
actual = lib.bools.and true true;
in
actual == expected;
"returns false when first is false" =
let
expected = false;
actual = lib.bools.and false true;
in
actual == expected;
"returns false when second is false" =
let
expected = false;
actual = lib.bools.and true false;
in
actual == expected;
"returns false when both are false" =
let
expected = false;
actual = lib.bools.and false false;
in
actual == expected;
};
"and'" =
let
getTrue = _: true;
getFalse = _: false;
in
{
"returns true when both are true" =
let
expected = true;
actual = lib.bools.and' getTrue getTrue null;
in
actual == expected;
"returns false when first is false" =
let
expected = false;
actual = lib.bools.and' getFalse getTrue null;
in
actual == expected;
"returns false when second is false" =
let
expected = false;
actual = lib.bools.and' getTrue getFalse null;
in
actual == expected;
"returns false when both are false" =
let
expected = false;
actual = lib.bools.and' getFalse getFalse null;
in
actual == expected;
};
"or" = {
"returns true when both are true" =
let
expected = true;
actual = lib.bools.or true true;
in
actual == expected;
"returns true when first is true" =
let
expected = true;
actual = lib.bools.or true false;
in
actual == expected;
"returns true when second is true" =
let
expected = true;
actual = lib.bools.or false true;
in
actual == expected;
"returns false when both are false" =
let
expected = false;
actual = lib.bools.or false false;
in
actual == expected;
};
"or'" =
let
getTrue = _: true;
getFalse = _: false;
in
{
"returns true when both are true" =
let
expected = true;
actual = lib.bools.or' getTrue getTrue null;
in
actual == expected;
"returns true when first is true" =
let
expected = true;
actual = lib.bools.or' getTrue getFalse null;
in
actual == expected;
"returns true when second is true" =
let
expected = true;
actual = lib.bools.or' getFalse getTrue null;
in
actual == expected;
"returns false when both are false" =
let
expected = false;
actual = lib.bools.or' getFalse getFalse null;
in
actual == expected;
};
"not" = {
"returns false when true" =
let
expected = false;
actual = lib.bools.not true;
in
actual == expected;
"returns true when false" =
let
expected = true;
actual = lib.bools.not false;
in
actual == expected;
};
}

View file

@ -1,155 +0,0 @@
lib: {
dag = {
validate = {
## Check that a value is a DAG entry.
##
## @type a -> Bool
entry = value: (value ? value) && (value ? before) && (value ? after);
## Check that a value is a DAG.
##
## @type a -> Bool
graph =
value:
let
isContentsValid = builtins.all lib.dag.validate.entry (builtins.attrValues value);
in
builtins.isAttrs value && isContentsValid;
};
sort = {
## Apply a topological sort to a DAG.
##
## @type Dag a -> { result :: List a } | { cycle :: List a, loops :: List a }
topological =
graph:
let
getEntriesBefore =
graph: name:
let
before = lib.attrs.filter (key: value: builtins.elem name value.before) graph;
in
builtins.attrNames before;
normalize = name: value: {
inherit name;
value = value.value;
after = value.after ++ (getEntriesBefore graph name);
};
normalized = builtins.mapAttrs normalize graph;
entries = builtins.attrValues normalized;
isBefore = a: b: builtins.elem a.name b.after;
sorted = lib.lists.sort.topological isBefore entries;
in
if sorted ? result then
{
result = builtins.map (value: {
name = value.name;
value = value.value;
}) sorted.result;
}
else
sorted;
};
apply = {
## Apply a set of defaults to a graph. This will ensure that missing entries are added
## and any entries that do exist are given the appropriate `before` and `after` values.
##
## @type Dag a -> Dag a -> Dag a
defaults =
graph: defaults:
let
result = builtins.mapAttrs (
name: entry:
if defaults ? ${name} then
if builtins.isString entry then
{
value = entry;
before = defaults.${name}.before or [ ];
after = defaults.${name}.after or [ ];
}
else
entry
// {
before = (entry.before or [ ]) ++ (defaults.${name}.before or [ ]);
after = (entry.after or [ ]) ++ (defaults.${name}.after or [ ]);
}
else
entry
) graph;
in
defaults // result;
};
## Map over the entries in a DAG and modify their values.
##
## @type (String -> a -> b) -> Dag a -> Dag b
map = f: builtins.mapAttrs (name: value: value // { value = f name value.value; });
entry = {
## Create a new DAG entry.
##
## @type List String -> List String -> a -> { before :: List String, after :: List String, value :: a }
between = after: before: value: { inherit before after value; };
## Create a new DAG entry with no dependencies.
##
## @type a -> { before :: List String, after :: List String, value :: a }
anywhere = lib.dag.entry.between [ ] [ ];
## Create a new DAG entry that occurs before other entries.
##
## @type List String -> a -> { before :: List String, after :: List String, value :: a }
before = before: lib.dag.entry.between [ ] before;
## Create a new DAG entry that occurs after other entries.
##
## @type List String -> a -> { before :: List String, after :: List String, value :: a }
after = after: lib.dag.entry.between after [ ];
};
entries = {
## Create a DAG from a list of entries, prefixed with a tag.
##
## @type String -> List String -> List String -> List a -> Dag a
between =
tag:
let
process =
i: after: before: entries:
let
name = "${tag}-${builtins.toString i}";
entry = builtins.head entries;
rest = builtins.tail entries;
in
if builtins.length entries == 0 then
{ }
else if builtins.length entries == 1 then
{ "${name}" = lib.dag.entry.between after before entry; }
else
{ "${name}" = lib.dag.entry.after after entry; } // (process (i + 1) before [ name ] rest);
in
process 0;
## Create a DAG from a list of entries, prefixed with a tag, that can occur anywhere.
##
## @type String -> List a -> Dag a
anywhere = tag: lib.dag.entries.between tag [ ] [ ];
## Create a DAG from a list of entries, prefixed with a tag, that occurs before other entries.
##
## @type String -> List String -> List a -> Dag a
before = tag: before: lib.dag.entries.between tag [ ] before;
## Create a DAG from a list of entries, prefixed with a tag, that occurs after other entries.
##
## @type String -> List String -> List a -> Dag a
after = tag: after: lib.dag.entries.between tag after [ ];
};
};
}

View file

@ -1,169 +0,0 @@
let
lib = import ./../default.nix;
in
{
"validate" = {
"entry" = {
"invalid value" =
let
expected = false;
actual = lib.dag.validate.entry { };
in
actual == expected;
"a manually created value" =
let
expected = true;
actual = lib.dag.validate.entry {
value = null;
before = [ ];
after = [ ];
};
in
actual == expected;
"entry.between" =
let
expected = true;
actual = lib.dag.validate.entry (lib.dag.entry.between [ ] [ ] null);
in
actual == expected;
"entry.anywhere" =
let
expected = true;
actual = lib.dag.validate.entry (lib.dag.entry.anywhere null);
in
actual == expected;
"entry.before" =
let
expected = true;
actual = lib.dag.validate.entry (lib.dag.entry.before [ ] null);
in
actual == expected;
"entry.after" =
let
expected = true;
actual = lib.dag.validate.entry (lib.dag.entry.after [ ] null);
in
actual == expected;
};
"graph" = {
"invalid value" =
let
expected = false;
actual = lib.dag.validate.graph { x = { }; };
in
actual == expected;
"a manually created value" =
let
expected = true;
actual = lib.dag.validate.graph {
x = {
value = null;
before = [ ];
after = [ ];
};
};
in
actual == expected;
"entries.between" =
let
expected = true;
graph =
lib.dag.entries.between "example" [ ]
[ ]
[
null
null
];
actual = lib.dag.validate.graph graph;
in
actual == expected;
"entries.anywhere" =
let
expected = true;
graph = lib.dag.entries.anywhere "example" [
null
null
];
actual = lib.dag.validate.graph graph;
in
actual == expected;
"entries.before" =
let
expected = true;
graph =
lib.dag.entries.before "example"
[ ]
[
null
null
];
actual = lib.dag.validate.graph graph;
in
actual == expected;
"entries.after" =
let
expected = true;
graph =
lib.dag.entries.after "example"
[ ]
[
null
null
];
actual = lib.dag.validate.graph graph;
in
actual == expected;
};
};
"sort" = {
"topological" = {
"handles an empty graph" =
let
expected = [ ];
actual = lib.dag.sort.topological { };
in
actual.result == expected;
"sorts a graph" =
let
expected = [
{
name = "a";
value = "a";
}
{
name = "b";
value = "b";
}
{
name = "c";
value = "c";
}
{
name = "d";
value = "d";
}
];
actual = lib.dag.sort.topological {
a = lib.dag.entry.anywhere "a";
b = lib.dag.entry.between [ "a" ] [ "c" ] "b";
c = lib.dag.entry.before [ "c" ] "c";
d = lib.dag.entry.after [ "c" ] "d";
};
in
actual.result == expected;
};
};
}

View file

@ -1,84 +0,0 @@
let
files = [
./attrs
./bools
./dag
./errors
./fp
./generators
./importers
./licenses
./lists
./math
./modules
./numbers
./options
./packages
./paths
./points
./strings
./types
./versions
];
libs = builtins.map (f: import f) files;
## Calculate the fixed point of a function. This will evaluate the function `f`
## until its result settles (or Nix's recursion limit is reached). This allows
## us to define recursive functions without worrying about the order of their
## definitions.
##
## @type (a -> a) -> a
fix =
f:
let
x = f x;
in
x;
## Merge two attribute sets recursively until a given predicate returns true.
## Any values that are _not_ attribute sets will be overridden with the value
## from `y` first if it exists and then `x` otherwise.
##
## @type Attrs a b c => (String -> Any -> Any -> Bool) -> a -> b -> c
mergeAttrsRecursiveUntil =
predicate: x: y:
let
process =
path:
builtins.zipAttrsWith (
name: values:
let
currentPath = path ++ [ name ];
isSingleValue = builtins.length values == 1;
isComplete = predicate currentPath (builtins.elemAt values 1) (builtins.elemAt values 0);
in
if isSingleValue || isComplete then builtins.elemAt values 0 else process currentPath values
);
in
process
[ ]
[
x
y
];
## Merge two attribute sets recursively. Any values that are _not_ attribute sets
## will be overridden with the value from `y` first if it exists and then `x`
## otherwise.
##
## @type Attrs a b c => a -> b -> c
mergeAttrsRecursive = mergeAttrsRecursiveUntil (
path: x: y:
!(builtins.isAttrs x && builtins.isAttrs y)
);
lib = fix (
self:
let
merge = acc: create: mergeAttrsRecursive acc (create self);
in
builtins.foldl' merge { } libs
);
in
lib.points.withExtend (lib.fp.const lib)

View file

@ -1,22 +0,0 @@
let
lib = import ./../default.nix;
in
{
"extend" = {
"lib is extensible" =
let
result = lib.extend (
final: prev:
prev
// {
__injected__ = true;
fp = prev.fp // {
__injected__ = true;
};
}
);
in
result ? __injected__ && result.fp ? __injected__ && result.fp ? const;
};
}

View file

@ -1,10 +0,0 @@
lib: {
errors = {
## Prints a message if the condition is not met. The result of
## the condition is returned.
##
## @notest
## @type Bool -> String -> Bool
trace = condition: message: if condition then true else builtins.trace message false;
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

View file

@ -1,64 +0,0 @@
lib: {
fp = {
## A function that returns its argument.
##
## @type a -> a
id = x: x;
## Create a function that ignores its argument and returns a constant value.
##
## @type a -> b -> a
const = x: (_: x);
## Compose two functions to produce a new function that applies them both
## from right to left.
##
## @type Function f g => (b -> c) -> (a -> b) -> a -> c
compose = f: g: (x: f (g x));
## Process a value with a series of functions. Functions are applied in the
## order they are provided.
##
## @type (List (Any -> Any)) -> Any -> Any
pipe = fs: (x: builtins.foldl' (value: f: f value) x fs);
## Reverse the order of arguments to a function that has two parameters.
##
## @type (a -> b -> c) -> b -> a -> c
flip2 =
f: a: b:
f b a;
## Reverse the order of arguments to a function that has three parameters.
##
## @type (a -> b -> c -> d) -> c -> b -> a -> d
flip3 =
f: a: b: c:
f c b a;
## Reverse the order of arguments to a function that has four parameters.
##
## @type (a -> b -> c -> d -> e) -> d -> c -> b -> a -> e
flip4 =
f: a: b: c: d:
f d c b a;
## Get the arguments of a function or functor.
## An attribute set is returned with the arguments as keys. The values
## are `true` when the argument has a default value specified and `false`
## when it does not.
##
## @type Function -> Attrs
args =
f: if f ? __functor then f.__args__ or (lib.fp.args (f.__functor f)) else builtins.functionArgs f;
## Create a function that is called with only the arguments it specifies.
##
## @type Attrs a => (a -> b) -> a -> b
withDynamicArgs =
f: args:
let
fArgs = lib.fp.args f;
common = builtins.intersectAttrs fArgs args;
in
if builtins.isAttrs args then if fArgs == { } then f args else f common else f args;
};
}

View file

@ -1,165 +0,0 @@
let
lib = import ./../default.nix;
in
{
"id" = {
"returns its argument" =
let
expected = "foo";
actual = lib.fp.id expected;
in
actual == expected;
};
"const" = {
"creates a function that returns its argument" =
let
expected = "foo";
actual = lib.fp.const expected "bar";
in
actual == expected;
};
"compose" = {
"composes two functions" =
let
f = x: x + 1;
g = x: x * 2;
expected = 5;
actual = lib.fp.compose f g 2;
in
actual == expected;
};
"pipe" = {
"pipes two functions" =
let
f = x: x + 1;
g = x: x * 2;
expected = 5;
actual = lib.fp.pipe [
g
f
] 2;
in
actual == expected;
};
"flip2" = {
"flips the arguments of a binary function" =
let
f = a: b: a - b;
expected = 1;
actual = lib.fp.flip2 f 1 2;
in
actual == expected;
};
"flip3" = {
"flips the arguments of a ternary function" =
let
f =
a: b: c:
a - b - c;
expected = 0;
actual = lib.fp.flip3 f 1 2 3;
in
actual == expected;
};
"flip4" = {
"flips the arguments of a quaternary function" =
let
f =
a: b: c: d:
a - b - c - d;
expected = -2;
actual = lib.fp.flip4 f 1 2 3 4;
in
actual == expected;
};
"args" = {
"gets a functions attr set arguments" =
let
expected = {
x = false;
y = true;
};
actual = lib.fp.args (
{
x,
y ? null,
}:
null
);
in
actual == expected;
"returns an empty set if the function has no attrs arguments" =
let
expected = { };
actual = lib.fp.args (args: null);
in
actual == expected;
"supports functors" =
let
expected = {
x = false;
y = true;
};
actual = lib.fp.args {
__functor =
self:
{
x,
y ? null,
}:
null;
};
in
actual == expected;
"supports cached functor arguments" =
let
expected = {
x = false;
y = true;
};
actual = lib.fp.args {
__args__ = {
x = false;
y = true;
};
__functor = self: args: null;
};
in
actual == expected;
};
"withDynamicArgs" = {
"applies a function with dynamic arguments" =
let
expected = {
x = true;
};
actual = lib.fp.withDynamicArgs (args@{ x }: args) {
x = true;
y = true;
};
in
actual == expected;
"applies all arguments if none are specified" =
let
expected = {
x = true;
y = true;
};
actual = lib.fp.withDynamicArgs (args: args) expected;
in
actual == expected;
};
}

View file

@ -1,152 +0,0 @@
lib: {
generators = {
## Limit evaluation of a valud to a certain depth.
##
## @type { limit? :: Int | Null, throw? :: Bool } -> a -> a
withRecursion =
{
limit ? null,
throw ? true,
}:
assert builtins.isInt limit;
let
special = [
"__functor"
"__functionArgs"
"__toString"
"__pretty__"
];
attr = next: name: if builtins.elem name special then lib.fp.id else next;
transform =
depth:
if limit != null && depth > limit then
if throw then
builtins.throw "Exceeded maximum eval-depth limit of ${builtins.toString limit} while trying to evaluate with `lib.generators.withRecursion'!"
else
lib.fp.const "<unevaluated>"
else
lib.fp.id;
process =
depth: value:
let
next = x: process (depth + 1) (transform (depth + 1) x);
in
if builtins.isAttrs value then
builtins.mapAttrs (attr next) value
else if builtins.isList value then
builtins.map next value
else
transform (depth + 1) value;
in
process 0;
## Create a pretty printer for Nix values.
##
## @type { indent? :: String, multiline? :: Bool, allowCustomPrettifiers? :: Bool } -> a -> string
pretty =
{
indent ? "",
multiline ? true,
allowCustomPrettifiers ? false,
}:
let
process =
indent: value:
let
prefix = if multiline then "\n${indent} " else " ";
suffix = if multiline then "\n${indent}" else " ";
prettyNull = "null";
prettyNumber = lib.numbers.into.string value;
prettyBool = lib.bools.into.string value;
prettyPath = builtins.toString value;
prettyString =
let
lines = builtins.filter (x: !builtins.isList x) (builtins.split "\n" value);
escapeSingleline = lib.strings.escape.any [
"\\"
"\""
"\${"
];
escapeMultiline =
builtins.replaceStrings
[
"\${"
"''"
]
[
"''\${"
"'''"
];
singlelineResult = "\"" + lib.strings.concatMapSep "\\n" escapeSingleline lines + "\"";
multilineResult =
let
escapedLines = builtins.map escapeMultiline lines;
# The last line gets a special treatment: if it's empty, '' is on its own line at the "outer"
# indentation level. Otherwise, '' is appended to the last line.
lastLine = lib.lists.last escapedLines;
contents = builtins.concatStringsSep prefix (lib.lists.init escapedLines);
contentsSuffix = if lastLine == "" then suffix else prefix + lastLine;
in
"''" + prefix + contents + contentsSuffix + "''";
in
if multiline && builtins.length lines > 1 then multilineResult else singlelineResult;
prettyList =
let
contents = lib.strings.concatMapSep prefix (process (indent + " ")) value;
in
if builtins.length value == 0 then "[ ]" else "[${prefix}${contents}${suffix}]";
prettyFunction =
let
args = lib.fp.args value;
markArgOptional = name: default: if default then name + "?" else name;
argsWithDefaults = lib.attrs.mapToList markArgOptional args;
serializedArgs = builtins.concatStringsSep ", " argsWithDefaults;
in
if args == { } then "<function>" else "<function, args: {${serializedArgs}}>";
prettyAttrs =
let
contents = builtins.concatStringsSep prefix (
lib.attrs.mapToList (
name: value:
"${lib.strings.escape.nix.identifier name} = ${
builtins.addErrorContext "while evaluating an attribute `${name}`" (process (indent + " ") value)
};"
) value
);
in
if allowCustomPrettifiers && value ? __pretty__ && value ? value then
value.__pretty__ value.value
else if value == { } then
"{ }"
else if lib.packages.isDerivation value then
"<derivation ${value.name or "???"}>"
else
"{${prefix}${contents}${suffix}}";
in
if null == value then
prettyNull
else if builtins.isInt value || builtins.isFloat value then
prettyNumber
else if builtins.isBool value then
prettyBool
else if builtins.isString value then
prettyString
else if builtins.isPath value then
prettyPath
else if builtins.isList value then
prettyList
else if builtins.isFunction value then
prettyFunction
else if builtins.isAttrs value then
prettyAttrs
else
builtins.abort "lib.generators.pretty: should never happen (value = ${value})";
in
process indent;
};
}

View file

@ -1,111 +0,0 @@
let
lib = import ./../default.nix;
in
{
"withRecursion" = {
"evaluates within a given limit" =
let
expected = {
x = 1;
};
actual = lib.generators.withRecursion { limit = 100; } expected;
in
expected == actual;
"fails when the limit is reached" =
let
expected = {
x = 1;
};
actual = lib.generators.withRecursion { limit = -1; } expected;
evaluated = builtins.tryEval (builtins.deepSeq actual actual);
in
!evaluated.success;
"does not fail when throw is disabled" =
let
expected = {
x = "<unevaluated>";
};
actual = lib.generators.withRecursion {
limit = -1;
throw = false;
} { x = 1; };
evaluated = builtins.tryEval (builtins.deepSeq actual actual);
in
evaluated.success && evaluated.value == expected;
};
"pretty" = {
"formats with defaults" =
let
expected = ''
{
attrs = { };
bool = true;
float = 0.0;
function = <function>;
int = 0;
list = [ ];
string = "string";
}'';
actual = lib.generators.pretty { } {
attrs = { };
bool = true;
float = 0.0;
function = x: x;
int = 0;
list = [ ];
string = "string";
# NOTE: We are not testing `path` types because they can return out of store
# values which are not deterministic.
# path = ./.;
};
in
actual == expected;
"formats with custom prettifiers" =
let
expected = ''
{
attrs = { };
bool = true;
custom = <custom>;
float = 0.0;
function = <function>;
int = 0;
list = [ ];
string = "string";
}'';
actual = lib.generators.pretty { allowCustomPrettifiers = true; } {
attrs = { };
bool = true;
float = 0.0;
function = x: x;
int = 0;
list = [ ];
string = "string";
custom = {
value = 0;
__pretty__ = value: "<custom>";
};
};
in
actual == expected;
"formats with multiline disabled" =
let
expected = "{ attrs = { }; bool = true; float = 0.0; function = <function>; int = 0; list = [ ]; string = \"string\"; }";
actual = lib.generators.pretty { multiline = false; } {
attrs = { };
bool = true;
float = 0.0;
function = x: x;
int = 0;
list = [ ];
string = "string";
};
in
actual == expected;
};
}

View file

@ -1,15 +0,0 @@
lib: {
importers = {
## Import a JSON file as a Nix value.
##
## @notest
## @type Path -> a
json = file: builtins.fromJSON (builtins.readFile file);
## Import a TOML file as a Nix value.
##
## @notest
## @type Path -> a
toml = file: builtins.fromTOML (builtins.readFile file);
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

File diff suppressed because it is too large Load diff

View file

@ -1,29 +0,0 @@
lib: {
licenses =
let
raw = import ./all.nix;
defaults = name: {
inherit name;
free = true;
};
withDefaults = name: license: (defaults name) // license;
withSpdx =
license:
if license ? spdx then
license // { url = "https://spdx.org/licenses/${license.spdx}.html"; }
else
license;
withRedistributable = license: { redistributable = license.free; } // license;
normalize =
name:
lib.fp.pipe [
(withDefaults name)
withSpdx
withRedistributable
];
in
builtins.mapAttrs normalize raw;
}

View file

@ -1,234 +0,0 @@
lib: {
lists = {
from = {
## Convert a value to a list. If the value is already a list,
## it will be returned as-is. If the value is not a list, it
## will be wrapped in a list.
##
## @type a | (List a) -> List a
any = value: if builtins.isList value then value else [ value ];
};
sort = {
## Perform a natural sort on a list of strings.
##
## @type List String -> List String
natural =
list:
let
vectorize =
string:
let
serialize = part: if builtins.isList part then lib.strings.into.int (builtins.head part) else part;
parts = lib.strings.split "(0|[1-9][0-9]*)" string;
in
builtins.map serialize parts;
prepared = builtins.map (value: [
(vectorize value)
value
]) list;
isLess = a: b: (lib.lists.compare lib.numbers.compare (builtins.head a) (builtins.head b)) < 0;
in
builtins.map (x: builtins.elemAt x 1) (builtins.sort isLess prepared);
## Perform a topological sort on a list of items. The predicate function determines whether
## its first argument comes before the second argument.
##
## @type (a -> a -> Bool) -> List a -> List a
topological =
predicate: list:
let
searched = lib.lists.search.depthFirst true predicate list;
results = lib.lists.sort.topological predicate (searched.visited ++ searched.rest);
in
if builtins.length list < 2 then
{ result = list; }
else if searched ? cycle then
{
loops = searched.loops;
cycle = lib.lists.reverse ([ searched.cycle ] ++ searched.visited);
}
else if results ? cycle then
results
else
{ result = [ searched.minimal ] ++ results.result; };
};
search = {
## Perform a depth first search on a list. The supplied predicate function determines whether
## its first argument comes before the second argument.
##
## @type Bool -> (a -> a -> Bool) -> List a
depthFirst =
isAcyclical: predicate: list:
let
process =
current: visited: rest:
let
loops = builtins.filter (value: predicate value current) visited;
partitioned = builtins.partition (value: predicate value current) rest;
in
if isAcyclical && (builtins.length loops > 0) then
{
cycle = current;
inherit loops visited rest;
}
else if builtins.length partitioned.right == 0 then
{
minimal = current;
inherit visited rest;
}
else
process (builtins.head partitioned.right) ([ current ] ++ visited) (
builtins.tail partitioned.right ++ partitioned.wrong
);
in
process (builtins.head list) [ ] (builtins.tail list);
};
## Map a list using both the index and value of each item. The
## index starts at 0.
##
## @type (Int -> a -> b) -> List a -> List b
mapWithIndex = f: list: builtins.genList (i: f i (builtins.elemAt list i)) (builtins.length list);
## Map a list using both the index and value of each item. The
## index starts at 1.
##
## @type (Int -> a -> b) -> List a -> List b
mapWithIndex1 =
f: list: builtins.genList (i: f (i + 1) (builtins.elemAt list i)) (builtins.length list);
## Compare two lists using a custom compare function. The compare
## function is called for each element in the lists that need to
## be compared.
##
## @type (a -> b -> -1 | 0 | 1) -> List a -> List b -> Int
compare =
compare: a: b:
let
result = compare (builtins.head a) (builtins.head b);
in
if a == [ ] then
if b == [ ] then 0 else -1
else if b == [ ] then
1
else if result == 0 then
lib.lists.compare compare (builtins.tail a) (builtins.tail b)
else
result;
## Get the last element of a list.
##
## @type List a -> a
last =
list:
assert lib.errors.trace (list != [ ]) "List cannot be empty";
builtins.elemAt list (builtins.length list - 1);
## Slice part of a list to create a new list.
##
## @type Int -> Int -> List -> List
slice =
start: count: list:
let
listLength = builtins.length list;
resultLength =
if start >= listLength then
0
else if start + count > listLength then
listLength - start
else
count;
in
builtins.genList (i: builtins.elemAt list (start + i)) resultLength;
## Take the first n elements of a list.
##
## @type Int -> List -> List
take = lib.lists.slice 0;
## Drop the first n elements of a list.
##
## @type Int -> List -> List
drop =
count: list:
let
listLength = builtins.length list;
in
lib.lists.slice count listLength list;
## Take all but the last element of a list.
##
## @type List -> List
init =
list:
assert lib.errors.trace (builtins.length list != 0) "lib.lists.init: list must not be empty.";
lib.lists.take (builtins.length list - 1) list;
## Reverse a list.
##
## @type List -> List
reverse =
list:
let
length = builtins.length list;
create = i: builtins.elemAt list (length - i - 1);
in
builtins.genList create length;
## Interleave a list with a separator.
##
## @type Separator -> List -> List
intersperse =
separator: list:
let
length = builtins.length list;
in
if length < 2 then
list
else
builtins.tail (
builtins.concatMap (part: [
separator
part
]) list
);
## Create a list of integers from a starting number to an ending
## number. This *includes* the ending number as well.
##
## @type Int -> Int -> List
range = start: end: if start > end then [ ] else builtins.genList (i: start + i) (end - start + 1);
## Depending on a given condition, either use the given value (as
## a list) or an empty list.
##
## @type Attrs a b => Bool -> a -> a | b
when =
condition: value: if condition then if builtins.isList value then value else [ value ] else [ ];
## Count the number of items in a list that satisfy a given predicate.
##
## @type (a -> Bool) -> List a -> Int
count =
predicate: list:
builtins.foldl' (total: value: if predicate value then total + 1 else total) 0 list;
## Remove duplicate items from a list.
##
## @type List -> List
unique =
list:
let
filter = result: value: if builtins.elem value result then result else result ++ [ value ];
in
builtins.foldl' filter [ ] list;
## Flatten a list of lists into a single list.
##
## @type List (List a) -> List a
flatten =
value: if builtins.isList value then builtins.concatMap lib.lists.flatten value else [ value ];
};
}

View file

@ -1,345 +0,0 @@
let
lib = import ./../default.nix;
in
{
"from" = {
"any" = {
"returns a list containing the value" =
let
expected = [ 1 ];
actual = lib.lists.from.any 1;
in
actual == expected;
"returns the value if the value was already a list" =
let
expected = [ 1 ];
actual = lib.lists.from.any expected;
in
actual == expected;
};
};
"sort" = {
"natural" = {
"sorts a list of strings" =
let
expected = [
"1"
"a"
"a0"
"a1"
"b"
"c"
];
actual = lib.lists.sort.natural [
"c"
"a"
"b"
"a1"
"a0"
"1"
];
in
actual == expected;
};
};
"mapWithIndex" = {
"maps a list using index 0" =
let
expected = [
"0: a"
"1: b"
"2: c"
];
actual = lib.lists.mapWithIndex (i: v: "${builtins.toString i}: ${v}") [
"a"
"b"
"c"
];
in
actual == expected;
};
"mapWithIndex1" = {
"maps a list using index 1" =
let
expected = [
"1: a"
"2: b"
"3: c"
];
actual = lib.lists.mapWithIndex1 (i: v: "${builtins.toString i}: ${v}") [
"a"
"b"
"c"
];
in
actual == expected;
};
"compare" = {
"compares two lists" = {
"returns -1 if the first list is smaller" =
let
expected = -1;
actual =
lib.lists.compare lib.numbers.compare
[
1
2
3
]
[
1
2
4
];
in
actual == expected;
"returns 1 if the first list is larger" =
let
expected = 1;
actual =
lib.lists.compare lib.numbers.compare
[
1
2
4
]
[
1
2
3
];
in
actual == expected;
"returns 0 if the lists are equal" =
let
expected = 0;
actual =
lib.lists.compare lib.numbers.compare
[
1
2
3
]
[
1
2
3
];
in
actual == expected;
};
};
"last" = {
"returns the last element of a list" =
let
expected = 3;
actual = lib.lists.last [
1
2
3
];
in
actual == expected;
"fails if the list is empty" =
let
actual = lib.lists.last [ ];
evaluated = builtins.tryEval actual;
in
!evaluated.success;
};
"slice" = {
"slices a list" = {
"slices a list from the start" =
let
expected = [
1
2
];
actual = lib.lists.slice 0 2 [
1
2
3
];
in
actual == expected;
"slices a list from the end" =
let
expected = [
2
3
];
actual = lib.lists.slice 1 3 [
1
2
3
];
in
actual == expected;
"slices a list from the middle" =
let
expected = [ 2 ];
actual = lib.lists.slice 1 1 [
1
2
3
];
in
actual == expected;
};
};
"take" = {
"takes the first n elements" =
let
expected = [
1
2
];
actual = lib.lists.take 2 [
1
2
3
];
in
actual == expected;
};
"drop" = {
"drops the first n elements" =
let
expected = [ 3 ];
actual = lib.lists.drop 2 [
1
2
3
];
in
actual == expected;
};
"reverse" = {
"reverses a list" =
let
expected = [
3
2
1
];
actual = lib.lists.reverse [
1
2
3
];
in
actual == expected;
};
"intersperse" = {
"intersperses a list with a separator" =
let
expected = [
1
"-"
2
"-"
3
];
actual = lib.lists.intersperse "-" [
1
2
3
];
in
actual == expected;
"handles lists with less than 2 elements" =
let
expected = [ 1 ];
actual = lib.lists.intersperse "-" [ 1 ];
in
actual == expected;
};
"range" = {
"returns a range of numbers" =
let
expected = [
1
2
3
4
5
];
actual = lib.lists.range 1 5;
in
actual == expected;
};
"when" = {
"returns the list if the condition is true" =
let
expected = [
1
2
3
];
actual = lib.lists.when true [
1
2
3
];
in
actual == expected;
"returns an empty list if the condition is false" =
let
expected = [ ];
actual = lib.lists.when false [
1
2
3
];
in
actual == expected;
};
"count" = {
"counts the number of elements in a list" =
let
expected = 2;
actual = lib.lists.count (value: value < 3) [
1
2
3
];
in
actual == expected;
};
"unique" = {
"removes duplicate elements" =
let
expected = [
1
2
3
];
actual = lib.lists.unique [
1
2
3
1
2
3
];
in
actual == expected;
};
}

View file

@ -1,13 +0,0 @@
lib: {
math = {
## Return the smaller of two numbers.
##
## @type Int -> Int -> Int
min = x: y: if x < y then x else y;
## Return the larger of two numbers.
##
## @type Int -> Int -> Int
max = x: y: if x > y then x else y;
};
}

View file

@ -1,22 +0,0 @@
let
lib = import ./../default.nix;
in
{
"min" = {
"returns the smaller number" =
let
expected = 1;
actual = lib.math.min 1 2;
in
actual == expected;
};
"max" = {
"returns the larger number" =
let
expected = 2;
actual = lib.math.max 1 2;
in
actual == expected;
};
}

View file

@ -1,702 +0,0 @@
lib: {
modules = {
from = {
## Create a module from a JSON file.
##
## @notest
## @type Path -> Module
json = file: {
__file__ = file;
config = lib.importers.json file;
};
## Create a module from a TOML file.
##
## @notest
## @type Path -> Module
toml = file: {
__file__ = file;
config = lib.importers.toml file;
};
};
apply = {
## Apply custom definitions such as `merge` and `when` to a definition.
## Note that this function does not perform actiosn like `merge`, but
## instead pulls out the merge contents to be processed by the module
## system.
##
## @type Definition -> List (Definition | (List Definition))
properties =
definition:
if lib.types.is "merge" definition then
builtins.concatMap lib.modules.apply.properties definition.content
else if lib.types.is "when" definition then
if !(builtins.isBool definition.condition) then
builtins.throw "lib.modules.when called with a non-boolean condition"
else if definition.condition then
lib.modules.apply.properties definition.content
else
[ ]
else
[ definition ];
## Apply overrides for a definition. This uses the priority system
## to determine which definition to use. The most important (lowest
## priority) choice will be used.
##
## @type List Definition -> { highestPriority :: Int, values :: List (Definition | (List Definition)) }
overrides =
definitions:
let
getPriority =
definition:
if lib.types.is "override" definition.value then
definition.value.priority
else
lib.modules.DEFAULT_PRIORITY;
normalize =
definition:
if lib.types.is "override" definition.value then
definition // { value = definition.value.content; }
else
definition;
highestPriority = builtins.foldl' (
priority: definition: lib.math.min priority (getPriority definition)
) 9999 definitions;
in
{
inherit highestPriority;
values = builtins.concatMap (
definition: if getPriority definition == highestPriority then [ (normalize definition) ] else [ ]
) definitions;
};
## Apply ordering for prioritized definitions.
##
## @type List Definition -> List Definition
order =
definitions:
let
normalize =
definition:
if lib.types.is "order" definition then
definition
// {
value = definition.value.content;
priority = definition.value.priority;
}
else
definition;
normalized = builtins.map normalize definitions;
compare =
a: b: (a.priority or lib.modules.DEFAULT_PRIORITY) < (b.priority or lib.modules.DEFAULT_PRIORITY);
in
builtins.sort compare normalized;
## Normalize the type of an option. This will set a default type if none
## was provided.
##
## @type List String -> Option -> Option
fixup =
location: option:
if option.type.submodules or null == null then
option // { type = option.type or lib.types.unspecified; }
else
option
// {
type = option.type.withSubModules option.options;
options = [ ];
};
## Invert the structure of `merge`, `when`, and `override` definitions so
## that they apply to each individual attribute in their respective sets.
## Note that this function _only_ supports attribute sets within specialized
## definitions such as `when` and `override`. Other values like lists will
## throw a type error.
##
## @type Definition -> List Definition
invert =
config:
if lib.types.is "merge" config then
builtins.concatMap lib.modules.apply.invert config.content
else if lib.types.is "when" config then
builtins.map (builtins.mapAttrs (name: value: lib.modules.when config.condition value)) (
lib.modules.apply.invert config.content
)
else if lib.types.is "override" config then
builtins.map (builtins.mapAttrs (name: value: lib.modules.override config.priority value)) (
lib.modules.apply.invert config.content
)
else
[ config ];
};
validate = {
## Check that a module only specifies supported attributes.
##
## @type Attrs -> Bool
keys =
module:
let
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
in
invalid == { };
};
## Modules only support certain keys at the root level. This list determines
## the valid attributes that users can supply.
##
## @type List String
VALID_KEYS = [
"__file__"
"__key__"
"includes"
"excludes"
"options"
"config"
"freeform"
"meta"
];
## Normalize a module to a standard structure. All other information will be
## lost in the conversion.
##
## @type String -> String -> Attrs -> Module
normalize =
file: key: module:
let
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
invalidKeys = builtins.concatStringsSep ", " (builtins.attrNames invalid);
in
if lib.modules.validate.keys module then
{
__file__ = builtins.toString module.__file__ or file;
__key__ = builtins.toString module.__key__ or key;
includes = module.includes or [ ];
excludes = module.excludes or [ ];
options = module.options or { };
config =
let
base = module.config or { };
withMeta =
config:
if module ? meta then
lib.modules.merge [
config
{ meta = module.meta; }
]
else
config;
withFreeform =
config:
if module ? freeform then
lib.modules.merge [
config
{ __module__.freeform = module.freeform; }
]
else
config;
in
withFreeform (withMeta base);
}
else
builtins.throw "Module `${key}` (${file}) has unsupported attribute(s): ${invalidKeys}";
## Convert a module that is either a function or an attribute set into
## a resolved attribute set. If the module was a function then it will
## be evaluated and the result will be returned.
##
## @type String -> Attrs | (Attrs -> Attrs) -> Attrs -> Attrs
resolve =
key: module: args:
let
dynamicArgs = builtins.mapAttrs (
name: value:
builtins.addErrorContext "while evaluating the module argument `${name}` in `${key}`" (
# The base case here is the set of static arguments supplied in the
# call to `lib.modules.run`.
args.${name}
# Then, any other arguments must be created dynamically from within
# the modules being evaluated.
or args.config.__module__.args.dynamic.${name}
)
) (lib.fp.args module);
in
if builtins.isFunction module then lib.fp.withDynamicArgs module (args // dynamicArgs) else module;
## The default priority to set for values that do not have one provided.
##
## @type Int
DEFAULT_PRIORITY = 100;
## Allow for sorting the values provided to a module by priority. The
## most important value will be used.
##
## @notest
## @type Int -> a -> Attrs
order = priority: value: {
__type__ = "order";
inherit priority value;
};
orders = {
## Order a value before others.
##
## @notest
## @type a -> Attrs
before = lib.modules.order 500;
## Use the default ordering for a value.
##
## @notest
## @type a -> Attrs
default = lib.modules.order 1000;
## Order a value after others.
##
## @notest
## @type a -> Attrs
after = lib.modules.order 1500;
};
## Extract a list of files from a list of modules.
##
## @type List Module -> List (String | Path)
getFiles = builtins.map (module: module.__file__);
## Create a conditional value which is only used when the condition's
## value is `true`.
##
## @notest
## @type Bool -> a -> When a
when = condition: content: {
__type__ = "when";
inherit condition content;
};
## Merge module attribute sets when evaluated in the module system.
##
## @notest
## @type List Attrs -> Attrs
merge = content: {
__type__ = "merge";
inherit content;
};
## Create a value override which can replace other definitions if it
## has the higher priority.
##
## @notest
## @type Int -> a -> Attrs
override = priority: content: {
__type__ = "override";
inherit priority content;
};
overrides = {
## Create an override used for setting the `default.value` from an
## option. This uses the lowest priority of all predefined overrides.
##
## @notest
## @type a -> Attrs
option = lib.modules.override 1500;
## Create a default override for a value. This uses a very low priority
## so that it can easily be overridden.
##
## @notest
## @type a -> Attrs
default = lib.modules.override 1000;
## Create a high priority override for a value. This is not the highest
## priority possible, but it will override nearly everything else except
## for very explicit cases.
##
## @notest
## @type a -> Attrs
force = lib.modules.override 50;
## Create a high priority override intended to be used only for VM targets.
## This allows for forcing certain values even if a user has otherwise
## specified `lib.modules.overrides.force`.
##
## @notest
## @type a -> Attrs
vm = lib.modules.override 10;
};
## Create alias definitions for a given option.
##
## @type Option -> Attrs
alias = lib.modules.aliasWith lib.fp.id;
## Create alias definitions for a given option.
##
## @type (Attrs -> Attrs) -> Option -> Attrs
aliasWith =
f: option:
let
exists = lib.types.is "option" option && option.isDefined;
value = f (lib.modules.merge option.definitions);
in
lib.modules.when exists value;
## Combine multiple modules together.
##
## @type List String -> List Module -> { matched :: Attrs, unmatched :: List Definition }
combine =
prefix: modules:
let
getConfig =
module:
builtins.map (config: {
__file__ = module.__file__;
inherit config;
}) (lib.modules.apply.invert module.config);
configs = builtins.concatMap getConfig modules;
process =
prefix: options: configs:
let
byName =
attr: f: modules:
builtins.zipAttrsWith (key: value: builtins.concatLists value) (
builtins.map (
module:
let
subtree = module.${attr};
in
if builtins.isAttrs subtree then
builtins.mapAttrs (name: f module) subtree
else
builtins.throw "Value for `${builtins.concatStringsSep "." prefix}` is of type `${builtins.typeOf subtree}` but an attribute set was expected."
) modules
);
declarationsByName = byName "options" (module: option: [
{
__file__ = module.__file__;
options = option;
}
]) options;
definitionsByName = byName "config" (
module: value:
builtins.map (config: {
__file__ = module.__file__;
inherit config;
}) (lib.modules.apply.invert value)
) configs;
definitionsByName' = byName "config" (module: value: [
{
__file__ = module.__file__;
inherit value;
}
]) configs;
getOptionFromDeclaration =
declaration:
if lib.types.is "option" declaration.options then
declaration
else
declaration
// {
options = lib.options.create { type = lib.types.submodule [ { options = declaration.options; } ]; };
};
resultsByName = builtins.mapAttrs (
name: declarations:
let
location = prefix ++ [ name ];
definitions = definitionsByName.${name} or [ ];
definitions' = definitionsByName'.${name} or [ ];
optionDeclarations = builtins.filter (
declaration: lib.types.is "option" declaration.options
) declarations;
in
if builtins.length optionDeclarations == builtins.length declarations then
let
option = lib.modules.apply.fixup location (lib.options.merge.declarations location declarations);
in
{
matched = lib.options.run location option definitions';
unmatched = [ ];
}
else if optionDeclarations != [ ] then
if builtins.all (declaration: declaration.options.type.name == "Submodule") optionDeclarations then
let
option = lib.modules.apply.fixup location (
lib.options.merge.declarations location (builtins.map getOptionFromDeclaration declarations)
);
in
{
matched = lib.options.run location option definitions';
unmatched = [ ];
}
else
builtins.throw "The option `${lib.options.getIdentifier location}` in module `${(builtins.head optionDeclarations).__file__}` would be a parent of the following options, but its type `${
(builtins.head optionDeclarations).options.type.description or "<no description>"
}` does not support nested options."
else
process location declarations definitions
) declarationsByName;
matched = builtins.mapAttrs (key: value: value.matched) resultsByName;
unmatched =
builtins.mapAttrs (name: value: value.unmatched) resultsByName
// builtins.removeAttrs definitionsByName' (builtins.attrNames matched);
in
{
inherit matched;
unmatched =
if configs == [ ] then
[ ]
else
builtins.concatLists (
lib.attrs.mapToList (
name: definitions:
builtins.map (
definition: definition // { prefix = [ name ] ++ (definition.prefix or [ ]); }
) definitions
) unmatched
);
};
in
process prefix modules configs;
## Run a set of modules. Custom arguments can also be supplied which will
## be provided to all modules statically as they are not modifiable.
##
## @type { modules? :: List (Attrs | (Attrs -> Attrs)), args? :: Attrs, prefix? :: List String } -> { type :: Module, extend :: lib.modules.run, options :: Attrs, config :: Attrs, __module__ :: Attrs }
run =
settings@{
modules ? [ ],
args ? { },
prefix ? [ ],
}:
let
type = lib.types.submodules.of { inherit modules args; };
extend =
extensions@{
modules ? [ ],
args ? { },
prefix ? [ ],
}:
lib.modules.run {
modules = (settings.modules or [ ]) ++ (extensions.modules or [ ]);
args = (settings.args or { }) // (extensions.args or { });
prefix = extensions.prefix or settings.prefix or [ ];
};
collect =
let
load =
args: file: key: module:
let
moduleFromValue = lib.modules.normalize file key (lib.modules.resolve key module args);
moduleFromPath = lib.modules.normalize (builtins.toString module) (builtins.toString module) (
lib.modules.resolve (builtins.toString module) (import module) args
);
in
if builtins.isAttrs module || builtins.isFunction module then
moduleFromValue
else if builtins.isString module || builtins.isPath module then
moduleFromPath
else
builtins.throw "The provided module must be either an attribute set, function, or path but got ${builtins.typeOf module}";
normalize =
parentFile: parentKey: modules: args:
let
normalized = lib.lists.mapWithIndex1 (
i: value:
let
module = load args parentFile "${parentKey}:unknown-${builtins.toString i}" value;
tree = normalize module.__file__ module.__key__ module.includes args;
in
{
inherit module;
key = module.__key__;
modules = tree.modules;
excludes = module.excludes ++ tree.excludes;
}
) modules;
in
{
modules = normalized;
excludes = builtins.concatLists (builtins.catAttrs "excludes" normalized);
};
withExclusions =
path:
{ modules, excludes }:
let
getKey =
module:
if builtins.isString module && (builtins.substring 0 1 module != "/") then
(builtins.toString path) + "/" + module
else
builtins.toString module;
excludedKeys = builtins.map getKey excludes;
removeExcludes = builtins.filter (value: !(builtins.elem value.key excludedKeys));
in
builtins.map (value: value.module) (
builtins.genericClosure {
startSet = removeExcludes modules;
operator = value: removeExcludes value.modules;
}
);
process =
path: modules: args:
withExclusions path (normalize "<unknown>" "" modules args);
in
process;
internal = {
__file__ = "virtual:aux/internal";
__key__ = "virtual:aux/internal";
options = {
__module__ = {
args = {
static = lib.options.create {
type = lib.types.attrs.lazy lib.types.raw;
writable = false;
internal = false;
description = "Static arguments provided to lib.modules.run which cannot be changed.";
};
dynamic = lib.options.create {
type = lib.types.attrs.lazy lib.types.raw;
${if prefix == [ ] then null else "internal"} = true;
visible = false;
description = "Additional arguments pased to each module.";
};
};
check = lib.options.create {
type = lib.types.bool;
default.value = true;
internal = true;
description = "Whether to perform checks on option definitions.";
};
freeform = lib.options.create {
type = lib.types.nullish lib.types.type;
default.value = null;
internal = true;
description = "If set, all options that don't have a declared type will be merged using this type.";
};
};
};
config = {
__module__ = {
args = {
static = args;
dynamic = {
meta = {
inherit extend type;
};
};
};
};
};
};
merged =
let
collected = collect (args.path or "") ([ internal ] ++ modules) (
{
inherit options config;
# The library does not include `extend` out of the box to avoid infinite recursion
# in most cases. Instead, we supply an estensible version here for modules to consume.
# This is only something that affects internal use of `lib`, the properly exported
# `lib` is augmented with an `extend` method.
lib = lib.points.withExtend (lib.fp.const lib);
}
// args
);
in
lib.modules.combine prefix (lib.lists.reverse collected);
options = merged.matched;
config =
let
declared = lib.attrs.mapRecursiveWhen (value: !(lib.types.is "option" value)) (
name: value: value.value
) options;
freeform =
let
definitions = builtins.map (definition: {
__file__ = definition.__file__;
value = lib.attrs.set definition.prefix definition.value;
}) merged.unmatched;
in
if definitions == [ ] then { } else declared.__module__.freeform.merge prefix definitions;
in
if declared.__module__.freeform == null then
declared
else
lib.attrs.mergeRecursive freeform declared;
checked =
if config.__module__.check && config.__module__.freeform == null && merged.unmatched != [ ] then
let
first = builtins.head merged.unmatched;
identifier = lib.options.getIdentifier (prefix ++ first.prefix);
definitions =
builtins.addErrorContext
"while evaluating the error message for definitions of non-existent option `${identifier}`"
(
builtins.addErrorContext "while evaluating a definition from `${first.__file__}`" (
lib.options.getDefinitions [ first ]
)
);
message = "The option `${identifier}` does not exist. Definitions:${definitions}";
in
if builtins.attrNames options == [ "__module__" ] then
if lib.options.getIdentifier prefix == "" then
builtins.throw ''
${message}
You are trying to declare options in `config` rather than `options`.
''
else
builtins.throw ''
${message}
There are no options defined in `${lib.options.getIdentifier prefix}`.
Are you sure you declared your options correctly?
''
else
builtins.throw message
else
null;
withCheck = builtins.seq checked;
in
{
inherit type extend;
options = withCheck options;
config = withCheck (builtins.removeAttrs config [ "__module__" ]);
__module__ = withCheck config.__module__;
};
};
}

View file

@ -1,709 +0,0 @@
let
lib = import ./../default.nix;
in
{
"apply" = {
"properties" = {
"handles normal values" =
let
expected = [ { } ];
actual = lib.modules.apply.properties { };
in
actual == expected;
"handles merge" =
let
expected = [
{ x = 1; }
{ x = 2; }
];
actual = lib.modules.apply.properties (
lib.modules.merge [
{ x = 1; }
{ x = 2; }
]
);
in
actual == expected;
"handles when" =
let
expected = [ [ { x = 1; } ] ];
actual = lib.modules.apply.properties (lib.modules.when true [ { x = 1; } ]);
in
actual == expected;
};
"overrides" = {
"handles normal values" =
let
expected = {
highestPriority = 100;
values = [ { value = 1; } ];
};
actual = lib.modules.apply.overrides [ { value = 1; } ];
in
actual == expected;
"handles overrides" =
let
expected = {
highestPriority = 100;
values = [ { value = "world"; } ];
};
actual = lib.modules.apply.overrides [
{ value = "world"; }
{ value = lib.modules.override 101 "hello"; }
];
in
actual == expected;
};
"order" = {
"handles normal values" =
let
expected = [ { } ];
actual = lib.modules.apply.order [ { } ];
in
actual == expected;
"handles priority" =
let
expected = [
{
value = 1;
priority = 10;
}
{
value = 3;
priority = 50;
}
{ value = 2; }
];
actual = lib.modules.apply.order [
{
value = 1;
priority = 10;
}
{ value = 2; }
{
value = 3;
priority = 50;
}
];
in
actual == expected;
};
"fixup" = {
"sets default type for option" =
let
actual = lib.modules.apply.fixup [ ] (lib.options.create { });
in
actual.type.name == "Unspecified";
};
"invert" = {
"inverts merge" =
let
expected = [
{ x = 1; }
{ x = 2; }
];
actual = lib.modules.apply.invert (
lib.modules.merge [
{ x = 1; }
{ x = 2; }
]
);
in
actual == expected;
"inverts when" =
let
expected = [
{
x = {
__type__ = "when";
condition = true;
content = 1;
};
y = {
__type__ = "when";
condition = true;
content = 2;
};
}
];
actual = lib.modules.apply.invert (
lib.modules.when true {
x = 1;
y = 2;
}
);
in
actual == expected;
"inverts overrides" =
let
expected = [
{
x = {
__type__ = "override";
priority = 100;
content = 1;
};
y = {
__type__ = "override";
priority = 100;
content = 2;
};
}
];
actual = lib.modules.apply.invert (
lib.modules.override 100 {
x = 1;
y = 2;
}
);
in
actual == expected;
};
};
"validate" = {
"keys" = {
"handles an empty set" =
let
value = lib.modules.validate.keys { };
in
value;
"handles a valid module" =
let
value = lib.modules.validate.keys {
__file__ = "virtual:aux/example";
__key__ = "aux/example";
includes = [ ];
excludes = [ ];
options = { };
config = { };
freeform = null;
meta = { };
};
in
value;
"handles an invalid module" =
let
value = lib.modules.validate.keys { invalid = null; };
in
!value;
};
};
"normalize" = {
"handles an empty set" =
let
expected = {
__file__ = "/aux/example.nix";
__key__ = "example";
config = { };
excludes = [ ];
includes = [ ];
options = { };
};
actual = lib.modules.normalize "/aux/example.nix" "example" { };
in
actual == expected;
"handles an example module" =
let
expected = {
__file__ = "myfile.nix";
__key__ = "mykey";
config = {
x = true;
};
excludes = [ ];
includes = [ ];
options = { };
};
actual = lib.modules.normalize "/aux/example.nix" "example" {
__file__ = "myfile.nix";
__key__ = "mykey";
config.x = true;
};
in
actual == expected;
};
"resolve" = {
"handles an attribute set" =
let
expected = {
config.x = 1;
};
actual = lib.modules.resolve "example" { config.x = 1; } { };
in
actual == expected;
"handles a function" =
let
expected = {
config.x = 1;
};
actual = lib.modules.resolve "example" (lib.fp.const { config.x = 1; }) { };
in
actual == expected;
"handles a function with arguments" =
let
expected = {
config.x = 1;
};
actual = lib.modules.resolve "example" (args: { config.x = args.x; }) { x = 1; };
in
actual == expected;
};
"getFiles" = {
"gets the files for a list of modules" =
let
expected = [ "/aux/example.nix" ];
actual = lib.modules.getFiles [ { __file__ = "/aux/example.nix"; } ];
in
actual == expected;
};
"combine" = {
"handles empty modules" =
let
expected = {
matched = { };
unmatched = [ ];
};
actual = lib.modules.combine [ ] [ (lib.modules.normalize "/aux/example.nix" "example" { }) ];
in
actual == expected;
"handles a single module" =
let
expected = {
matched = { };
unmatched = [
{
__file__ = "/aux/example.nix";
prefix = [ "x" ];
value = 1;
}
];
};
actual =
lib.modules.combine
[ ]
[
(lib.modules.normalize "/aux/example.nix" "example" {
config = {
x = 1;
};
})
];
in
actual == expected;
"handles multiple modules" =
let
unmatched = [
{
__file__ = "/aux/example2.nix";
prefix = [ "y" ];
value = 2;
}
];
actual =
lib.modules.combine
[ ]
[
(lib.modules.normalize "/aux/example1.nix" "example2" {
options = {
x = lib.options.create { };
};
config = {
x = 1;
};
})
(lib.modules.normalize "/aux/example2.nix" "example2" {
config = {
y = 2;
};
})
];
in
(actual.unmatched == unmatched) && actual.matched ? x;
};
"run" = {
"empty" =
let
evaluated = lib.modules.run {
modules = [
{
options.aux = {
message = lib.options.create { type = lib.types.string; };
};
config = {
aux.message = "Hello, World!";
};
}
];
};
in
evaluated ? config;
"hello world" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options.aux = {
message = lib.options.create { type = lib.types.string; };
};
config = {
aux.message = "Hello, World!";
};
}
];
};
actual = evaluated.config.aux.message;
in
actual == expected;
"recursive" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
(
{ config }:
{
options.aux = {
message = lib.options.create { type = lib.types.string; };
proxy = lib.options.create { type = lib.types.string; };
};
config = {
aux = {
proxy = "Hello, World!";
message = config.aux.proxy;
};
};
}
)
];
};
actual = evaluated.config.aux.message;
in
actual == expected;
"conditional" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options.aux = {
message = lib.options.create { type = lib.types.string; };
};
config = {
aux = {
message = lib.modules.when true expected;
};
};
}
];
};
in
evaluated.config.aux.message == expected;
"conditional list" =
let
expected = [ "Hello, World!" ];
evaluated = lib.modules.run {
modules = [
{
options.aux = {
message = lib.options.create { type = lib.types.list.of lib.types.string; };
};
config = {
aux = {
message = lib.modules.when true expected;
};
};
}
];
};
in
evaluated.config.aux.message == expected;
"submodules" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
default.value = {
message = "hi";
};
type = lib.types.attrs.of (
lib.types.submodule (
{ name }:
{
# freeform = lib.types.any;
options.message = lib.options.create { type = lib.types.string; };
}
)
);
};
};
config = {
aux = {
x = {
message = "Hello, World!";
};
};
};
}
];
};
in
evaluated.config.aux.x.message == expected;
"submodules without shorthand" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodules.of {
shorthand = false;
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
};
};
};
config = {
aux = {
options = {
message2 = lib.options.create { type = lib.types.string; };
};
config = {
message = expected;
message2 = expected;
};
};
};
}
];
};
in
evaluated.config.aux.message == evaluated.config.aux.message2;
"function submodules" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodules.of {
shorthand = false;
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
};
};
};
config = {
aux = args: { config.message = expected; };
};
}
];
};
in
evaluated.config.aux.message == expected;
"merges submodules" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodules.of {
shorthand = false;
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
};
};
};
config = {
aux = args: {
options.message2 = lib.options.create { type = lib.types.string; };
config.message = expected;
};
};
}
{
config = {
aux.config.message2 = expected;
};
}
];
};
in
evaluated.config.aux.message == evaluated.config.aux.message2;
"flexible shorthand" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodules.of {
shorthand = true;
modules = [ { options.message = lib.options.create { type = lib.types.string; }; } ];
};
};
};
config = {
aux = args: {
options.message2 = lib.options.create { type = lib.types.string; };
config.message = expected;
};
};
}
{
config = {
aux.message2 = expected;
};
}
];
};
in
evaluated.config.aux.message == evaluated.config.aux.message2;
"base level submodule" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodule { options.message = lib.options.create { type = lib.types.string; }; };
};
};
config = {
aux = args: {
options.message2 = lib.options.create { type = lib.types.string; };
config.message = expected;
};
};
}
{
config = {
aux.message2 = expected;
};
}
];
};
in
evaluated.config.aux.message == evaluated.config.aux.message2;
"base level submodule (freeform)" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodule {
freeform = lib.types.any;
options.message = lib.options.create { type = lib.types.string; };
};
};
};
config = {
aux = args: {
options.message2 = lib.options.create { type = lib.types.string; };
config.message = expected;
};
};
}
{
config = {
aux.exists = true;
aux.message2 = expected;
};
}
];
};
in
(evaluated.config.aux.message == evaluated.config.aux.message2) && evaluated.config.aux.exists;
"nested submodules" =
let
expected = "Hello, World!";
evaluated = lib.modules.run {
modules = [
{
options = {
aux = lib.options.create {
type = lib.types.submodule {
freeform = lib.types.any;
options.message = lib.options.create { type = lib.types.string; };
};
};
};
config = {
aux = args: {
options.message2 = lib.options.create { type = lib.types.string; };
config.message = expected;
};
};
}
{
config = {
aux.exists = true;
aux.message2 = expected;
};
}
];
};
in
(evaluated.config.aux.message == evaluated.config.aux.message2) && evaluated.config.aux.exists;
};
}

View file

@ -1,72 +0,0 @@
lib: {
numbers = {
into = {
## Convert a number into a string.
##
## @type Int | Float -> String
string = value: if builtins.isInt value then builtins.toString value else builtins.toJSON value;
## Convert a number into a list of digits in the given base.
##
## @type Int -> Int -> List Int
base =
base: target:
let
process =
value:
let
r = value - ((value / base) * base);
q = (value - r) / base;
in
if value < base then [ value ] else [ r ] ++ process q;
result = process target;
in
assert lib.errors.trace (builtins.isInt base) "Base must be an integer.";
assert lib.errors.trace (builtins.isInt target) "Target must be an integer.";
assert lib.errors.trace (base >= 2) "Base must be at least 2.";
assert lib.errors.trace (target >= 0) "Target cannot be negative.";
lib.lists.reverse result;
## Convert a number into a hexadecimal string.
##
## @type Int -> String
hex =
value:
let
serialize =
part:
if part < 10 then
builtins.toString part
else if part == 10 then
"A"
else if part == 11 then
"B"
else if part == 12 then
"C"
else if part == 13 then
"D"
else if part == 14 then
"E"
else if part == 15 then
"F"
else
builtins.throw "Invalid hex digit.";
in
lib.strings.concatMap serialize (lib.numbers.into.base 16 value);
};
## Compare two numbers. When the first number is less than the second, -1
## is returned. When the first number is greater than the second, 1 is
## returned. When the numbers are equal, 0 is returned.
##
## @type Int -> Int -> Int
compare =
a: b:
if a < b then
-1
else if a > b then
1
else
0;
};
}

View file

@ -1,67 +0,0 @@
let
lib = import ./../default.nix;
in
{
"into" = {
"string" = {
"converts an int into a string" =
let
expected = "1";
actual = lib.numbers.into.string 1;
in
actual == expected;
"converts a float into a string" =
let
expected = "1.0";
actual = lib.numbers.into.string 1.0;
in
actual == expected;
};
"base" = {
"converts a number into a given base" =
let
expected = [
1
0
0
];
actual = lib.numbers.into.base 2 4;
in
actual == expected;
};
"hex" = {
"converts a number into a hex string" =
let
expected = "64";
actual = lib.numbers.into.hex 100;
in
actual == expected;
};
};
"compare" = {
"returns -1 when first is less than second" =
let
expected = -1;
actual = lib.numbers.compare 1 2;
in
actual == expected;
"returns 0 when first is equal to second" =
let
expected = 0;
actual = lib.numbers.compare 1 1;
in
actual == expected;
"returns 1 when first is greater than second" =
let
expected = 1;
actual = lib.numbers.compare 2 1;
in
actual == expected;
};
}

View file

@ -1,348 +0,0 @@
lib: {
options = {
merge = {
## Merge a list of option definitions into a single value.
##
## @type Location -> List Definition -> Any
default =
location: definitions:
let
values = lib.options.getDefinitionValues definitions;
first = builtins.elemAt values 0;
mergedFunctions = x: lib.options.mergeDefault location (builtins.map (f: f x) values);
mergedLists = builtins.concatLists values;
mergedAttrs = builtins.foldl' lib.attrs.merge { } values;
mergedBools = builtins.any lib.bools.or false values;
mergedStrings = lib.strings.concat values;
in
if builtins.length values == 1 then
builtins.elemAt values 0
else if builtins.all builtins.isFunction values then
mergedFunctions
else if builtins.all builtins.isList values then
mergedLists
else if builtins.all builtins.isAttrs values then
mergedAttrs
else if builtins.all builtins.isBool values then
mergedBools
else if builtins.all lib.strings.isString values then
mergedStrings
else if builtins.all builtins.isInt values && builtins.all (x: x == first) values then
first
# TODO: Improve this error message to show the location and definitions for the option.
else
builtins.throw "Cannot merge definitions.";
## Merge multiple option definitions together.
##
## @type Location -> Type -> List Definition
definitions =
location: type: definitions:
let
identifier = lib.options.getIdentifier location;
resolve =
definition:
let
properties = builtins.addErrorContext "while evaluating definitions from `${definition.__file__ or "<unknown>"}`:" (
lib.modules.apply.properties definition.value
);
normalize = value: {
__file__ = definition.__file__ or "<unknown>";
inherit value;
};
in
builtins.map normalize properties;
resolved = builtins.concatMap resolve definitions;
overridden = lib.modules.apply.overrides resolved;
values =
if builtins.any (definition: lib.types.is "order" definition.value) overridden.values then
lib.modules.apply.order overridden.values
else
overridden.values;
isDefined = values != [ ];
invalid = builtins.filter (definition: !(type.check definition.value)) values;
merged =
if isDefined then
if builtins.all (definition: type.check definition.value) values then
type.merge location values
else
builtins.throw "A definition for `${identifier}` is not of type `${type.description}`. Definition values:${lib.options.getDefinitions invalid}"
else
builtins.throw "The option `${identifier}` is used but not defined.";
optional = if isDefined then { value = merged; } else { };
in
{
inherit
isDefined
values
merged
optional
;
raw = {
inherit values;
inherit (overridden) highestPriority;
};
};
## Merge multiple option declarations together.
##
## @type Location -> List Option
declarations =
location: options:
let
merge =
result: option:
let
mergedType = result.type.mergeType option.options.type.functor;
isTypeMergeable = mergedType != null;
shared = name: option.options ? ${name} && result ? ${name};
typeSet = lib.attrs.when ((shared "type") && isTypeMergeable) { type = mergedType; };
files = result.declarations;
serializedFiles = builtins.concatStringsSep " and " files;
typeSubmodules = option.options.type.submodules or null;
submodules =
if typeSubmodules != null then
builtins.map (module: {
__file__ = option.__file__;
includes = [ module ];
}) typeSubmodules
++ result.options
else
result.options;
in
if
shared "default"
|| shared "example"
|| shared "description"
|| shared "apply"
|| (shared "type" && !isTypeMergeable)
then
builtins.throw "The option `${lib.options.getIdentifier location}` in `${option.__file__}` is already declared in ${serializedFiles}"
else
option.options
// result
// {
declarations = result.declarations ++ [ option.__file__ ];
options = submodules;
}
// typeSet;
in
builtins.foldl' merge {
inherit location;
declarations = [ ];
options = [ ];
} options;
## Merge an option, only supporting a single unique definition.
##
## @type String -> Location -> List Definition -> Any
unique =
message: location: definitions:
let
identifier = lib.options.getIdentifier location;
total = builtins.length definitions;
first = builtins.elemAt definitions 0;
in
if total == 1 then
first.value
else if total == 0 then
builtins.throw "Cannot merge unused option `${identifier}`.\n${message}"
else
builtins.throw "The option `${identifier}` is defined multiple times, but must be unique.\n${message}\nDefinitions:${lib.options.getDefinitions definitions}";
## Merge a single instance of an option.
##
## @type Location -> List Definition -> Any
one = lib.options.merge.unique "";
equal =
location: definitions:
let
identifier = lib.options.getIdentifier location;
first = builtins.elemAt definitions 0;
rest = builtins.tail definitions;
merge =
x: y:
if x != y then
builtins.throw "The option `${identifier}` has conflicting definitions:${lib.options.getDefinitions definitions}"
else
x;
merged = builtins.foldl' merge first rest;
in
if builtins.length definitions == 0 then
builtins.throw "Cannot merge unused option `${identifier}`."
else if builtins.length definitions == 1 then
first.value
else
merged.value;
};
## Check whether a value is an option.
##
## @type Attrs -> Bool
is = lib.types.is "option";
## Create an option.
##
## @type { type? :: String | Null, apply? :: (a -> b) | Null, default? :: { value :: a, text :: String }, example? :: String | Null, visible? :: Bool | Null, internal? :: Bool | Null, writable? :: Bool | Null, description? :: String | Null } -> Option a
create =
settings@{
type ? lib.types.unspecified,
apply ? null,
default ? { },
example ? null,
visible ? null,
internal ? null,
writable ? null,
description ? null,
}:
{
__type__ = "option";
inherit
type
apply
default
example
visible
internal
writable
description
;
};
## Create a sink option.
##
## @type @alias lib.options.create
sink =
settings:
let
defaults = {
internal = true;
visible = false;
default = false;
description = "A sink option for unused definitions";
type = lib.types.create {
name = "sink";
check = lib.fp.const true;
merge = lib.fp.const (lib.fp.const false);
};
apply = value: builtins.throw "Cannot read the value of a Sink option.";
};
in
lib.options.create (defaults // settings);
## Get the definition values from a list of options definitions.
##
## @type List Definition -> Any
getDefinitionValues = definitions: builtins.map (definition: definition.value) definitions;
## Convert a list of option identifiers into a single identifier.
##
## @type List String -> String
getIdentifier =
location:
let
special = [
# lib.types.attrs.of (lib.types.submodule {})
"<name>"
# lib.types.list.of (submodule {})
"*"
# lib.types.function
"<function body>"
];
escape = part: if builtins.elem part special then part else lib.strings.escape.nix.identifier part;
in
lib.strings.concatMapSep "." escape location;
## Get a string message of the definitions for an option.
##
## @type List Definition -> String
getDefinitions =
definitions:
let
serialize =
definition:
let
valueWithRecursionLimit = lib.generators.withRecursion {
limit = 10;
throw = false;
} definition.value;
eval = builtins.tryEval (lib.generators.pretty { } valueWithRecursionLimit);
lines = lib.strings.split "\n" eval.value;
linesLength = builtins.length lines;
firstFiveLines = lib.lists.take 5 lines;
ellipsis = lib.lists.when (linesLength > 5) "...";
value = builtins.concatStringsSep "\n " (firstFiveLines ++ ellipsis);
result =
if !eval.success then
""
else if linesLength > 1 then
":\n " + value
else
": " + value;
in
"\n- In `${definition.__file__}`${result}";
in
lib.strings.concatMap serialize definitions;
## Run a set of definitions, calculating the resolved value and associated information.
##
## @type Location -> Option -> List Definition -> String & { value :: Any, highestPriority :: Int, isDefined :: Bool, files :: List String, definitions :: List Any, definitionsWithLocations :: List Definition }
run =
location: option: definitions:
let
identifier = lib.options.getIdentifier location;
definitionsWithDefault =
if option ? default && option.default ? value then
[
{
__file__ = builtins.head option.declarations;
value = lib.modules.overrides.option option.default.value;
}
]
++ definitions
else
definitions;
merged =
if option.writable or null == false && builtins.length definitionsWithDefault > 1 then
let
separatedDefinitions = builtins.map (
definition:
definition
// {
value = (lib.options.merge.definitions location option.type [ definition ]).merged;
}
) definitionsWithDefault;
in
builtins.throw "The option `${identifier}` is not writable, but is set more than once:${lib.options.getDefinitions separatedDefinitions}"
else
lib.options.merge.definitions location option.type definitionsWithDefault;
value = if option.apply or null != null then option.apply merged.merged else merged.merged;
in
option
// {
value = builtins.addErrorContext "while evaluating the option `${identifier}`:" value;
highestPriority = merged.raw.highestPriority;
isDefined = merged.isDefined;
files = builtins.map (definition: definition.__file__) merged.values;
definitions = lib.options.getDefinitionValues merged.values;
definitionsWithLocations = merged.values;
__toString = identifier;
};
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

View file

@ -1,52 +0,0 @@
lib: {
packages = {
## Check whether a value is a derivation. Note that this will also return true
## for "fake" derivations which are constructed by helpers such as
## `lib.paths.into.drv` for convenience.
##
## @type a -> Bool
isDerivation = value: value.type or null == "derivation";
## Sanitize a string to produce a valid name for a derivation.
##
## @type String -> String
sanitizeDerivationName =
let
validate = builtins.match "[[:alnum:]+_?=-][[:alnum:]+._?=-]*";
in
value:
# First detect the common case of already valid strings, to speed those up
if builtins.stringLength value <= 207 && validate value != null then
builtins.unsafeDiscardStringContext value
else
lib.fp.pipe [
# Get rid of string context. This is safe under the assumption that the
# resulting string is only used as a derivation name
builtins.unsafeDiscardStringContext
# Strip all leading "."
(x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0)
(lib.strings.split "[^[:alnum:]+._?=-]+")
# Replace invalid character ranges with a "-"
(lib.strings.concatMap (x: if builtins.isList x then "-" else x))
# Limit to 211 characters (minus 4 chars for ".drv")
(x: builtins.substring (lib.math.max (builtins.stringLength x - 207) 0) (-1) x)
# If the result is empty, replace it with "unknown"
(x: if builtins.stringLength x == 0 then "unknown" else x)
] value;
## Get an output of a derivation.
##
## @type String -> Derivation -> String
getOutput =
output: package:
if !package ? outputSpecified || !package.outputSpecified then
package.${output} or package.out or package
else
package;
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

View file

@ -1,67 +0,0 @@
lib: {
paths = {
into = {
## Convert a path into a derivation.
##
## @type Path -> Derivation
drv =
value:
let
path = builtins.storePath value;
result = {
type = "derivation";
name = lib.packages.sanitizeDerivationName (builtins.substring 33 (-1) (builtins.baseNameOf path));
outPath = path;
outputs = [ "out" ];
outputName = "out";
out = result;
};
in
result;
};
validate = {
## Check whether a path is contained within the Nix store.
##
## @type Path -> Bool
store =
value:
if lib.strings.validate.stringifiable value then
builtins.substring 0 1 (builtins.toString value) == "/"
&& builtins.dirOf (builtins.toString value) == builtins.storeDir
else
false;
};
## Create a search path from a list of paths.
##
## @type String -> [String] -> String
search =
target: paths:
lib.strings.concatMapSep ":" (path: path + "/" + target) (
builtins.filter (value: value != null) paths
);
## Create a search path from a list of packages.
##
## @type String -> [Derivation] -> String
searchFromOutput =
output: target: packages:
lib.paths.search target (builtins.map (lib.packages.getOutput output) packages);
## Create a search path for the binary output of a package.
##
## @type [Derivation] -> String
bin = lib.paths.searchFromOutput "bin" "bin";
## Create a search path for the library output of a package.
##
## @type [Derivation] -> String
lib = lib.paths.searchFromOutput "lib" "lib";
## Create a search path for the include output of a package.
##
## @type [Derivation] -> String
include = lib.paths.searchFromOutput "dev" "include";
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

View file

@ -1,54 +0,0 @@
lib: {
points = {
## Calculate the fixed point of a function. This will evaluate the function `f`
## until its result settles (or Nix's recursion limit is reached). This allows
## us to define recursive functions without worrying about the order of their
## definitions.
##
## @type (a -> a) -> a
fix =
f:
let
x = f x;
in
x;
## Calculate the fixed point of a function. This will evaluate the function `f`
## until its result settles (or Nix's recursion limit is reached). This allows
## us to define recursive functions without worrying about the order of their
## definitions. Unlike `fix`, the resulting value is also given a `__unfix__`
## attribute that is set to the original function passed to `fix'`.
##
## @type (a -> a) -> a & { __unfix__ :: (a -> a) }
fix' =
f:
let
x = f x // {
__unfix__ = f;
};
in
x;
## Extend a function's output with an additional function. This is the basis for
## features like overlays.
##
## @type (a -> b -> c) -> (a -> b) -> a -> b & c
extends =
g: f: self:
let
previous = f self;
next = g self previous;
in
previous // next;
## Add an `extend` method to the result of a function.
##
## @type (a -> b) -> b & { extend :: (a -> b -> c) -> b & c }
withExtend =
f:
let
create = self: (f self) // { extend = g: lib.points.withExtend (lib.points.extends g f); };
in
lib.points.fix' create;
};
}

View file

@ -1,52 +0,0 @@
let
lib = import ./../default.nix;
in
{
"fix" = {
"calculates a fixed point" =
let
expected = {
original = 0;
calculated = 1;
};
actual = lib.points.fix (self: {
original = 0;
calculated = self.original + 1;
});
in
actual == expected;
};
"fix'" = {
"allows unfixing a fixed point" =
let
expected = {
original = 0;
calculated = 6;
};
result = lib.points.fix' (self: {
original = 0;
calculated = self.original + 1;
});
actual = result.__unfix__ { original = 5; };
in
actual == expected;
};
"extends" = {
"overlays two functions' return values" =
let
first = self: previous: { z = 3; };
second = self: { y = 2; };
expected = {
y = 2;
z = 3;
};
actual = lib.points.extends first second { x = 1; };
in
actual == expected;
};
}

View file

@ -1,59 +0,0 @@
{
upper = [
"A"
"B"
"C"
"D"
"E"
"F"
"G"
"H"
"I"
"J"
"K"
"L"
"M"
"N"
"O"
"P"
"Q"
"R"
"S"
"T"
"U"
"V"
"W"
"X"
"Y"
"Z"
];
lower = [
"a"
"b"
"c"
"d"
"e"
"f"
"g"
"h"
"i"
"j"
"k"
"l"
"m"
"n"
"o"
"p"
"q"
"r"
"s"
"t"
"u"
"v"
"w"
"x"
"y"
"z"
];
}

View file

@ -1,97 +0,0 @@
{
" " = 32;
"!" = 33;
"\"" = 34;
"#" = 35;
"$" = 36;
"%" = 37;
"&" = 38;
"'" = 39;
"(" = 40;
")" = 41;
"*" = 42;
"+" = 43;
"," = 44;
"-" = 45;
"." = 46;
"/" = 47;
"0" = 48;
"1" = 49;
"2" = 50;
"3" = 51;
"4" = 52;
"5" = 53;
"6" = 54;
"7" = 55;
"8" = 56;
"9" = 57;
":" = 58;
";" = 59;
"<" = 60;
"=" = 61;
">" = 62;
"?" = 63;
"@" = 64;
"A" = 65;
"B" = 66;
"C" = 67;
"D" = 68;
"E" = 69;
"F" = 70;
"G" = 71;
"H" = 72;
"I" = 73;
"J" = 74;
"K" = 75;
"L" = 76;
"M" = 77;
"N" = 78;
"O" = 79;
"P" = 80;
"Q" = 81;
"R" = 82;
"S" = 83;
"T" = 84;
"U" = 85;
"V" = 86;
"W" = 87;
"X" = 88;
"Y" = 89;
"Z" = 90;
"[" = 91;
"\\" = 92;
"]" = 93;
"^" = 94;
"_" = 95;
"`" = 96;
"a" = 97;
"b" = 98;
"c" = 99;
"d" = 100;
"e" = 101;
"f" = 102;
"g" = 103;
"h" = 104;
"i" = 105;
"j" = 106;
"k" = 107;
"l" = 108;
"m" = 109;
"n" = 110;
"o" = 111;
"p" = 112;
"q" = 113;
"r" = 114;
"s" = 115;
"t" = 116;
"u" = 117;
"v" = 118;
"w" = 119;
"x" = 120;
"y" = 121;
"z" = 122;
"{" = 123;
"|" = 124;
"}" = 125;
"~" = 126;
}

View file

@ -1,342 +0,0 @@
lib: {
strings = {
into = {
## Convert a character to an integer.
##
## @type String -> Integer
int = char: builtins.getAttr char lib.strings.ascii;
## Convert a string into a list of characters.
##
## @type String -> List String
chars =
value:
let
range = lib.lists.range 0 (builtins.stringLength value - 1);
pick = index: builtins.substring index 1 value;
in
builtins.map pick range;
shell = {
## Convert a value into a shell variable.
##
## @type String -> Any -> String
var =
name: target:
let
baseVar = "${name}=${lib.strings.escape.shell.arg target}";
listVar = "declare -a ${name}=(${lib.strings.escape.shell.args target})";
attrsVar = "declare -A ${name}=(${
builtins.concatStringsSep " " (
lib.attrs.mapToList (
k: v: "[${lib.strings.escape.shell.arg k}]=${lib.strings.escape.shell.arg v}"
) target
)
})";
in
assert lib.errors.trace (lib.strings.validate.posix name) "Invalid shell variable name: ${name}";
if builtins.isAttrs target && !lib.strings.validate.stringifiable target then
attrsVar
else if builtins.isList target then
listVar
else
baseVar;
## Create shell variables for a map of values.
##
## @type Attrs -> String
vars =
target: builtins.concatStringsSep "\n" (lib.attrs.mapToList lib.strings.into.shell.var target);
};
};
escape = {
## Escape parts of a string.
##
## @type List String -> String -> String
any =
patterns: source:
let
escaped = builtins.map (x: "\\${x}") patterns;
replacer = builtins.replaceStrings patterns escaped;
in
replacer source;
## Escape a given set of characters in a string using their
## ASCII code prefixed with "\x".
##
## @type List String -> String
c =
list:
let
serialize =
char:
let
hex = lib.numbers.into.hex (lib.strings.into.int char);
in
"\\x${lib.strings.lower hex}";
in
builtins.replaceStrings list (builtins.map serialize list);
nix = {
## Escape a string of Nix code.
##
## @type String -> String
value = value: lib.strings.escape.any [ "$" ] (builtins.toJSON value);
## Escape a string for use as a Nix identifier.
##
## @type String -> String
identifier =
value:
if builtins.match "[a-zA-Z_][a-zA-Z0-9_'-]*" value != null then
value
else
lib.strings.escape.nix.value value;
};
## Escape a string for use in a regular expression.
##
## @type String -> String
regex = lib.strings.escape.any (lib.strings.into.chars "\\[{()^$?*+|.");
## Escape a string for use in XML.
##
## @type String -> String
xml =
builtins.replaceStrings
[
"\""
"'"
"<"
">"
"&"
]
[
"&quot;"
"&apos;"
"&lt;"
"&gt;"
"&amp;"
];
shell = {
## Escape a string for use as a shell argument.
##
## @type String -> String
arg = value: "'${builtins.replaceStrings [ "'" ] [ "'\\''" ] (builtins.toString value)}'";
## Escape multiple strings for use as shell arguments.
##
## @type List String -> String
args = lib.strings.concatMapSep " " lib.strings.escape.shell.arg;
};
};
validate = {
## Check if a string is a valid POSIX identifier.
##
## @type String -> Bool
posix = name: builtins.match "[a-zA-Z_][a-zA-Z0-9_]*" name != null;
## Check if a value can be used as a string.
##
## @type Any -> Bool
stringifiable =
value: builtins.isString value || builtins.isPath value || value ? outPath || value ? __toString;
## Check whether a string is empty. This includes strings that
## only contain whitespace.
##
## @type String -> Bool
empty = value: builtins.match "[ \t\n]*" value != null;
};
order = {
## Create an ordered string that will be placed anywhere.
##
## @type String -> OrderedString
anywhere = value: {
inherit value;
deps = [ ];
};
## Create an ordered string that will be placed before its dependencies.
##
## @type String -> List String -> OrderedString
after = deps: value: { inherit deps value; };
## Apply the order for a list of ordered strings. This function also supports
## plain strings in the list so long as they have an accompanying static definition
## provided.
##
## @type Attrs -> List (OrderedString | String)
apply =
static: items:
let
process =
complete: remaining:
let
next = builtins.head remaining;
before = process complete next.deps;
after = process before.complete (builtins.tail remaining);
in
if builtins.length remaining == 0 then
{
inherit complete;
result = [ ];
}
else if builtins.isAttrs next then
{
inherit complete;
result = before.result ++ [ next.value ] ++ after.result;
}
else
process (complete // { "${next}" = true; }) ([ static.${next} ] ++ builtins.tail remaining);
processed = process { } items;
in
processed.result;
};
## Return a given string if a condition is true, otherwise return
## an empty string.
##
## @type Bool -> String -> String
when = condition: value: if condition then value else "";
## A table of ASCII characters mapped to their integer character code.
##
## @type Attrs
ascii = import ./ascii.nix;
## Lists of both upper and lower case ASCII characters.
##
## @type { upper :: List String, lower :: List String }
alphabet = import ./alphabet.nix;
## Concatenate a list of strings together.
##
## @type List String -> String
concat = builtins.concatStringsSep "";
## Concatenate and map a list of strings together.
##
## @type List String -> String
concatMap = lib.strings.concatMapSep "";
## Concatenate and map a list of strings together with a separator.
##
## @type String -> (a -> String) -> List a -> String
concatMapSep =
separator: f: list:
builtins.concatStringsSep separator (builtins.map f list);
## Change a string to uppercase.
##
## @type String -> String
upper = builtins.replaceStrings lib.strings.alphabet.lower lib.strings.alphabet.upper;
## Change a string to lowercase.
##
## @type String -> String
lower = builtins.replaceStrings lib.strings.alphabet.upper lib.strings.alphabet.lower;
## Add the context of one string to another.
##
## @type String -> String -> String
withContext = context: value: builtins.substring 0 0 context + value;
## Split a string by a separator.
##
## @type String -> String -> List String
split =
separator: value:
let
escaped = lib.strings.escape.regex (builtins.toString separator);
raw = builtins.split escaped (builtins.toString value);
parts = builtins.filter builtins.isString raw;
in
builtins.map (lib.strings.withContext value) parts;
## Check if a string starts with a given prefix.
##
## @type String -> String -> Bool
hasPrefix =
prefix: value:
let
text = builtins.substring 0 (builtins.stringLength prefix) value;
in
text == prefix;
## Check if a string ends with a given suffix.
##
## @type String -> String -> Bool
hasSuffix =
suffix: value:
let
valueLength = builtins.stringLength value;
suffixLength = builtins.stringLength suffix;
text = builtins.substring (valueLength - suffixLength) valueLength value;
in
(valueLength >= suffixLength) && text == suffix;
## Check if a string contains a given infix.
##
## @type String -> String -> Bool
hasInfix = infix: value: builtins.match ".*${lib.strings.escape.regex infix}.*" "${value}" != null;
## Remove a prefix from a string if it exists.
##
## @type String -> String -> String
removePrefix =
prefix: value:
let
prefixLength = builtins.stringLength prefix;
valueLength = builtins.stringLength value;
in
if lib.strings.hasPrefix prefix value then
builtins.substring prefixLength (valueLength - prefixLength) value
else
value;
## Remove a suffix from a string if it exists.
##
## @type String -> String -> String
removeSuffix =
suffix: value:
let
suffixLength = builtins.stringLength suffix;
valueLength = builtins.stringLength value;
in
if lib.strings.hasSuffix suffix value then
builtins.substring 0 (valueLength - suffixLength) value
else
value;
## Pad the start of a string with a character until it reaches
## a given length.
##
## @type Integer -> String -> String
padStart =
length: char: value:
let
valueLength = builtins.stringLength value;
padding = builtins.genList (_: char) (length - valueLength);
in
if valueLength < length then (builtins.concatStringsSep "" padding) + value else value;
## Pad the end of a string with a character until it reaches
## a given length.
##
## @type Integer -> String -> String
padEnd =
length: char: value:
let
valueLength = builtins.stringLength value;
padding = builtins.genList (_: char) (length - valueLength);
in
if valueLength < length then value + (builtins.concatStringsSep "" padding) else value;
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

File diff suppressed because it is too large Load diff

View file

@ -1,55 +0,0 @@
let
lib = import ./../default.nix;
in
{
"PortableSubmodule" = {
"is portable" =
let
submodule =
{ config }:
{
options = {
primitive = lib.options.create {
type = lib.types.string;
default.value = "<default>";
};
computed = lib.options.create {
type = lib.types.string;
default.value = "computed: ${config.primitive}";
};
};
};
option = lib.options.create {
default.value = { };
type = lib.types.submodules.portable {
module = submodule;
};
};
moduleA = {
options.a = option;
};
moduleB =
{ config }:
{
options.b = option;
config.b = config.a;
};
moduleC = {
config.b.primitive = "custom";
};
result = lib.modules.run {
modules = [
moduleA
moduleB
moduleC
];
};
in
result.config.b.computed == "computed: custom";
};
}

View file

@ -1,58 +0,0 @@
lib: {
versions = {
## Check if a version is greater than another.
##
## @type String -> String -> Bool
gt = first: second: builtins.compareVersions first second == -1;
## Check if a version is greater than or equal to another.
##
## @type String -> String -> Bool
gte = first: second: builtins.compareVersions first second != 1;
## Check if a version is less than another.
##
## @type String -> String -> Bool
lt = first: second: builtins.compareVersions first second == 1;
## Check if a version is less than or equal to another.
##
## @type String -> String -> Bool
lte = first: second: builtins.compareVersions first second != -1;
## Check if a version is equal to another.
##
## @type String -> String -> Bool
eq = first: second: builtins.compareVersions first second == 0;
## Get the major version from a version string.
##
## @type String -> String
major =
version:
let
parts = builtins.splitVersion version;
in
builtins.elemAt parts 0;
## Get the minor version from a version string.
##
## @type String -> String
minor =
version:
let
parts = builtins.splitVersion version;
in
builtins.elemAt parts 1;
## Get the patch version from a version string.
##
## @type String -> String
patch =
version:
let
parts = builtins.splitVersion version;
in
builtins.elemAt parts 2;
};
}

View file

@ -1,4 +0,0 @@
let
lib = import ./../default.nix;
in
{ }

View file

@ -1,18 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p jq
set -euo pipefail
namespace=${1:-}
if [ -z "$namespace" ]; then
nix eval -f ./default.test.nix --show-trace --raw
else
if [ -d "./src/$namespace" ]; then
nix eval -f "./src/$namespace/default.test.nix" --show-trace --json | jq
else
echo "Namespace $namespace not found"
exit 1
fi
fi

View file

@ -1,8 +1,10 @@
{
lib ? import ./../lib,
foundation ? import ./../foundation { system = "i686-linux"; },
}:
let
pins = import ./npins;
lib = import pins.lib;
modules = import ./src/modules.nix;
result = lib.modules.run {

34
tidepool/flake.lock generated
View file

@ -1,18 +1,12 @@
{
"nodes": {
"foundation": {
"inputs": {
"lib": [
"lib"
]
},
"locked": {
"dir": "foundation",
"lastModified": 1724190751,
"narHash": "sha256-e8sOmeXS9YWuQqjW6gvtS3PIueazkf4S1iiJ/94eXv4=",
"ref": "master",
"rev": "4b36a5a0133f5481840bdfaf693c971215a7ef1f",
"revCount": 84,
"dirtyRev": "c30f94972c84c5c758b705e4363f624e75560916-dirty",
"dirtyShortRev": "c30f949-dirty",
"lastModified": 1742785479,
"narHash": "sha256-jYjvytd4Ut1z3izUuGCfq2KKjHLgWYvhMtPtyRh4FR8=",
"type": "git",
"url": "file:../"
},
@ -22,27 +16,9 @@
"url": "file:../"
}
},
"lib": {
"locked": {
"dir": "lib",
"lastModified": 1724190751,
"narHash": "sha256-e8sOmeXS9YWuQqjW6gvtS3PIueazkf4S1iiJ/94eXv4=",
"ref": "master",
"rev": "4b36a5a0133f5481840bdfaf693c971215a7ef1f",
"revCount": 84,
"type": "git",
"url": "file:../"
},
"original": {
"dir": "lib",
"type": "git",
"url": "file:../"
}
},
"root": {
"inputs": {
"foundation": "foundation",
"lib": "lib"
"foundation": "foundation"
}
}
},

View file

@ -1,11 +1,7 @@
{
inputs = {
lib = {
url = "git+file:../?dir=lib";
};
foundation = {
url = "git+file:../?dir=foundation";
inputs.lib.follows = "lib";
};
};
@ -13,7 +9,6 @@
inputs:
let
exports = import ./default.nix {
lib = inputs.lib.lib;
foundation = inputs.foundation.packages.i686-linux;
};
in

View file

@ -0,0 +1,80 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource =
{
repository,
revision,
url ? null,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else
assert repository.type == "Git";
let
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName repository.url revision;
in
builtins.fetchGit {
url = repository.url;
rev = revision;
inherit name;
# hash = hash;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

View file

@ -0,0 +1,16 @@
{
"pins": {
"lib": {
"type": "Git",
"repository": {
"type": "Git",
"url": "git+ssh://forgejo@git.auxolotl.org/auxolotl/lib.git"
},
"branch": "main",
"revision": "7552ab48bb394d59d2bf1f7a558d28ce59da524d",
"url": null,
"hash": "0705fm00k9f95b6idf5qnfvqm4qf1a0cv966ghgd48kd1qy4il5c"
}
},
"version": 3
}