forked from auxolotl/labs
feat: dag, internal inputs solution, license update
This commit is contained in:
parent
f24f0876a9
commit
04bc516868
|
@ -27,4 +27,4 @@ may collaborate.
|
|||
| Name | Phase | Description |
|
||||
| ------------------------------ | --------- | -------------------------------------------------------------------------- |
|
||||
| [Aux Lib](./lib) | Iteration | A library of common functions used in the Aux ecosystem. |
|
||||
| [Aux Foundation](./foundation) | Idea | Foundational packages which allow for bootstrapping a greater package set. |
|
||||
| [Aux Foundation](./foundation) | Iteration | Foundational packages which allow for bootstrapping a greater package set. |
|
||||
|
|
|
@ -1,6 +1,26 @@
|
|||
{
|
||||
"nodes": {
|
||||
"root": {}
|
||||
"lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"dirtyRev": "f24f0876a9103c7adb8120ce9709fb90c73f2a7c-dirty",
|
||||
"dirtyShortRev": "f24f087-dirty",
|
||||
"lastModified": 1718105966,
|
||||
"narHash": "sha256-L68G29+bPmwZSERg3VYXdfont/w+mssmWnrs6tyBijk=",
|
||||
"type": "git",
|
||||
"url": "file:../?dir=lib"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"type": "git",
|
||||
"url": "file:../?dir=lib"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"lib": "lib"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
|
|
|
@ -2,16 +2,13 @@
|
|||
description = "A set of foundational packages required for bootstrapping a larger package set.";
|
||||
|
||||
inputs = {
|
||||
# TODO: When this project is moved to its own repository we will want to add
|
||||
# inputs for the relevant dependencies.
|
||||
# lib = {
|
||||
# url = "path:../lib";
|
||||
# };
|
||||
lib = {
|
||||
url = "git+file:../?dir=lib";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs: let
|
||||
# inherit (inputs.lib) lib;
|
||||
lib = import ./../lib;
|
||||
inherit (inputs.lib) lib;
|
||||
|
||||
modules = import ./src;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
MIT License
|
||||
Copyright (c) 2003-2023 Eelco Dolstra and the Nixpkgs/NixOS contributors
|
||||
Copyright (c) 2017-2023 Home Manager contributors
|
||||
Copyright (c) 2024 Aux Contributors
|
||||
|
||||
Permission is hereby granted, free
|
||||
|
|
|
@ -7,6 +7,7 @@ let
|
|||
./src/default.test.nix
|
||||
./src/attrs/default.test.nix
|
||||
./src/bools/default.test.nix
|
||||
./src/dag/default.test.nix
|
||||
./src/errors/default.test.nix
|
||||
./src/fp/default.test.nix
|
||||
./src/generators/default.test.nix
|
||||
|
|
138
lib/src/dag/default.nix
Normal file
138
lib/src/dag/default.nix
Normal file
|
@ -0,0 +1,138 @@
|
|||
lib: {
|
||||
dag = {
|
||||
validate = {
|
||||
## Check that a value is a DAG entry.
|
||||
##
|
||||
## @type a -> Bool
|
||||
entry = value:
|
||||
(value ? value)
|
||||
&& (value ? before)
|
||||
&& (value ? after);
|
||||
|
||||
## Check that a value is a DAG.
|
||||
##
|
||||
## @type a -> Bool
|
||||
graph = value: let
|
||||
isContentsValid = builtins.all lib.dag.validate.entry (builtins.attrValues value);
|
||||
in
|
||||
builtins.isAttrs value
|
||||
&& isContentsValid;
|
||||
};
|
||||
|
||||
sort = {
|
||||
## Apply a topological sort to a DAG.
|
||||
##
|
||||
## @type Dag a -> { result :: List a } | { cycle :: List a, loops :: List a }
|
||||
topographic = graph: let
|
||||
getEntriesBefore = graph: name: let
|
||||
before =
|
||||
lib.attrs.filter
|
||||
(key: value: builtins.elem name value.before)
|
||||
graph;
|
||||
in
|
||||
builtins.attrNames before;
|
||||
|
||||
normalize = name: value: {
|
||||
inherit name;
|
||||
value = value.value;
|
||||
after = value.after ++ (getEntriesBefore graph name);
|
||||
};
|
||||
|
||||
normalized = builtins.mapAttrs normalize graph;
|
||||
|
||||
entries = builtins.attrValues normalized;
|
||||
|
||||
isBefore = a: b: builtins.elem a.name b.after;
|
||||
|
||||
sorted = lib.lists.sort.topographic isBefore entries;
|
||||
in
|
||||
if sorted ? result
|
||||
then {
|
||||
result =
|
||||
builtins.map (value: {
|
||||
name = value.name;
|
||||
value = value.value;
|
||||
})
|
||||
sorted.result;
|
||||
}
|
||||
else sorted;
|
||||
};
|
||||
|
||||
## Map over the entries in a DAG and modify their values.
|
||||
##
|
||||
## @type (String -> a -> b) -> Dag a -> Dag b
|
||||
map = f:
|
||||
builtins.mapAttrs
|
||||
(name: value:
|
||||
value
|
||||
// {
|
||||
value = f name value.value;
|
||||
});
|
||||
|
||||
entry = {
|
||||
## Create a new DAG entry.
|
||||
##
|
||||
## @type List String -> List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
between = before: after: value: {
|
||||
inherit before after value;
|
||||
};
|
||||
|
||||
## Create a new DAG entry with no dependencies.
|
||||
##
|
||||
## @type a -> { before :: List String, after :: List String, value :: a }
|
||||
anywhere = lib.dag.entry.between [] [];
|
||||
|
||||
## Create a new DAG entry that occurs before other entries.
|
||||
##
|
||||
## @type List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
before = before: lib.dag.entry.between before [];
|
||||
|
||||
## Create a new DAG entry that occurs after other entries.
|
||||
##
|
||||
## @type List String -> a -> { before :: List String, after :: List String, value :: a }
|
||||
after = lib.dag.entry.between [];
|
||||
};
|
||||
|
||||
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: before: after: 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 before after entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
"${name}" = lib.dag.entry.after after entry;
|
||||
}
|
||||
// (
|
||||
process (i + 1) before [name] rest
|
||||
);
|
||||
in
|
||||
process 0;
|
||||
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that can occur anywhere.
|
||||
##
|
||||
## @type String -> List a -> Dag a
|
||||
anywhere = tag: lib.dag.entries.between tag [] [];
|
||||
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that occurs before other entries.
|
||||
##
|
||||
## @type String -> List String -> List a -> Dag a
|
||||
before = tag: before: lib.dag.entries.between tag before [];
|
||||
|
||||
## Create a DAG from a list of entries, prefixed with a tag, that occurs after other entries.
|
||||
##
|
||||
## @type String -> List String -> List a -> Dag a
|
||||
after = tag: lib.dag.entries.between tag [];
|
||||
};
|
||||
};
|
||||
}
|
135
lib/src/dag/default.test.nix
Normal file
135
lib/src/dag/default.test.nix
Normal file
|
@ -0,0 +1,135 @@
|
|||
let
|
||||
lib = import ./../default.nix;
|
||||
in {
|
||||
"validate" = {
|
||||
"entry" = {
|
||||
"invalid value" = let
|
||||
expected = false;
|
||||
actual = lib.dag.validate.entry {};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"a manually created value" = let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry {
|
||||
value = null;
|
||||
before = [];
|
||||
after = [];
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.between" = let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.between [] [] null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.anywhere" = let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.anywhere null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.before" = let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.before [] null);
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entry.after" = let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.entry (lib.dag.entry.after [] null);
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
|
||||
"graph" = {
|
||||
"invalid value" = let
|
||||
expected = false;
|
||||
actual = lib.dag.validate.graph {
|
||||
x = {};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"a manually created value" = let
|
||||
expected = true;
|
||||
actual = lib.dag.validate.graph {
|
||||
x = {
|
||||
value = null;
|
||||
before = [];
|
||||
after = [];
|
||||
};
|
||||
};
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.between" = let
|
||||
expected = true;
|
||||
graph = lib.dag.entries.between "example" [] [] [null null];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.anywhere" = let
|
||||
expected = true;
|
||||
graph = lib.dag.entries.anywhere "example" [null null];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.before" = let
|
||||
expected = true;
|
||||
graph = lib.dag.entries.before "example" [] [null null];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
|
||||
"entries.after" = let
|
||||
expected = true;
|
||||
graph = lib.dag.entries.after "example" [] [null null];
|
||||
actual = lib.dag.validate.graph graph;
|
||||
in
|
||||
actual == expected;
|
||||
};
|
||||
};
|
||||
|
||||
"sort" = {
|
||||
"topographic" = {
|
||||
"handles an empty graph" = let
|
||||
expected = [];
|
||||
actual = lib.dag.sort.topographic {};
|
||||
in
|
||||
actual.result == expected;
|
||||
|
||||
"sorts a graph" = let
|
||||
expected = [
|
||||
{
|
||||
name = "a";
|
||||
value = "a";
|
||||
}
|
||||
{
|
||||
name = "b";
|
||||
value = "b";
|
||||
}
|
||||
{
|
||||
name = "c";
|
||||
value = "c";
|
||||
}
|
||||
{
|
||||
name = "d";
|
||||
value = "d";
|
||||
}
|
||||
];
|
||||
actual = lib.dag.sort.topographic {
|
||||
a = lib.dag.entry.anywhere "a";
|
||||
b = lib.dag.entry.between ["c"] ["a"] "b";
|
||||
c = lib.dag.entry.before ["c"] "c";
|
||||
d = lib.dag.entry.after ["c"] "d";
|
||||
};
|
||||
in
|
||||
actual.result == expected;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -2,6 +2,7 @@ let
|
|||
files = [
|
||||
./attrs
|
||||
./bools
|
||||
./dag
|
||||
./errors
|
||||
./fp
|
||||
./generators
|
||||
|
|
|
@ -29,6 +29,56 @@ lib: {
|
|||
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 topographic 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
|
||||
topographic = predicate: list: let
|
||||
searched = lib.lists.search.depthFirst true predicate list;
|
||||
results = lib.lists.sort.topographic predicate (searched.visited ++ searched.rest);
|
||||
in
|
||||
if builtins.length list < 2
|
||||
then {result = list;}
|
||||
else if searched ? cycle
|
||||
then {
|
||||
loops = searched.loops;
|
||||
cycle = lib.lists.reverse ([searched.cycle] ++ searched.visited);
|
||||
}
|
||||
else if results ? cycle
|
||||
then results
|
||||
else {
|
||||
result = [searched.minimal] ++ results.result;
|
||||
};
|
||||
};
|
||||
|
||||
search = {
|
||||
## Perform a depth first search on a list. The supplied predicate function determines whether
|
||||
## its first argument comes before the second argument.
|
||||
##
|
||||
## @type Bool -> (a -> a -> Bool) -> List a
|
||||
depthFirst = isAcyclical: predicate: list: let
|
||||
process = current: visited: rest: let
|
||||
loops = builtins.filter (value: predicate value current) visited;
|
||||
partitioned = builtins.partition (value: predicate value current) rest;
|
||||
in
|
||||
if isAcyclical && (builtins.length loops > 0)
|
||||
then {
|
||||
cycle = current;
|
||||
inherit loops visited rest;
|
||||
}
|
||||
else if builtins.length partitioned.right == 0
|
||||
then {
|
||||
minimal = current;
|
||||
inherit visited rest;
|
||||
}
|
||||
else
|
||||
process
|
||||
(builtins.head partitioned.right)
|
||||
([current] ++ visited)
|
||||
(builtins.tail partitioned.right ++ partitioned.wrong);
|
||||
in
|
||||
process (builtins.head list) [] (builtins.tail list);
|
||||
};
|
||||
|
||||
## Map a list using both the index and value of each item. The
|
||||
|
|
|
@ -130,6 +130,54 @@ lib: {
|
|||
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.
|
||||
##
|
||||
|
|
|
@ -1036,4 +1036,71 @@ lib: {
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
dag = {
|
||||
## Create a type that allows a DAG (Directed Acyclic Graph) of a given type.
|
||||
##
|
||||
## @type Attrs -> Attrs
|
||||
of = type: let
|
||||
resolved = lib.types.attrs.of (lib.types.dag.entry type);
|
||||
in
|
||||
lib.types.create {
|
||||
name = "Dag";
|
||||
description = "Dag of ${type.description}";
|
||||
check = resolved.check;
|
||||
merge = resolved.merge;
|
||||
fallback = resolved.fallback;
|
||||
getSubOptions = prefix: type.getSubOptions (prefix ++ ["<name>"]);
|
||||
getSubModules = type.getSubModules;
|
||||
withSubModules = modules: lib.types.dag.of (type.withSubModules modules);
|
||||
functor = lib.types.functor "dag.of" // {wrapped = type;};
|
||||
children = {
|
||||
element = type;
|
||||
resolved = resolved;
|
||||
};
|
||||
};
|
||||
|
||||
## Create a type that allows a DAG entry of a given type.
|
||||
##
|
||||
## @type Attrs -> Attrs
|
||||
entry = type: let
|
||||
submodule = lib.types.submodule ({name}: {
|
||||
options = {
|
||||
value = lib.options.create {
|
||||
type = type;
|
||||
};
|
||||
before = lib.options.create {
|
||||
type = lib.types.list.of lib.types.string;
|
||||
};
|
||||
after = lib.options.create {
|
||||
type = lib.types.list.of lib.types.string;
|
||||
};
|
||||
};
|
||||
});
|
||||
normalize = definition: let
|
||||
value =
|
||||
if definition ? priority
|
||||
then lib.modules.order definition.priority definition.value
|
||||
else definition.value;
|
||||
in
|
||||
if lib.dag.validate.entry definition.value
|
||||
then definition.value
|
||||
else lib.dag.entry.anywhere value;
|
||||
in
|
||||
lib.types.create {
|
||||
name = "DagEntry";
|
||||
description = "DagEntry (${type.description})";
|
||||
merge = location: definitions:
|
||||
submodule.merge
|
||||
location
|
||||
(
|
||||
builtins.map
|
||||
(definition: {
|
||||
__file__ = definition.__file__;
|
||||
value = normalize definition;
|
||||
})
|
||||
definitions
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,26 +7,34 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1,
|
||||
"narHash": "sha256-CDfGWoJg+7i9z6quwh/WXuTkOhqKme73dUTvWCvJ8EA=",
|
||||
"path": "../foundation",
|
||||
"type": "path"
|
||||
"dir": "foundation",
|
||||
"dirtyRev": "f24f0876a9103c7adb8120ce9709fb90c73f2a7c-dirty",
|
||||
"dirtyShortRev": "f24f087-dirty",
|
||||
"lastModified": 1718105966,
|
||||
"narHash": "sha256-cvGKyJzoPAXjuM+YDpQM30qwshgoYZmAYtJywFPyfGI=",
|
||||
"type": "git",
|
||||
"url": "file:../?dir=foundation"
|
||||
},
|
||||
"original": {
|
||||
"path": "../foundation",
|
||||
"type": "path"
|
||||
"dir": "foundation",
|
||||
"type": "git",
|
||||
"url": "file:../?dir=foundation"
|
||||
}
|
||||
},
|
||||
"lib": {
|
||||
"locked": {
|
||||
"lastModified": 1,
|
||||
"narHash": "sha256-TtgysWL53BK3f3JrPFunjzJaXWaDG2RbuPMglCwATOY=",
|
||||
"path": "../lib",
|
||||
"type": "path"
|
||||
"dir": "lib",
|
||||
"dirtyRev": "f24f0876a9103c7adb8120ce9709fb90c73f2a7c-dirty",
|
||||
"dirtyShortRev": "f24f087-dirty",
|
||||
"lastModified": 1718105966,
|
||||
"narHash": "sha256-cvGKyJzoPAXjuM+YDpQM30qwshgoYZmAYtJywFPyfGI=",
|
||||
"type": "git",
|
||||
"url": "file:../?dir=lib"
|
||||
},
|
||||
"original": {
|
||||
"path": "../lib",
|
||||
"type": "path"
|
||||
"dir": "lib",
|
||||
"type": "git",
|
||||
"url": "file:../?dir=lib"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
{
|
||||
inputs = {
|
||||
# TODO: When this project is moved to its own repository we will want to add
|
||||
# inputs for the relevant dependencies.
|
||||
# lib = {
|
||||
# url = "path:../lib";
|
||||
# };
|
||||
# foundation = {
|
||||
# url = "path:../foundation";
|
||||
# inputs.lib.follows = "lib";
|
||||
# };
|
||||
lib = {
|
||||
url = "git+file:../?dir=lib";
|
||||
};
|
||||
foundation = {
|
||||
url = "git+file:../?dir=foundation";
|
||||
inputs.lib.follows = "lib";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs: let
|
||||
exports = import ./default.nix {
|
||||
# lib = inputs.lib.lib;
|
||||
# foundation = inputs.foundation.packages.i686-linux;
|
||||
lib = inputs.lib.lib;
|
||||
foundation = inputs.foundation.packages.i686-linux;
|
||||
};
|
||||
in
|
||||
exports;
|
||||
|
|
|
@ -58,7 +58,8 @@ in {
|
|||
};
|
||||
|
||||
description = lib.options.create {
|
||||
type = lib.types.string;
|
||||
type = lib.types.nullish lib.types.string;
|
||||
default.value = null;
|
||||
description = "The description for the package.";
|
||||
};
|
||||
|
||||
|
@ -112,7 +113,7 @@ in {
|
|||
name = lib.options.create {
|
||||
type = lib.types.string;
|
||||
default = {
|
||||
value = "${config.pname}-${config.version}";
|
||||
value = "${config.pname}-${config.version or "unknown"}";
|
||||
text = "\${config.pname}-\${config.version}";
|
||||
};
|
||||
description = "The name of the package.";
|
||||
|
@ -132,6 +133,13 @@ in {
|
|||
|
||||
meta = lib.options.create {
|
||||
type = lib'.types.meta;
|
||||
default = {
|
||||
text = "{ name = <package>.pname; }";
|
||||
value = {
|
||||
name = config.pname;
|
||||
};
|
||||
};
|
||||
description = "The metadata for the package.";
|
||||
};
|
||||
|
||||
env = lib.options.create {
|
||||
|
@ -141,7 +149,11 @@ in {
|
|||
};
|
||||
|
||||
phases = lib.options.create {
|
||||
type = lib.types.attrs.of lib.types.string;
|
||||
type = lib.types.dag.of (
|
||||
lib.types.either
|
||||
lib.types.string
|
||||
(lib.types.function lib.types.string)
|
||||
);
|
||||
default.value = {};
|
||||
description = "Phases for the package's builder to use.";
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue