chore: initial commit
This commit is contained in:
commit
0409563e32
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License 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.
|
29
README.md
Normal file
29
README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Aux Labs
|
||||
|
||||
Welcome to Aux Labs! Complimentary beakers and companion
|
||||
cubes are available in the gift shop.
|
||||
|
||||
The Aux Laboratory is a place for experimentation. Here you will find novel
|
||||
solutions for problems in the Aux world. These experiments are not intended
|
||||
to be used by anyone yet due to their highly unstable nature. However, we
|
||||
have decided to publish them here together so that members of the community
|
||||
may collaborate.
|
||||
|
||||
> **Note**: This repository is a part of Aux's early ad-hoc structure. In the
|
||||
> future we will be moving to a standardized Aux Enhancement Proposal (AEP)
|
||||
> format.
|
||||
|
||||
## Experiment Phases
|
||||
|
||||
| Phase | Description |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Idea | An idea exists to solve a problem we currently have with Aux. Send a pull request to this repository creating a new directory for your experiment. The directory should contain a `README.md` file explaining the purpose of the experiment. |
|
||||
| Iteration | Work on the experiment is done to solve for unknowns and come up with a good solution for the original problem. It may also be helpful to collaborate with others for feedback. |
|
||||
| Proposal | The experiment has been satisfactorily completed and is ready to be considered for official adoption by the project. Discussion with the relevant Special Interest Group should take place to handle the transition of the project out of the lab and into its own repository. |
|
||||
| Adoption | The experiment has been adopted and is now a part of Aux! The experiment should be moved to its own repository and the original experiment directory should be deleted |
|
||||
|
||||
## Experiments
|
||||
|
||||
| Name | Phase | Description |
|
||||
| ---------------- | ----- | -------------------------------------------------------- |
|
||||
| [Aux Lib](./lib) | Idea | A library of common functions used in the Aux ecosystem. |
|
23
lib/LICENSE
Normal file
23
lib/LICENSE
Normal file
|
@ -0,0 +1,23 @@
|
|||
MIT License
|
||||
Copyright (c) 2003-2023 Eelco Dolstra and the Nixpkgs/NixOS 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.
|
104
lib/README.md
Normal file
104
lib/README.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
# 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 = "github:auxolotl/labs?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://github.com/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`.
|
||||
|
||||
### Formatting
|
||||
|
||||
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`.
|
||||
|
||||
### Adding Functionality
|
||||
|
||||
Before adding new features to the library, submit an issue or talk the idea over with one or
|
||||
more of the project maintainers. We want to make sure that the library does not become bloated
|
||||
with tools that aren't used. Some features may be better handled in a separate project. If you
|
||||
do get the go-ahead to begin working on your feature, please place it in the library structure
|
||||
similarly to how existing features are. For example, things dealing with strings should go in
|
||||
`src/strings/default.nix`.
|
||||
|
||||
Additionally, you should prefer to group things in attribute sets for like-functionality. More
|
||||
broad categories such as `strings` and `lists` are helpful, but scoped groups for things like
|
||||
`into`, `from`, and `validate` also make the library more discoverable. Having all of the
|
||||
different parts of the library mirroring this organizational structure makes building intuition
|
||||
for working with the library much easier. To know when to group new things, consider the
|
||||
following:
|
||||
|
||||
- Would your function name be multiple words like `fromString`?
|
||||
- Are there multiple variants of this function?
|
||||
- Would it be easier to find in a group?
|
||||
- Would grouping help avoid name collisions or confusion?
|
1
lib/default.nix
Normal file
1
lib/default.nix
Normal file
|
@ -0,0 +1 @@
|
|||
import ./src
|
7
lib/flake.nix
Normal file
7
lib/flake.nix
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
description = "A very basic flake";
|
||||
|
||||
outputs = _: {
|
||||
lib = import ./src;
|
||||
};
|
||||
}
|
141
lib/src/attrs/default.nix
Normal file
141
lib/src/attrs/default.nix
Normal file
|
@ -0,0 +1,141 @@
|
|||
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 (
|
||||
key: values: let
|
||||
currentPath = path ++ [key];
|
||||
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
|
||||
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
|
||||
key = builtins.head path;
|
||||
rest = builtins.tail path;
|
||||
in
|
||||
if path == []
|
||||
then target
|
||||
else if target ? ${key}
|
||||
then lib.attrs.select rest fallback target.${key}
|
||||
else 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 target
|
||||
else error;
|
||||
|
||||
# TODO: Document this.
|
||||
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
|
||||
key = builtins.head path;
|
||||
rest = builtins.tail path;
|
||||
in
|
||||
if path == []
|
||||
then true
|
||||
else if target ? ${key}
|
||||
then lib.attrs.has rest target.${key}
|
||||
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 keys and values to a list.
|
||||
##
|
||||
## @type Any a => (String -> Any -> a) -> Attrs -> List a
|
||||
mapToList = f: target:
|
||||
builtins.map (key: f key target.${key}) (builtins.attrNames target);
|
||||
|
||||
# TODO: Document this.
|
||||
mapRecursive = f: target:
|
||||
lib.attrs.mapRecursiveWhen (lib.fp.const true) f target;
|
||||
|
||||
# TODO: Document this.
|
||||
mapRecursiveWhen = predicate: f: target: let
|
||||
process = path:
|
||||
builtins.mapAttrs (
|
||||
key: value:
|
||||
if builtins.isAttrs value && predicate value
|
||||
then process (path ++ [key]) value
|
||||
else f (path ++ [key]) value
|
||||
);
|
||||
in
|
||||
process [] target;
|
||||
|
||||
# TODO: Document this.
|
||||
filter = predicate: target: let
|
||||
keys = builtins.attrNames target;
|
||||
process = key: let
|
||||
value = target.${key};
|
||||
in
|
||||
if predicate key value
|
||||
then [
|
||||
{
|
||||
name = key;
|
||||
value = value;
|
||||
}
|
||||
]
|
||||
else [];
|
||||
valid = builtins.concatMap process keys;
|
||||
in
|
||||
builtins.listToAttrs valid;
|
||||
};
|
||||
}
|
17
lib/src/attrs/default.test.nix
Normal file
17
lib/src/attrs/default.test.nix
Normal file
|
@ -0,0 +1,17 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {
|
||||
"select" = {
|
||||
"selects a nested value" = let
|
||||
expected = "value";
|
||||
actual =
|
||||
lib.attrs.select
|
||||
["x" "y" "z"]
|
||||
null
|
||||
{
|
||||
x.y.z = expected;
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
}
|
48
lib/src/bools/default.nix
Normal file
48
lib/src/bools/default.nix
Normal file
|
@ -0,0 +1,48 @@
|
|||
lib: {
|
||||
bools = {
|
||||
into = {
|
||||
string = value:
|
||||
if value
|
||||
then "true"
|
||||
else "false";
|
||||
};
|
||||
|
||||
## 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;
|
||||
};
|
||||
}
|
76
lib/src/default.nix
Normal file
76
lib/src/default.nix
Normal file
|
@ -0,0 +1,76 @@
|
|||
let
|
||||
files = [
|
||||
./attrs
|
||||
./bools
|
||||
./errors
|
||||
./fp
|
||||
./generators
|
||||
./importers
|
||||
./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 (
|
||||
key: values: let
|
||||
currentPath = path ++ [key];
|
||||
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
|
8
lib/src/errors/default.nix
Normal file
8
lib/src/errors/default.nix
Normal file
|
@ -0,0 +1,8 @@
|
|||
lib: {
|
||||
errors = {
|
||||
trace = condition: message:
|
||||
if condition
|
||||
then true
|
||||
else builtins.trace message false;
|
||||
};
|
||||
}
|
46
lib/src/fp/default.nix
Normal file
46
lib/src/fp/default.nix
Normal file
|
@ -0,0 +1,46 @@
|
|||
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 x) 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;
|
||||
|
||||
# TODO: Document this.
|
||||
args = f:
|
||||
if f ? __functor
|
||||
then f.__args__ or lib.fp.args (f.__functor f)
|
||||
else builtins.functionArgs f;
|
||||
};
|
||||
}
|
144
lib/src/generators/default.nix
Normal file
144
lib/src/generators/default.nix
Normal file
|
@ -0,0 +1,144 @@
|
|||
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.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;
|
||||
};
|
||||
}
|
13
lib/src/importers/default.nix
Normal file
13
lib/src/importers/default.nix
Normal file
|
@ -0,0 +1,13 @@
|
|||
lib: {
|
||||
importers = {
|
||||
## Import a JSON file as a Nix value.
|
||||
##
|
||||
## @type Path -> a
|
||||
json = file: builtins.fromJSON (builtins.readFile file);
|
||||
|
||||
## Import a TOML file as a Nix value.
|
||||
##
|
||||
## @type Path -> a
|
||||
toml = file: builtins.fromTOML (builtins.readFile file);
|
||||
};
|
||||
}
|
160
lib/src/lists/default.nix
Normal file
160
lib/src/lists/default.nix
Normal file
|
@ -0,0 +1,160 @@
|
|||
lib: {
|
||||
lists = {
|
||||
from = {
|
||||
# TODO: Document this.
|
||||
any = value:
|
||||
if builtins.isList value
|
||||
then value
|
||||
else [value];
|
||||
};
|
||||
|
||||
sort = {
|
||||
## Perform a natural sort on a list of strings.
|
||||
##
|
||||
## @type List -> List
|
||||
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);
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
mapWithIndex = f: list:
|
||||
builtins.genList
|
||||
(i: f i (builtins.elemAt list i))
|
||||
(builtins.length list);
|
||||
|
||||
# TODO: Document this.
|
||||
mapWithIndex1 = f: list:
|
||||
builtins.genList
|
||||
(i: f (i + 1) (builtins.elemAt list i))
|
||||
(builtins.length list);
|
||||
|
||||
## Compare two lists.
|
||||
##
|
||||
## @type (a -> b -> Int) -> 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.assertMsg (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;
|
||||
|
||||
## 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 [];
|
||||
|
||||
# TODO: Document this.
|
||||
count = predicate: list:
|
||||
builtins.foldl' (
|
||||
total: value:
|
||||
if predicate value
|
||||
then total + 1
|
||||
else total
|
||||
)
|
||||
0
|
||||
list;
|
||||
|
||||
# TODO: Document this.
|
||||
unique = list: let
|
||||
filter = result: value:
|
||||
if builtins.elem value result
|
||||
then result
|
||||
else result ++ [value];
|
||||
in
|
||||
builtins.foldl' filter [] list;
|
||||
};
|
||||
}
|
15
lib/src/math/default.nix
Normal file
15
lib/src/math/default.nix
Normal file
|
@ -0,0 +1,15 @@
|
|||
lib: {
|
||||
math = {
|
||||
# TODO: Document this.
|
||||
min = x: y:
|
||||
if x < y
|
||||
then x
|
||||
else y;
|
||||
|
||||
# TODO: Document this.
|
||||
max = x: y:
|
||||
if x > y
|
||||
then x
|
||||
else y;
|
||||
};
|
||||
}
|
600
lib/src/modules/default.nix
Normal file
600
lib/src/modules/default.nix
Normal file
|
@ -0,0 +1,600 @@
|
|||
lib: {
|
||||
modules = {
|
||||
from = {
|
||||
## Create a module from a JSON file.
|
||||
##
|
||||
## @type Path -> Module
|
||||
json = file: {
|
||||
__file__ = file;
|
||||
config = lib.importers.json file;
|
||||
};
|
||||
|
||||
## Create a module from a TOML file.
|
||||
##
|
||||
## @type Path -> Module
|
||||
toml = file: {
|
||||
__file__ = file;
|
||||
config = lib.importers.toml file;
|
||||
};
|
||||
};
|
||||
|
||||
apply = {
|
||||
# TODO: Document this.
|
||||
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];
|
||||
|
||||
# TODO: Document this.
|
||||
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;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
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;
|
||||
|
||||
# TODO: Document this.
|
||||
fixup = location: option:
|
||||
if option.type.getSubModules or null == null
|
||||
then
|
||||
option
|
||||
// {
|
||||
type = option.type or lib.types.unspecified;
|
||||
}
|
||||
else
|
||||
option
|
||||
// {
|
||||
type = option.type.withSubModules option.options;
|
||||
options = [];
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
invert = config:
|
||||
if lib.types.is "merge" config
|
||||
then builtins.concatMap lib.modules.apply.invert config.content
|
||||
else if lib.types.is "when" config
|
||||
then
|
||||
builtins.map
|
||||
(builtins.mapAttrs (key: value: lib.modules.when config.condition value))
|
||||
(lib.modules.apply.invert config.content)
|
||||
else if lib.types.is "override" config
|
||||
then
|
||||
builtins.map
|
||||
(builtins.mapAttrs (key: value: lib.modules.override config.priority value))
|
||||
(lib.modules.apply.invert config.content)
|
||||
else [config];
|
||||
};
|
||||
|
||||
validate = {
|
||||
# TODO: Document this.
|
||||
keys = module: let
|
||||
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
|
||||
in
|
||||
invalid == {};
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
VALID_KEYS = [
|
||||
"__file__"
|
||||
"__key__"
|
||||
"includes"
|
||||
"excludes"
|
||||
"options"
|
||||
"config"
|
||||
"freeform"
|
||||
"meta"
|
||||
];
|
||||
|
||||
# TODO: Document this.
|
||||
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}` has unsupported attribute(s): ${invalidKeys}";
|
||||
|
||||
# TODO: Document this.
|
||||
resolve = key: module: args: let
|
||||
dynamicArgs =
|
||||
builtins.mapAttrs
|
||||
(
|
||||
name: value:
|
||||
builtins.addErrorContext
|
||||
"while evaluating the module argument `${name}` in `${key}`"
|
||||
(args.${name} or args.config.__module__.args.dynamic.${name})
|
||||
)
|
||||
(lib.fp.args module);
|
||||
in
|
||||
if builtins.isFunction module
|
||||
then module (args // dynamicArgs)
|
||||
else module;
|
||||
|
||||
# TODO: Document this.
|
||||
DEFAULT_PRIORITY = 100;
|
||||
|
||||
## Allow for sorting the values provided to a module by priority. The
|
||||
## most important value will be used.
|
||||
##
|
||||
## @type Int -> a -> Priority a
|
||||
order = priority: value: {
|
||||
__type__ = "order";
|
||||
inherit priority value;
|
||||
};
|
||||
|
||||
orders = {
|
||||
# TODO: Document this.
|
||||
before = lib.modules.order 500;
|
||||
# TODO: Document this.
|
||||
default = lib.modules.order 1000;
|
||||
# TODO: Document this.
|
||||
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__);
|
||||
|
||||
# TODO: Document this.
|
||||
when = condition: content: {
|
||||
__type__ = "when";
|
||||
inherit condition content;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
merge = content: {
|
||||
__type__ = "merge";
|
||||
inherit content;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
override = priority: content: {
|
||||
__type__ = "override";
|
||||
inherit priority content;
|
||||
};
|
||||
|
||||
overrides = {
|
||||
# TODO: Document this.
|
||||
option = lib.modules.override 1500;
|
||||
# TODO: Document this.
|
||||
default = lib.modules.override 1000;
|
||||
# TODO: Document this.
|
||||
force = lib.modules.override 50;
|
||||
# TODO: Document this.
|
||||
vm = lib.modules.override 10;
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
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
|
||||
# TODO: Document this.
|
||||
byName = attr: f: modules:
|
||||
builtins.zipAttrsWith
|
||||
(lib.fp.const builtins.concatLists)
|
||||
(builtins.map (
|
||||
module: let
|
||||
subtree = module.${attr};
|
||||
in
|
||||
if builtins.isAttrs subtree
|
||||
then builtins.mapAttrs (key: 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__}` does not support nested options."
|
||||
else process location declarations definitions
|
||||
)
|
||||
declarationsByName;
|
||||
|
||||
matched = builtins.mapAttrs (key: value: value.matched) resultsByName;
|
||||
|
||||
unmatched =
|
||||
builtins.mapAttrs (key: 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;
|
||||
|
||||
# TODO: Document this.
|
||||
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 ++ extensions.modules;
|
||||
args = (settings.args or {}) // extensions.args;
|
||||
prefix = extensions.prefix or settings.prefix or [];
|
||||
};
|
||||
|
||||
# TODO: Document this.
|
||||
collect = let
|
||||
load = args: file: key: module: let
|
||||
moduleFromValue = lib.modules.normalize file key (lib.modules.resolve key module args);
|
||||
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.option;
|
||||
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 "")
|
||||
(modules ++ [internal])
|
||||
({inherit lib options config;} // 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))
|
||||
(key: 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__;
|
||||
};
|
||||
};
|
||||
}
|
28
lib/src/modules/default.test.nix
Normal file
28
lib/src/modules/default.test.nix
Normal file
|
@ -0,0 +1,28 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {
|
||||