lib: add type generators dagOf and listOrDagOf

Given an inner type, the former function generates a type that expect
DAG option values. The latter function is only present to temporarily
allow the `programs.ssh.matchBlocks` to keep accepting list values.
This commit is contained in:
Robert Helgesson 2020-01-14 23:41:59 +01:00
parent bff499113e
commit 6c127efb2d
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
8 changed files with 209 additions and 1 deletions

96
modules/lib/types-dag.nix Normal file
View file

@ -0,0 +1,96 @@
{ dag, lib }:
with lib;
let
isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);
dagContentType = elemType: types.submodule {
options = {
data = mkOption { type = elemType; };
after = mkOption { type = with types; uniq (listOf str); };
before = mkOption { type = with types; uniq (listOf str); };
};
};
in
{
# A directed acyclic graph of some inner type.
dagOf = elemType:
let
convertAllToDags =
let
maybeConvert = n: v:
if isDagEntry v
then v
else dag.entryAnywhere v;
in
map (def: def // { value = mapAttrs maybeConvert def.value; });
attrEquivalent = types.attrsOf (dagContentType elemType);
in
mkOptionType rec {
name = "dagOf";
description = "DAG of ${elemType.description}s";
check = isAttrs;
merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: dagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
};
# A directed acyclic graph of some inner type OR a list of that
# inner type. This is a temporary hack for use by the
# `programs.ssh.matchBlocks` and is only guaranteed to be vaguely
# correct!
#
# In particular, adding a dependency on one of the "unnamed-N-M"
# entries generated by a list value is almost guaranteed to destroy
# the list's order.
#
# This function will be removed in version 20.09.
listOrDagOf = elemType:
let
paddedIndexStr = list: i:
let
padWidth = stringLength (toString (length list));
in
fixedWidthNumber padWidth i;
convertAllToDags = defs:
let
convertAttrValue = n: v:
if isDagEntry v then v
else dag.entryAnywhere v;
convertListValue = namePrefix: vs:
let
pad = paddedIndexStr vs;
makeEntry = i: v:
nameValuePair "${namePrefix}.${pad i}" (dag.entryAnywhere v);
in
listToAttrs (imap1 makeEntry vs);
convertValue = i: value:
if isList value
then convertListValue "unnamed-${paddedIndexStr defs i}" value
else mapAttrs convertAttrValue value;
in
imap1 (i: def: def // { value = convertValue i def.value; }) defs;
attrEquivalent = types.attrsOf (dagContentType elemType);
in
mkOptionType rec {
name = "dagOf";
description = "DAG of ${elemType.description}s";
check = x: isAttrs x || isList x;
merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs);
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: dagOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
};
}

View file

@ -1,9 +1,18 @@
{ lib }:
{ lib, dag ? import ./dag.nix { inherit lib; } }:
with lib;
let
hmLib = import ./default.nix { inherit lib; };
typesDag = import ./types-dag.nix { inherit dag lib; };
in
{
inherit (typesDag) dagOf listOrDagOf;
selectorFunction = mkOptionType {
name = "selectorFunction";
description =

View file

@ -34,6 +34,7 @@ import nmt {
// import ./modules/services/sxhkd
// import ./modules/systemd
)
// import ./lib/types
// import ./modules/files
// import ./modules/home-environment
// import ./modules/misc/fontconfig

View file

@ -0,0 +1,3 @@
before:before
between:between
after:after

View file

@ -0,0 +1,39 @@
{ config, lib, pkgs, ... }:
with lib;
let
dag = config.lib.dag;
hmTypes = import ../../../modules/lib/types.nix { inherit dag lib; };
result =
let
sorted = dag.topoSort config.tested.dag;
data = map (e: "${e.name}:${e.data}") sorted.result;
in
concatStringsSep "\n" data + "\n";
in
{
options.tested.dag = mkOption {
type = with types; hmTypes.dagOf str;
};
config = {
tested = mkMerge [
{ dag.after = dag.entryAnywhere "after"; }
{ dag.before = dag.entryBefore ["after"] "before"; }
{ dag.between = dag.entryBetween ["after"] ["before"] "between"; }
];
home.file."result.txt".text = result;
nmt.script = ''
assertFileContent \
home-files/result.txt \
${./dag-merge-result.txt}
'';
};
}

View file

@ -0,0 +1,4 @@
{
lib-types-dag-merge = ./dag-merge.nix;
lib-types-list-or-dag-merge = ./list-or-dag-merge.nix;
}

View file

@ -0,0 +1,15 @@
before:before
between:between
after:after
unnamed-1.1:k
unnamed-1.2:l
unnamed-2.01:a
unnamed-2.02:b
unnamed-2.03:c
unnamed-2.04:d
unnamed-2.05:e
unnamed-2.06:f
unnamed-2.07:g
unnamed-2.08:h
unnamed-2.09:i
unnamed-2.10:j

View file

@ -0,0 +1,41 @@
{ config, lib, pkgs, ... }:
with lib;
let
dag = config.lib.dag;
hmTypes = import ../../../modules/lib/types.nix { inherit dag lib; };
result =
let
sorted = dag.topoSort config.tested.dag;
data = map (e: "${e.name}:${e.data}") sorted.result;
in
concatStringsSep "\n" data + "\n";
in
{
options.tested.dag = mkOption {
type = with types; hmTypes.listOrDagOf str;
};
config = {
tested = mkMerge [
{ dag = [ "k" "l" ]; }
{ dag = [ "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" ]; }
{ dag.after = dag.entryAnywhere "after"; }
{ dag.before = dag.entryBefore ["after"] "before"; }
{ dag.between = dag.entryBetween ["after"] ["before"] "between"; }
];
home.file."result.txt".text = result;
nmt.script = ''
assertFileContent \
home-files/result.txt \
${./list-or-dag-merge-result.txt}
'';
};
}