aboutsummaryrefslogtreecommitdiff
path: root/home-manager/modules/lib/types-dag.nix
blob: 2efb12645d4d10f0cc83c0ab1a7d6ad54131c268 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
{ dag, lib }:

with lib;

let

  isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);

  dagContentType = elemType:
    types.submodule ({ name, ... }: {
      options = {
        data = mkOption { type = elemType; };
        after = mkOption { type = with types; uniq (listOf str); };
        before = mkOption { type = with types; uniq (listOf str); };
      };
      config = mkIf (elemType.name == "submodule") {
        data._module.args.dagName = name;
      };
    });

in rec {
  # A directed acyclic graph of some inner type.
  #
  # Note, if the element type is a submodule then the `name` argument
  # will always be set to the string "data" since it picks up the
  # internal structure of the DAG values. To give access to the
  # "actual" attribute name a new submodule argument is provided with
  # the name `dagName`.
  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;

      convertAll = loc: defs:
        let
          convertListValue = namePrefix: def:
            let
              vs = def.value;
              pad = paddedIndexStr vs;
              makeEntry = i: v: nameValuePair "${namePrefix}.${pad i}" v;
              warning = ''
                In file ${def.file}
                a list is being assigned to the option '${
                  concatStringsSep "." loc
                }'.
                This will soon be an error due to the list form being deprecated.
                Please use the attribute set form instead with DAG functions to
                express the desired order of entries.
              '';
            in warn warning (listToAttrs (imap1 makeEntry vs));

          convertValue = i: def:
            if isList def.value then
              convertListValue "unnamed-${paddedIndexStr defs i}" def
            else
              def.value;
        in imap1 (i: def: def // { value = convertValue i def; }) defs;

      dagType = dagOf elemType;
    in mkOptionType rec {
      name = "listOrDagOf";
      description = "list or DAG of ${elemType.description}s";
      check = x: isList x || dagType.check x;
      merge = loc: defs: dagType.merge loc (convertAll loc defs);
      getSubOptions = dagType.getSubOptions;
      getSubModules = dagType.getSubModules;
      substSubModules = m: listOrDagOf (elemType.substSubModules m);
      functor = (defaultFunctor name) // { wrapped = elemType; };
    };
}