chore: define scopes by dirname, not manually
Before this commit, there existed slight confusion between the directory name and the library scope name. For example, `lib.points.fix` is defined in `points/default.nix` under an attribute set named `points`. Theoretically, one could create a new directory, such as `foo`, but name the library scope `bar`. With this commit, the library scope is inferred from dirname automatically. This also has the benefit of slightly reducing nesting and reducing duplication of data. As a downside, it turns `files`, a list of paths, into a list of strings. This is necessary because when paths are parsed in NixLang, they become absolute paths, and so `builtins.toString ./lists` would become "/home/foo/lib/src/lists", which is undesirable. This is a downside, because it makes using filepath LSP (like the one Neovim usually ships with, where it provides autocomplete for paths) impossible, and so typos become a little easier to make. This is necessary for future work on migrating to RFC 145 style doc-comments. This migration would allow us to build API documentation, but first, the nesting needs to be removed, because `nixdoc` (an utility to build Markdown out of doc-comments) doesn't expect it and generates empty entries.
This commit is contained in:
parent
2af4e9f1d5
commit
683b6cb609
22 changed files with 3735 additions and 3735 deletions
|
|
@ -1,216 +1,214 @@
|
|||
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 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 =
|
||||
## 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
|
||||
first = builtins.elemAt values 0;
|
||||
second = builtins.elemAt values 1;
|
||||
currentPath = path ++ [ name ];
|
||||
isSingleValue = builtins.length values == 1;
|
||||
isComplete = predicate currentPath (builtins.elemAt values 1) (builtins.elemAt values 0);
|
||||
in
|
||||
if builtins.length values == 1 then
|
||||
false
|
||||
else if builtins.isAttrs first then
|
||||
builtins.isAttrs second && lib.attrs.match first second
|
||||
if isSingleValue then
|
||||
builtins.elemAt values 0
|
||||
else if isComplete then
|
||||
builtins.elemAt values 1
|
||||
else
|
||||
first == second;
|
||||
process currentPath values
|
||||
);
|
||||
in
|
||||
process
|
||||
[ ]
|
||||
[
|
||||
x
|
||||
y
|
||||
];
|
||||
|
||||
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);
|
||||
## 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)
|
||||
);
|
||||
|
||||
## 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;
|
||||
## 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;
|
||||
|
||||
## 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;
|
||||
## 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;
|
||||
|
||||
## 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 { };
|
||||
## 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;
|
||||
|
||||
## 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);
|
||||
## 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;
|
||||
|
||||
## 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;
|
||||
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);
|
||||
|
||||
## 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;
|
||||
## 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;
|
||||
|
||||
## 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;
|
||||
## 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;
|
||||
|
||||
## 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;
|
||||
};
|
||||
## Depending on a given condition, either use the given value or an empty
|
||||
## attribute set.
|
||||
##
|
||||
## @type Attrs a b => Bool -> a -> a | b
|
||||
when = condition: value: if condition then value else { };
|
||||
|
||||
## Map an attribute set's names and values to a list.
|
||||
##
|
||||
## @type Any a => (String -> Any -> a) -> Attrs -> List a
|
||||
mapToList = f: target: builtins.map (name: f name target.${name}) (builtins.attrNames target);
|
||||
|
||||
## Map an attribute set recursively. Only non-set leaf nodes will be mapped.
|
||||
##
|
||||
## @type (List String -> Any -> Any) -> Attrs -> Attrs
|
||||
mapRecursive = f: target: lib.attrs.mapRecursiveWhen (lib.fp.const true) f target;
|
||||
|
||||
## Map an attribute set recursively when a given predicate returns true.
|
||||
## Only leaf nodes according to the predicate will be mapped.
|
||||
##
|
||||
## @type (Attrs -> Bool) -> (List String -> Any -> Any) -> Attrs -> Attrs
|
||||
mapRecursiveWhen =
|
||||
predicate: f: target:
|
||||
let
|
||||
process =
|
||||
path:
|
||||
builtins.mapAttrs (
|
||||
name: value:
|
||||
if builtins.isAttrs value && predicate value then
|
||||
process (path ++ [ name ]) value
|
||||
else
|
||||
f (path ++ [ name ]) value
|
||||
);
|
||||
in
|
||||
process [ ] target;
|
||||
|
||||
## Filter an attribute set by a given predicate. The filter is only performed
|
||||
## on the base level of the attribute set.
|
||||
##
|
||||
## @type (String -> Any -> Bool) -> Attrs -> Attrs
|
||||
filter =
|
||||
predicate: target:
|
||||
let
|
||||
names = builtins.attrNames target;
|
||||
process =
|
||||
name:
|
||||
let
|
||||
value = target.${name};
|
||||
in
|
||||
if predicate name value then [ { inherit name value; } ] else [ ];
|
||||
valid = builtins.concatMap process names;
|
||||
in
|
||||
builtins.listToAttrs valid;
|
||||
|
||||
## Generate an attribute set from a list of names and a function that is
|
||||
## applied to each name.
|
||||
##
|
||||
## @type (List String) -> (String -> Any) -> Attrs
|
||||
generate =
|
||||
names: f:
|
||||
let
|
||||
pairs = builtins.map (name: {
|
||||
inherit name;
|
||||
value = f name;
|
||||
}) names;
|
||||
in
|
||||
builtins.listToAttrs pairs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,46 @@
|
|||
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.
|
||||
into = {
|
||||
## Convert a boolean value into a string.
|
||||
##
|
||||
## @type Bool -> a -> b -> a | b
|
||||
when =
|
||||
condition: x: y:
|
||||
if condition then x else y;
|
||||
## @type Bool -> String
|
||||
string = value: if value then "true" else "false";
|
||||
|
||||
## Perform a logical AND operation on two values.
|
||||
## Convert a boolean into either the string "yes" or "no".
|
||||
##
|
||||
## @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;
|
||||
## @type Bool -> String
|
||||
yesno = value: if value then "yes" else "no";
|
||||
};
|
||||
|
||||
## Choose between two values based on a condition. When true, the first value
|
||||
## is returned, otherwise the second value is returned.
|
||||
##
|
||||
## @type Bool -> a -> b -> a | b
|
||||
when =
|
||||
condition: x: y:
|
||||
if condition then x else y;
|
||||
|
||||
## Perform a logical AND operation on two values.
|
||||
##
|
||||
## @type Bool -> Bool -> Bool
|
||||
and = a: b: a && b;
|
||||
|
||||
## Perform a logical AND operation on two functions being applied to a value.
|
||||
##
|
||||
## @type (a -> Bool) -> (a -> Bool) -> a -> Bool
|
||||
and' = f: g: (x: (f x) && (g x));
|
||||
|
||||
## Perform a logical OR operation on two values.
|
||||
##
|
||||
## @type Bool -> Bool -> Bool
|
||||
or = a: b: a || b;
|
||||
|
||||
## Perform a logical OR operation on two functions being applied to a value.
|
||||
##
|
||||
## @type (a -> Bool) -> (a -> Bool) -> a -> Bool
|
||||
or' = f: g: (x: (f x) || (g x));
|
||||
|
||||
## Perform a logical NOT operation on a value.
|
||||
##
|
||||
## @type Bool -> Bool
|
||||
not = a: !a;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,155 +1,153 @@
|
|||
lib: {
|
||||
dag = {
|
||||
validate = {
|
||||
## Check that a value is a DAG entry.
|
||||
##
|
||||
## @type a -> Bool
|
||||
entry = value: (value ? value) && (value ? before) && (value ? after);
|
||||
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;
|
||||
};
|
||||
## 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;
|
||||
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;
|
||||
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;
|
||||
after = value.after ++ (getEntriesBefore graph name);
|
||||
};
|
||||
}) sorted.result;
|
||||
}
|
||||
else
|
||||
sorted;
|
||||
};
|
||||
|
||||
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 [ ]);
|
||||
}
|
||||
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
|
||||
) graph;
|
||||
in
|
||||
defaults // result;
|
||||
};
|
||||
// {
|
||||
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.
|
||||
## 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 (String -> a -> b) -> Dag a -> Dag b
|
||||
map = f: builtins.mapAttrs (name: value: value // { value = f name value.value; });
|
||||
## @type List String -> List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
between = after: before: value: { inherit before after 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 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 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 [ ];
|
||||
};
|
||||
|
||||
## 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;
|
||||
|
||||
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 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 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 [ ];
|
||||
};
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that occurs after other entries.
|
||||
##
|
||||
## @type String -> List String -> List a -> Dag a
|
||||
after = tag: after: lib.dag.entries.between tag after [ ];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,43 @@
|
|||
let
|
||||
files = [
|
||||
./attrs
|
||||
./bools
|
||||
./dag
|
||||
./errors
|
||||
./fp
|
||||
./generators
|
||||
./importers
|
||||
./licenses
|
||||
./lists
|
||||
./math
|
||||
./modules
|
||||
./numbers
|
||||
./options
|
||||
./packages
|
||||
./paths
|
||||
./points
|
||||
./strings
|
||||
./types
|
||||
./versions
|
||||
"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;
|
||||
# Convert string to path
|
||||
pathify = strpath: ./. + "/${strpath}";
|
||||
|
||||
# We need to reconstruct this:
|
||||
#
|
||||
# ```
|
||||
# lib: { foo = "sdf"; bar = "sdfds"; }
|
||||
# ```
|
||||
#
|
||||
# To this:
|
||||
#
|
||||
# ```
|
||||
# lib: { filename = { foo = "sdf"; bar = "sdfds"; }; }
|
||||
# ```
|
||||
#
|
||||
# For this, we return a new attrset with key "${filename}", and we wrap this thing into a lambda
|
||||
libs = builtins.map (f: lib: { "${f}" = import (pathify f) lib; }) 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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
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;
|
||||
};
|
||||
## Prints a message if the condition is not met. The result of
|
||||
## the condition is returned.
|
||||
##
|
||||
## @notest
|
||||
## @type Bool -> String -> Bool
|
||||
trace = condition: message: if condition then true else builtins.trace message false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,62 @@
|
|||
lib: {
|
||||
fp = {
|
||||
## A function that returns its argument.
|
||||
##
|
||||
## @type a -> a
|
||||
id = x: x;
|
||||
## 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);
|
||||
## 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));
|
||||
## 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);
|
||||
## 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;
|
||||
## 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;
|
||||
## 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;
|
||||
};
|
||||
## Create a function that is called with only the arguments it specifies.
|
||||
##
|
||||
## @type Attrs a => (a -> b) -> a -> b
|
||||
withDynamicArgs =
|
||||
f: args:
|
||||
let
|
||||
fArgs = lib.fp.args f;
|
||||
common = builtins.intersectAttrs fArgs args;
|
||||
in
|
||||
if builtins.isAttrs args then if fArgs == { } then f args else f common else f args;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,152 +1,150 @@
|
|||
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'!"
|
||||
## 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
|
||||
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;
|
||||
};
|
||||
"{${prefix}${contents}${suffix}}";
|
||||
in
|
||||
if null == value then
|
||||
prettyNull
|
||||
else if builtins.isInt value || builtins.isFloat value then
|
||||
prettyNumber
|
||||
else if builtins.isBool value then
|
||||
prettyBool
|
||||
else if builtins.isString value then
|
||||
prettyString
|
||||
else if builtins.isPath value then
|
||||
prettyPath
|
||||
else if builtins.isList value then
|
||||
prettyList
|
||||
else if builtins.isFunction value then
|
||||
prettyFunction
|
||||
else if builtins.isAttrs value then
|
||||
prettyAttrs
|
||||
else
|
||||
builtins.abort "lib.generators.pretty: should never happen (value = ${value})";
|
||||
in
|
||||
process indent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
lib: {
|
||||
importers = {
|
||||
## Import a JSON file as a Nix value.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> a
|
||||
json = file: builtins.fromJSON (builtins.readFile file);
|
||||
## 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);
|
||||
};
|
||||
## Import a TOML file as a Nix value.
|
||||
##
|
||||
## @notest
|
||||
## @type Path -> a
|
||||
toml = file: builtins.fromTOML (builtins.readFile file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,27 @@
|
|||
lib: {
|
||||
licenses =
|
||||
let
|
||||
raw = import ./all.nix;
|
||||
lib:
|
||||
let
|
||||
raw = import ./all.nix;
|
||||
|
||||
defaults = name: {
|
||||
inherit name;
|
||||
free = true;
|
||||
};
|
||||
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;
|
||||
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;
|
||||
}
|
||||
normalize =
|
||||
name:
|
||||
lib.fp.pipe [
|
||||
(withDefaults name)
|
||||
withSpdx
|
||||
withRedistributable
|
||||
];
|
||||
in
|
||||
builtins.mapAttrs normalize raw
|
||||
|
|
|
|||
|
|
@ -1,255 +1,249 @@
|
|||
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);
|
||||
};
|
||||
|
||||
cartesian = {
|
||||
product = domain:
|
||||
let
|
||||
names = builtins.attrNames domain;
|
||||
|
||||
process =
|
||||
results: name:
|
||||
builtins.concatMap
|
||||
(result: builtins.map (values: result // { ${name} = values; }) domain.${name})
|
||||
results;
|
||||
in
|
||||
builtins.foldl' process [{ }] names;
|
||||
|
||||
map = f: domain:
|
||||
builtins.map f (lib.lists.cartesian.product domain);
|
||||
};
|
||||
|
||||
## Map a list using both the index and value of each item. The
|
||||
## index starts at 0.
|
||||
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 (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 ];
|
||||
## @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);
|
||||
};
|
||||
|
||||
cartesian = {
|
||||
product =
|
||||
domain:
|
||||
let
|
||||
names = builtins.attrNames domain;
|
||||
|
||||
process =
|
||||
results: name:
|
||||
builtins.concatMap (
|
||||
result: builtins.map (values: result // { ${name} = values; }) domain.${name}
|
||||
) results;
|
||||
in
|
||||
builtins.foldl' process [ { } ] names;
|
||||
|
||||
map = f: domain: builtins.map f (lib.lists.cartesian.product domain);
|
||||
};
|
||||
|
||||
## 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 ];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,14 +50,32 @@ in
|
|||
"computes the cartesian product of two lists" =
|
||||
let
|
||||
expected = [
|
||||
{ x = 1; y = "a"; }
|
||||
{ x = 1; y = "b"; }
|
||||
{ x = 2; y = "a"; }
|
||||
{ x = 2; y = "b"; }
|
||||
{
|
||||
x = 1;
|
||||
y = "a";
|
||||
}
|
||||
{
|
||||
x = 1;
|
||||
y = "b";
|
||||
}
|
||||
{
|
||||
x = 2;
|
||||
y = "a";
|
||||
}
|
||||
{
|
||||
x = 2;
|
||||
y = "b";
|
||||
}
|
||||
];
|
||||
actual = lib.lists.cartesian.product {
|
||||
x = [ 1 2 ];
|
||||
y = [ "a" "b" ];
|
||||
x = [
|
||||
1
|
||||
2
|
||||
];
|
||||
y = [
|
||||
"a"
|
||||
"b"
|
||||
];
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
|
@ -73,12 +91,17 @@ in
|
|||
"2-b"
|
||||
];
|
||||
actual = lib.lists.cartesian.map ({ x, y }: "${builtins.toString x}-${y}") {
|
||||
x = [ 1 2 ];
|
||||
y = [ "a" "b" ];
|
||||
x = [
|
||||
1
|
||||
2
|
||||
];
|
||||
y = [
|
||||
"a"
|
||||
"b"
|
||||
];
|
||||
};
|
||||
in
|
||||
(builtins.trace (builtins.deepSeq actual actual))
|
||||
actual == expected;
|
||||
(builtins.trace (builtins.deepSeq actual actual)) actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -189,7 +212,7 @@ in
|
|||
actual = lib.lists.last [ ];
|
||||
evaluated = builtins.tryEval actual;
|
||||
in
|
||||
!evaluated.success;
|
||||
!evaluated.success;
|
||||
};
|
||||
|
||||
"slice" = {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
lib: {
|
||||
math = {
|
||||
## Return the smaller of two numbers.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
min = x: y: if x < y then x else y;
|
||||
## 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;
|
||||
};
|
||||
## Return the larger of two numbers.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
max = x: y: if x > y then x else y;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -212,7 +212,7 @@ in
|
|||
aux = lib.options.create {
|
||||
description = "Hello world!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -222,7 +222,7 @@ in
|
|||
aux = lib.options.create {
|
||||
example = "Heya!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -230,8 +230,7 @@ in
|
|||
];
|
||||
};
|
||||
in
|
||||
evaluated.options.aux.description == "Hello world!"
|
||||
&& evaluated.options.aux.example == "Heya!";
|
||||
evaluated.options.aux.description == "Hello world!" && evaluated.options.aux.example == "Heya!";
|
||||
"fails to merge unmergeable options" =
|
||||
let
|
||||
evaluated = lib.modules.run {
|
||||
|
|
@ -241,7 +240,7 @@ in
|
|||
aux = lib.options.create {
|
||||
description = "Hello world!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -251,7 +250,7 @@ in
|
|||
aux = lib.options.create {
|
||||
description = "Heya!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -259,7 +258,7 @@ in
|
|||
];
|
||||
};
|
||||
in
|
||||
(builtins.tryEval evaluated.options.aux.description).success == false;
|
||||
(builtins.tryEval evaluated.options.aux.description).success == false;
|
||||
"merges three options" =
|
||||
let
|
||||
evaluated = lib.modules.run {
|
||||
|
|
@ -285,7 +284,7 @@ in
|
|||
aux = lib.options.create {
|
||||
example = "Heya!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -293,9 +292,11 @@ in
|
|||
{
|
||||
options = {
|
||||
aux = lib.options.create {
|
||||
default = { hello = "world"; };
|
||||
default = {
|
||||
hello = "world";
|
||||
};
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -303,9 +304,9 @@ in
|
|||
];
|
||||
};
|
||||
in
|
||||
evaluated.options.aux.description == "Hello world!"
|
||||
&& evaluated.options.aux.example == "Heya!"
|
||||
&& evaluated.options.aux.default == { hello = "world"; };
|
||||
evaluated.options.aux.description == "Hello world!"
|
||||
&& evaluated.options.aux.example == "Heya!"
|
||||
&& evaluated.options.aux.default == { hello = "world"; };
|
||||
"overrides option properties" =
|
||||
let
|
||||
evaluated = lib.modules.run {
|
||||
|
|
@ -315,7 +316,7 @@ in
|
|||
aux = lib.options.create {
|
||||
description = lib.modules.overrides.force "Hello world!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -325,14 +326,15 @@ in
|
|||
aux = lib.options.create {
|
||||
description = "Heya!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in evaluated.options.aux.description == "Hello world!";
|
||||
in
|
||||
evaluated.options.aux.description == "Hello world!";
|
||||
"permits duplicate option properties" =
|
||||
let
|
||||
evaluated = lib.modules.run {
|
||||
|
|
@ -342,7 +344,7 @@ in
|
|||
aux = lib.options.create {
|
||||
description = "Hello world!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -352,7 +354,7 @@ in
|
|||
aux = lib.options.create {
|
||||
description = "Hello world!";
|
||||
type = lib.types.submodules.of {
|
||||
modules = [];
|
||||
modules = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -360,7 +362,7 @@ in
|
|||
];
|
||||
};
|
||||
in
|
||||
evaluated.options.aux.description == "Hello world!";
|
||||
evaluated.options.aux.description == "Hello world!";
|
||||
};
|
||||
|
||||
"normalize" = {
|
||||
|
|
|
|||
|
|
@ -1,72 +1,70 @@
|
|||
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.
|
||||
into = {
|
||||
## Convert a number into a string.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
compare =
|
||||
a: b:
|
||||
if a < b then
|
||||
-1
|
||||
else if a > b then
|
||||
1
|
||||
else
|
||||
0;
|
||||
## @type Int | Float -> String
|
||||
string = value: if builtins.isInt value then builtins.toString value else builtins.toJSON value;
|
||||
|
||||
## Convert a number into a list of digits in the given base.
|
||||
##
|
||||
## @type Int -> Int -> List Int
|
||||
base =
|
||||
base: target:
|
||||
let
|
||||
process =
|
||||
value:
|
||||
let
|
||||
r = value - ((value / base) * base);
|
||||
q = (value - r) / base;
|
||||
in
|
||||
if value < base then [ value ] else [ r ] ++ process q;
|
||||
result = process target;
|
||||
in
|
||||
assert lib.errors.trace (builtins.isInt base) "Base must be an integer.";
|
||||
assert lib.errors.trace (builtins.isInt target) "Target must be an integer.";
|
||||
assert lib.errors.trace (base >= 2) "Base must be at least 2.";
|
||||
assert lib.errors.trace (target >= 0) "Target cannot be negative.";
|
||||
lib.lists.reverse result;
|
||||
|
||||
## Convert a number into a hexadecimal string.
|
||||
##
|
||||
## @type Int -> String
|
||||
hex =
|
||||
value:
|
||||
let
|
||||
serialize =
|
||||
part:
|
||||
if part < 10 then
|
||||
builtins.toString part
|
||||
else if part == 10 then
|
||||
"A"
|
||||
else if part == 11 then
|
||||
"B"
|
||||
else if part == 12 then
|
||||
"C"
|
||||
else if part == 13 then
|
||||
"D"
|
||||
else if part == 14 then
|
||||
"E"
|
||||
else if part == 15 then
|
||||
"F"
|
||||
else
|
||||
builtins.throw "Invalid hex digit.";
|
||||
in
|
||||
lib.strings.concatMap serialize (lib.numbers.into.base 16 value);
|
||||
};
|
||||
|
||||
## Compare two numbers. When the first number is less than the second, -1
|
||||
## is returned. When the first number is greater than the second, 1 is
|
||||
## returned. When the numbers are equal, 0 is returned.
|
||||
##
|
||||
## @type Int -> Int -> Int
|
||||
compare =
|
||||
a: b:
|
||||
if a < b then
|
||||
-1
|
||||
else if a > b then
|
||||
1
|
||||
else
|
||||
0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,360 +1,357 @@
|
|||
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;
|
||||
getPriority =
|
||||
value:
|
||||
if lib.types.is "override" value then
|
||||
value.priority
|
||||
else
|
||||
lib.modules.DEFAULT_PRIORITY;
|
||||
higherPriority = value1: value2: (getPriority value1) < (getPriority value2);
|
||||
mergeProperty = name:
|
||||
if (higherPriority result.${name} option.options.${name}) then result.${name}
|
||||
else if (higherPriority option.options.${name} result.${name}) then option.options.${name}
|
||||
else if result.${name} == option.options.${name} then result.${name} # This is mostly so any defaults/etc. get merged cleanly
|
||||
else builtins.throw "The option `${lib.options.getIdentifier location}` in `${option.__file__}` is already declared in ${serializedFiles}, but their ${name} values could not be merged. Please set one as a higher priority (e.g. using `${name} = lib.modules.overrides.force <value>`) or remove a definition";
|
||||
in
|
||||
if
|
||||
shared "type" && !isTypeMergeable
|
||||
then
|
||||
builtins.throw "The option `${lib.options.getIdentifier location}` in `${option.__file__}` is already declared in ${serializedFiles} but its type is not mergeable. Please remove a definition"
|
||||
else
|
||||
option.options
|
||||
// result
|
||||
// {
|
||||
declarations = result.declarations ++ [ option.__file__ ];
|
||||
options = submodules;
|
||||
}
|
||||
// (if shared "default" then { default = mergeProperty "default"; } else {})
|
||||
// (if shared "example" then { example = mergeProperty "example"; } else {})
|
||||
// (if shared "description" then { description = mergeProperty "description"; } else {})
|
||||
// (if shared "apply" then { apply = mergeProperty "apply"; } else {})
|
||||
// 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.
|
||||
merge = {
|
||||
## Merge a list of option definitions into a single value.
|
||||
##
|
||||
## @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 ? lib.modules.overrides.default null,
|
||||
default ? lib.modules.overrides.default { },
|
||||
example ? lib.modules.overrides.default null,
|
||||
visible ? null,
|
||||
internal ? null,
|
||||
writable ? null,
|
||||
description ? lib.modules.overrides.default null,
|
||||
}:
|
||||
{
|
||||
__type__ = "option";
|
||||
inherit
|
||||
type
|
||||
apply
|
||||
default
|
||||
example
|
||||
visible
|
||||
internal
|
||||
writable
|
||||
description
|
||||
;
|
||||
};
|
||||
|
||||
## Create a sink option.
|
||||
##
|
||||
## @type @alias lib.options.create
|
||||
sink =
|
||||
settings:
|
||||
## @type Location -> List Definition -> Any
|
||||
default =
|
||||
location: definitions:
|
||||
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.";
|
||||
};
|
||||
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
|
||||
lib.options.create (defaults // settings);
|
||||
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.";
|
||||
|
||||
## Get the definition values from a list of options definitions.
|
||||
## Merge multiple option definitions together.
|
||||
##
|
||||
## @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:
|
||||
## @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;
|
||||
|
||||
definitionsWithDefault =
|
||||
if option ? default && option.default ? value then
|
||||
[
|
||||
{
|
||||
__file__ = builtins.head option.declarations;
|
||||
value = lib.modules.overrides.option option.default.value;
|
||||
}
|
||||
]
|
||||
++ definitions
|
||||
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
|
||||
definitions;
|
||||
overridden.values;
|
||||
|
||||
isDefined = values != [ ];
|
||||
|
||||
invalid = builtins.filter (definition: !(type.check definition.value)) values;
|
||||
|
||||
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}"
|
||||
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
|
||||
lib.options.merge.definitions location option.type definitionsWithDefault;
|
||||
builtins.throw "The option `${identifier}` is used but not defined.";
|
||||
|
||||
value = if option.apply or null != null then option.apply merged.merged else merged.merged;
|
||||
optional = if isDefined then { value = merged; } else { };
|
||||
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;
|
||||
{
|
||||
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;
|
||||
getPriority =
|
||||
value: if lib.types.is "override" value then value.priority else lib.modules.DEFAULT_PRIORITY;
|
||||
higherPriority = value1: value2: (getPriority value1) < (getPriority value2);
|
||||
mergeProperty =
|
||||
name:
|
||||
if (higherPriority result.${name} option.options.${name}) then
|
||||
result.${name}
|
||||
else if (higherPriority option.options.${name} result.${name}) then
|
||||
option.options.${name}
|
||||
else if result.${name} == option.options.${name} then
|
||||
result.${name} # This is mostly so any defaults/etc. get merged cleanly
|
||||
else
|
||||
builtins.throw "The option `${lib.options.getIdentifier location}` in `${option.__file__}` is already declared in ${serializedFiles}, but their ${name} values could not be merged. Please set one as a higher priority (e.g. using `${name} = lib.modules.overrides.force <value>`) or remove a definition";
|
||||
in
|
||||
if shared "type" && !isTypeMergeable then
|
||||
builtins.throw "The option `${lib.options.getIdentifier location}` in `${option.__file__}` is already declared in ${serializedFiles} but its type is not mergeable. Please remove a definition"
|
||||
else
|
||||
option.options
|
||||
// result
|
||||
// {
|
||||
declarations = result.declarations ++ [ option.__file__ ];
|
||||
options = submodules;
|
||||
}
|
||||
// (if shared "default" then { default = mergeProperty "default"; } else { })
|
||||
// (if shared "example" then { example = mergeProperty "example"; } else { })
|
||||
// (if shared "description" then { description = mergeProperty "description"; } else { })
|
||||
// (if shared "apply" then { apply = mergeProperty "apply"; } else { })
|
||||
// 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 ? lib.modules.overrides.default null,
|
||||
default ? lib.modules.overrides.default { },
|
||||
example ? lib.modules.overrides.default null,
|
||||
visible ? null,
|
||||
internal ? null,
|
||||
writable ? null,
|
||||
description ? lib.modules.overrides.default null,
|
||||
}:
|
||||
{
|
||||
__type__ = "option";
|
||||
inherit
|
||||
type
|
||||
apply
|
||||
default
|
||||
example
|
||||
visible
|
||||
internal
|
||||
writable
|
||||
description
|
||||
;
|
||||
};
|
||||
|
||||
## Create a sink option.
|
||||
##
|
||||
## @type @alias lib.options.create
|
||||
sink =
|
||||
settings:
|
||||
let
|
||||
defaults = {
|
||||
internal = true;
|
||||
visible = false;
|
||||
default = false;
|
||||
description = "A sink option for unused definitions";
|
||||
type = lib.types.create {
|
||||
name = "sink";
|
||||
check = lib.fp.const true;
|
||||
merge = lib.fp.const (lib.fp.const false);
|
||||
};
|
||||
apply = value: builtins.throw "Cannot read the value of a Sink option.";
|
||||
};
|
||||
in
|
||||
lib.options.create (defaults // settings);
|
||||
|
||||
## Get the definition values from a list of options definitions.
|
||||
##
|
||||
## @type List Definition -> Any
|
||||
getDefinitionValues = definitions: builtins.map (definition: definition.value) definitions;
|
||||
|
||||
## Convert a list of option identifiers into a single identifier.
|
||||
##
|
||||
## @type List String -> String
|
||||
getIdentifier =
|
||||
location:
|
||||
let
|
||||
special = [
|
||||
# lib.types.attrs.of (lib.types.submodule {})
|
||||
"<name>"
|
||||
# lib.types.list.of (submodule {})
|
||||
"*"
|
||||
# lib.types.function
|
||||
"<function body>"
|
||||
];
|
||||
escape = part: if builtins.elem part special then part else lib.strings.escape.nix.identifier part;
|
||||
in
|
||||
lib.strings.concatMapSep "." escape location;
|
||||
|
||||
## Get a string message of the definitions for an option.
|
||||
##
|
||||
## @type List Definition -> String
|
||||
getDefinitions =
|
||||
definitions:
|
||||
let
|
||||
serialize =
|
||||
definition:
|
||||
let
|
||||
valueWithRecursionLimit = lib.generators.withRecursion {
|
||||
limit = 10;
|
||||
throw = false;
|
||||
} definition.value;
|
||||
|
||||
eval = builtins.tryEval (lib.generators.pretty { } valueWithRecursionLimit);
|
||||
|
||||
lines = lib.strings.split "\n" eval.value;
|
||||
linesLength = builtins.length lines;
|
||||
firstFiveLines = lib.lists.take 5 lines;
|
||||
|
||||
ellipsis = lib.lists.when (linesLength > 5) "...";
|
||||
|
||||
value = builtins.concatStringsSep "\n " (firstFiveLines ++ ellipsis);
|
||||
|
||||
result =
|
||||
if !eval.success then
|
||||
""
|
||||
else if linesLength > 1 then
|
||||
":\n " + value
|
||||
else
|
||||
": " + value;
|
||||
in
|
||||
"\n- In `${definition.__file__}`${result}";
|
||||
in
|
||||
lib.strings.concatMap serialize definitions;
|
||||
|
||||
## Run a set of definitions, calculating the resolved value and associated information.
|
||||
##
|
||||
## @type Location -> Option -> List Definition -> String & { value :: Any, highestPriority :: Int, isDefined :: Bool, files :: List String, definitions :: List Any, definitionsWithLocations :: List Definition }
|
||||
run =
|
||||
location: option: definitions:
|
||||
let
|
||||
identifier = lib.options.getIdentifier location;
|
||||
|
||||
definitionsWithDefault =
|
||||
if option ? default && option.default ? value then
|
||||
[
|
||||
{
|
||||
__file__ = builtins.head option.declarations;
|
||||
value = lib.modules.overrides.option option.default.value;
|
||||
}
|
||||
]
|
||||
++ definitions
|
||||
else
|
||||
definitions;
|
||||
|
||||
merged =
|
||||
if option.writable or null == false && builtins.length definitionsWithDefault > 1 then
|
||||
let
|
||||
separatedDefinitions = builtins.map (
|
||||
definition:
|
||||
definition
|
||||
// {
|
||||
value = (lib.options.merge.definitions location option.type [ definition ]).merged;
|
||||
}
|
||||
) definitionsWithDefault;
|
||||
in
|
||||
builtins.throw "The option `${identifier}` is not writable, but is set more than once:${lib.options.getDefinitions separatedDefinitions}"
|
||||
else
|
||||
lib.options.merge.definitions location option.type definitionsWithDefault;
|
||||
|
||||
value = if option.apply or null != null then option.apply merged.merged else merged.merged;
|
||||
in
|
||||
option
|
||||
// {
|
||||
value = builtins.addErrorContext "while evaluating the option `${identifier}`:" value;
|
||||
highestPriority = merged.raw.highestPriority;
|
||||
isDefined = merged.isDefined;
|
||||
files = builtins.map (definition: definition.__file__) merged.values;
|
||||
definitions = lib.options.getDefinitionValues merged.values;
|
||||
definitionsWithLocations = merged.values;
|
||||
__toString = identifier;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,50 @@
|
|||
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";
|
||||
## 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
|
||||
## 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)
|
||||
# Strip all leading "."
|
||||
(x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0)
|
||||
|
||||
(lib.strings.split "[^[:alnum:]+._?=-]+")
|
||||
(lib.strings.split "[^[:alnum:]+._?=-]+")
|
||||
|
||||
# Replace invalid character ranges with a "-"
|
||||
(lib.strings.concatMap (x: if builtins.isList x then "-" else x))
|
||||
# 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)
|
||||
# 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;
|
||||
# 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;
|
||||
};
|
||||
## Get an output of a derivation.
|
||||
##
|
||||
## @type String -> Derivation -> String
|
||||
getOutput =
|
||||
output: package:
|
||||
if !package ? outputSpecified || !package.outputSpecified then
|
||||
package.${output} or package.out or package
|
||||
else
|
||||
package;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,65 @@
|
|||
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.
|
||||
into = {
|
||||
## Convert a path into a derivation.
|
||||
##
|
||||
## @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";
|
||||
## @type Path -> Derivation
|
||||
drv =
|
||||
value:
|
||||
let
|
||||
path = builtins.storePath value;
|
||||
result = {
|
||||
type = "derivation";
|
||||
name = lib.packages.sanitizeDerivationName (builtins.substring 33 (-1) (builtins.baseNameOf path));
|
||||
outPath = path;
|
||||
outputs = [ "out" ];
|
||||
outputName = "out";
|
||||
out = result;
|
||||
};
|
||||
in
|
||||
result;
|
||||
};
|
||||
|
||||
validate = {
|
||||
## Check whether a path is contained within the Nix store.
|
||||
##
|
||||
## @type Path -> Bool
|
||||
store =
|
||||
value:
|
||||
if lib.strings.validate.stringifiable value then
|
||||
builtins.substring 0 1 (builtins.toString value) == "/"
|
||||
&& builtins.dirOf (builtins.toString value) == builtins.storeDir
|
||||
else
|
||||
false;
|
||||
};
|
||||
|
||||
## Create a search path from a list of paths.
|
||||
##
|
||||
## @type String -> [String] -> String
|
||||
search =
|
||||
target: paths:
|
||||
lib.strings.concatMapSep ":" (path: path + "/" + target) (
|
||||
builtins.filter (value: value != null) paths
|
||||
);
|
||||
|
||||
## Create a search path from a list of packages.
|
||||
##
|
||||
## @type String -> [Derivation] -> String
|
||||
searchFromOutput =
|
||||
output: target: packages:
|
||||
lib.paths.search target (builtins.map (lib.packages.getOutput output) packages);
|
||||
|
||||
## Create a search path for the binary output of a package.
|
||||
##
|
||||
## @type [Derivation] -> String
|
||||
bin = lib.paths.searchFromOutput "bin" "bin";
|
||||
|
||||
## Create a search path for the library output of a package.
|
||||
##
|
||||
## @type [Derivation] -> String
|
||||
lib = lib.paths.searchFromOutput "lib" "lib";
|
||||
|
||||
## Create a search path for the include output of a package.
|
||||
##
|
||||
## @type [Derivation] -> String
|
||||
include = lib.paths.searchFromOutput "dev" "include";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,52 @@
|
|||
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.
|
||||
##
|
||||
## @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;
|
||||
## 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;
|
||||
## 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;
|
||||
};
|
||||
## Add an `extend` method to the result of a function.
|
||||
##
|
||||
## @type (a -> b) -> b & { extend :: (a -> b -> c) -> b & c }
|
||||
withExtend =
|
||||
f:
|
||||
let
|
||||
create = self: (f self) // { extend = g: lib.points.withExtend (lib.points.extends g f); };
|
||||
in
|
||||
lib.points.fix' create;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,342 +1,340 @@
|
|||
lib: {
|
||||
strings = {
|
||||
into = {
|
||||
## Convert a character to an integer.
|
||||
##
|
||||
## @type String -> Integer
|
||||
int = char: builtins.getAttr char lib.strings.ascii;
|
||||
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.
|
||||
## 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 -> List String
|
||||
chars =
|
||||
value:
|
||||
## @type String -> Any -> String
|
||||
var =
|
||||
name: target:
|
||||
let
|
||||
range = lib.lists.range 0 (builtins.stringLength value - 1);
|
||||
pick = index: builtins.substring index 1 value;
|
||||
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
|
||||
builtins.map pick range;
|
||||
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;
|
||||
|
||||
shell = {
|
||||
## Convert a value into a shell variable.
|
||||
##
|
||||
## @type String -> Any -> String
|
||||
var =
|
||||
name: target:
|
||||
## 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
|
||||
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
|
||||
)
|
||||
})";
|
||||
hex = lib.numbers.into.hex (lib.strings.into.int char);
|
||||
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;
|
||||
"\\x${lib.strings.lower hex}";
|
||||
in
|
||||
builtins.replaceStrings list (builtins.map serialize list);
|
||||
|
||||
## 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);
|
||||
};
|
||||
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 = {
|
||||
## 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 string for use in a regular expression.
|
||||
##
|
||||
## @type String -> String
|
||||
regex = lib.strings.escape.any (lib.strings.into.chars "\\[{()^$?*+|.");
|
||||
|
||||
## Escape a given set of characters in a string using their
|
||||
## ASCII code prefixed with "\x".
|
||||
## Escape a string for use in XML.
|
||||
##
|
||||
## @type String -> String
|
||||
xml =
|
||||
builtins.replaceStrings
|
||||
[
|
||||
"\""
|
||||
"'"
|
||||
"<"
|
||||
">"
|
||||
"&"
|
||||
]
|
||||
[
|
||||
"""
|
||||
"'"
|
||||
"<"
|
||||
">"
|
||||
"&"
|
||||
];
|
||||
|
||||
shell = {
|
||||
## Escape a string for use as a shell argument.
|
||||
##
|
||||
## @type String -> String
|
||||
arg = value: "'${builtins.replaceStrings [ "'" ] [ "'\\''" ] (builtins.toString value)}'";
|
||||
|
||||
## Escape multiple strings for use as shell arguments.
|
||||
##
|
||||
## @type List String -> String
|
||||
c =
|
||||
list:
|
||||
let
|
||||
serialize =
|
||||
char:
|
||||
let
|
||||
hex = lib.numbers.into.hex (lib.strings.into.int char);
|
||||
in
|
||||
"\\x${lib.strings.lower hex}";
|
||||
in
|
||||
builtins.replaceStrings list (builtins.map serialize list);
|
||||
|
||||
nix = {
|
||||
## Escape a string of Nix code.
|
||||
##
|
||||
## @type String -> String
|
||||
value = value: lib.strings.escape.any [ "$" ] (builtins.toJSON value);
|
||||
|
||||
## Escape a string for use as a Nix identifier.
|
||||
##
|
||||
## @type String -> String
|
||||
identifier =
|
||||
value:
|
||||
if builtins.match "[a-zA-Z_][a-zA-Z0-9_'-]*" value != null then
|
||||
value
|
||||
else
|
||||
lib.strings.escape.nix.value value;
|
||||
};
|
||||
|
||||
## Escape a string for use in a regular expression.
|
||||
##
|
||||
## @type String -> String
|
||||
regex = lib.strings.escape.any (lib.strings.into.chars "\\[{()^$?*+|.");
|
||||
|
||||
## Escape a string for use in XML.
|
||||
##
|
||||
## @type String -> String
|
||||
xml =
|
||||
builtins.replaceStrings
|
||||
[
|
||||
"\""
|
||||
"'"
|
||||
"<"
|
||||
">"
|
||||
"&"
|
||||
]
|
||||
[
|
||||
"""
|
||||
"'"
|
||||
"<"
|
||||
">"
|
||||
"&"
|
||||
];
|
||||
|
||||
shell = {
|
||||
## Escape a string for use as a shell argument.
|
||||
##
|
||||
## @type String -> String
|
||||
arg = value: "'${builtins.replaceStrings [ "'" ] [ "'\\''" ] (builtins.toString value)}'";
|
||||
|
||||
## Escape multiple strings for use as shell arguments.
|
||||
##
|
||||
## @type List String -> String
|
||||
args = lib.strings.concatMapSep " " lib.strings.escape.shell.arg;
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,58 +1,56 @@
|
|||
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 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 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 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 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;
|
||||
## 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 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 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;
|
||||
};
|
||||
## Get the patch version from a version string.
|
||||
##
|
||||
## @type String -> String
|
||||
patch =
|
||||
version:
|
||||
let
|
||||
parts = builtins.splitVersion version;
|
||||
in
|
||||
builtins.elemAt parts 2;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue