summaryrefslogtreecommitdiff
path: root/modules/launchd/types.nix
blob: 38d7f2043a8c67305feab546d35c11df18db23af (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
100
101
102
103
104
105
106
107
108
109
110
{ lib, ... }:

let
  inherit (lib) imap1 types mkOption showOption optionDescriptionPhrase mergeDefinitions;
  inherit (builtins) map filter length deepSeq throw toString concatLists;
  inherit (lib.options) showDefs;
  wildcardText = lib.literalMD "`*`";

  /**
    A type of list which does not allow duplicate elements. The base/inner
    list type to use (e.g. `types.listOf` or `types.nonEmptyListOf`) is passed
    via argument `listType`, which must be the final type and not a function.

    NOTE: The extra check for duplicates is quadratic and strict, so use this
    type sparingly and only:

      * when needed, and
      * when the list is expected to be recursively short (e.g. < 10 elements)
        and shallow (i.e. strict evaluation of the list won't take too long)

    The implementation of this function is similar to that of
    `types.nonEmptyListOf`.
  */
  types'.uniqueList = listType: listType // {
      description = "unique ${types.optionDescriptionPhrase (class: class == "noun") listType}";
      substSubModules = m: types'.uniqueList (listType.substSubModules m);
      # This has been taken from the implementation of `types.listOf`, but has
      # been modified to throw on duplicates. This check cannot be done in the
      # `check` fn as this check is deep/strict, and because `check` runs
      # prior to merging.
      merge = loc: defs:
        let
          # Each element of `dupes` is a list. When there are duplicates,
          # later lists will be duplicates of earlier lists, so just throw on
          # the first set of duplicates found so that we don't have duplicate
          # error msgs.
          checked = filter (li:
            if length li > 1
            then throw "The option `${showOption loc}' contains duplicate entries after merging:\n${showDefs li}"
            else false) dupes;
          dupes = map (def: filter (def': def'.value == def.value) merged) merged;
          merged = filter (x: x ? value) (concatLists (imap1 (n: def:
            imap1 (m: el:
              let
                inherit (def) file;
                loc' = loc ++ ["[definition ${toString n}-entry ${toString m}]"];
              in
                (mergeDefinitions
                  loc'
                  listType.nestedTypes.elemType
                  [{ inherit file; value = el; }]
                ).optionalValue // {inherit loc' file;}
            ) def.value
          ) defs));
      in
        deepSeq checked (map (x: x.value) merged);
    };
in {
  StartCalendarInterval = let
    CalendarIntervalEntry = types.submodule {
      options = {
        Minute = mkOption {
          type = types.nullOr (types.ints.between 0 59);
          default = null;
          defaultText = wildcardText;
          description = ''
            The minute on which this job will be run.
          '';
        };

        Hour = mkOption {
          type = types.nullOr (types.ints.between 0 23);
          default = null;
          defaultText = wildcardText;
          description = ''
            The hour on which this job will be run.
          '';
        };

        Day = mkOption {
          type = types.nullOr (types.ints.between 1 31);
          default = null;
          defaultText = wildcardText;
          description = ''
            The day on which this job will be run.
          '';
        };

        Weekday = mkOption {
          type = types.nullOr (types.ints.between 0 7);
          default = null;
          defaultText = wildcardText;
          description = ''
            The weekday on which this job will be run (0 and 7 are Sunday).
          '';
        };

        Month = mkOption {
          type = types.nullOr (types.ints.between 1 12);
          default = null;
          defaultText = wildcardText;
          description = ''
            The month on which this job will be run.
          '';
        };
      };
    };
  in
    types.either CalendarIntervalEntry (types'.uniqueList (types.nonEmptyListOf CalendarIntervalEntry));
}