From 6c127efb2d84f8e0c08dfdc0d7b55623935f40a4 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Tue, 14 Jan 2020 23:41:59 +0100 Subject: [PATCH] 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. --- modules/lib/types-dag.nix | 96 ++++++++++++++++++++ modules/lib/types.nix | 11 ++- tests/default.nix | 1 + tests/lib/types/dag-merge-result.txt | 3 + tests/lib/types/dag-merge.nix | 39 ++++++++ tests/lib/types/default.nix | 4 + tests/lib/types/list-or-dag-merge-result.txt | 15 +++ tests/lib/types/list-or-dag-merge.nix | 41 +++++++++ 8 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 modules/lib/types-dag.nix create mode 100644 tests/lib/types/dag-merge-result.txt create mode 100644 tests/lib/types/dag-merge.nix create mode 100644 tests/lib/types/default.nix create mode 100644 tests/lib/types/list-or-dag-merge-result.txt create mode 100644 tests/lib/types/list-or-dag-merge.nix diff --git a/modules/lib/types-dag.nix b/modules/lib/types-dag.nix new file mode 100644 index 00000000..4003d713 --- /dev/null +++ b/modules/lib/types-dag.nix @@ -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 ++ [""]); + 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 ++ [""]); + getSubModules = elemType.getSubModules; + substSubModules = m: dagOf (elemType.substSubModules m); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; +} diff --git a/modules/lib/types.nix b/modules/lib/types.nix index 1b514d20..da8b7d4f 100644 --- a/modules/lib/types.nix +++ b/modules/lib/types.nix @@ -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 = diff --git a/tests/default.nix b/tests/default.nix index 95a0f16b..6f2b5ec0 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -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 diff --git a/tests/lib/types/dag-merge-result.txt b/tests/lib/types/dag-merge-result.txt new file mode 100644 index 00000000..9779ef13 --- /dev/null +++ b/tests/lib/types/dag-merge-result.txt @@ -0,0 +1,3 @@ +before:before +between:between +after:after diff --git a/tests/lib/types/dag-merge.nix b/tests/lib/types/dag-merge.nix new file mode 100644 index 00000000..c5b884b6 --- /dev/null +++ b/tests/lib/types/dag-merge.nix @@ -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} + ''; + }; +} diff --git a/tests/lib/types/default.nix b/tests/lib/types/default.nix new file mode 100644 index 00000000..9fce65f8 --- /dev/null +++ b/tests/lib/types/default.nix @@ -0,0 +1,4 @@ +{ + lib-types-dag-merge = ./dag-merge.nix; + lib-types-list-or-dag-merge = ./list-or-dag-merge.nix; +} diff --git a/tests/lib/types/list-or-dag-merge-result.txt b/tests/lib/types/list-or-dag-merge-result.txt new file mode 100644 index 00000000..5fb67a51 --- /dev/null +++ b/tests/lib/types/list-or-dag-merge-result.txt @@ -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 diff --git a/tests/lib/types/list-or-dag-merge.nix b/tests/lib/types/list-or-dag-merge.nix new file mode 100644 index 00000000..3f4cd0ce --- /dev/null +++ b/tests/lib/types/list-or-dag-merge.nix @@ -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} + ''; + }; +}