about summary refs log tree commit diff
path: root/pkgs/profpatsch/nixos-toml-modules.nix
blob: 1424292a2d6c009f726812a9d8d2adcbaa822d49 (plain) (blame)
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
111
112
113
114
115
116
117
118
119
{ lib }:

let
  tv = lib.traceValSeqN 3;
  parseSemver = s:
    let els = lib.splitString "." s;
    in assert (lib.assertMsg (builtins.length els == 3) "semver must be of the form maj.min.patch, was ${s}"); {
        major = lib.head els;
        minor = lib.head (lib.tail els);
        patch = lib.head (lib.tail (lib.tail els));
      };
  semver = major: minor: patch: { inherit major minor patch; };

  nixos-options-0_0_1 = {toml, tomlFileDir}:
    let
      toOption = { description, type, default }: lib.mkOption {
        inherit description default;
        # TODO: this only works for types which are not functions obviously
        type = lib.types.${type};
      };

      transformOptions =
        let isOptionAttrset = a: a ? description && a ? type && a ? default;
        in lib.mapAttrsRecursiveCond
          # TODO: if an option set had these fields for any reason,
          # we wouldn’t recognize it as an option.
          # Maybe there should be an escape hatch in the toml description?
          # We wouldn’t want the toml description to need an extra annotation for an option definition
          (attrs: !isOptionAttrset attrs)
          (_path: attrs:
            if isOptionAttrset attrs
            then toOption attrs
            else lib.id);

    in {
      options = transformOptions toml;
    };

  nixos-config-0_0_1 = {toml, tomlFileDir } : config:
    let
      configVariables = toml.configVariables or {};
      getSingleAttr = attrs: errWrongLength:
        let keys = lib.attrNames attrs;
            len = lib.length keys;
        in
        assert lib.assertMsg (len == 1)
          (errWrongLength { num = len; keys = keys; });
        let key = lib.head keys;
        in { name = key; value = attrs.${key}; };
      mapIfAttrs = attrKeys: f: attrs:
        # lib.attrNames is always sorted like (lib.sort lib.lessThan)
        if (lib.sort lib.lessThan attrKeys) == (lib.attrNames attrs)
        then { mapped = true; val = f attrs; }
        else { mapped = false; val = attrs; };

      transformConfigVariable = { _configVariable }:
        let var = getSingleAttr _configVariable
          ({ num, keys }: "_configVariable must have exactly one attribute, the config variable to match, but you gave ${toString num}: ${lib.generators.toPretty {} keys}");
        in
        assert lib.assertMsg (configVariables ? ${var.name})
          "_configVariable referenced ${var.name}, but you haven’t specified ${var.name} in [configVariables], only: ${lib.generators.toPretty {} toml.configVariables}";
        let
          initialPath = configVariables.${var.name};
          lastPathElem = var.value;
        in lib.getAttrFromPath (initialPath ++ [lastPathElem]) config;

      relativeStringToPath = parentPath: relString: parentPath + ("/" + relString);
      transformImports = map ({module}: relativeStringToPath tomlFileDir module);

      # recurse a structure of attrsets and lists recursively.
      # f takes the current value and returns a { recurse : bool, val : a}
      # where if recurse is true the result of f (val) will be recursed into further
      # and val is the returned value.
      mapValRecursive = f: val:
        let mapped = f val;
        in if !mapped.recurse then mapped.val
        else if lib.isAttrs mapped.val
          then lib.mapAttrs (_: mapValRecursive f) mapped.val
        else if lib.isList mapped.val
          then map (mapValRecursive f) mapped.val
        else mapped.val;

      transformConfig =
        { imports = transformImports toml.imports;
          config =
            mapValRecursive
              (val:
                 if lib.isAttrs val
                 then
                   let m = mapIfAttrs [ "_configVariable" ] transformConfigVariable val;
                   in { recurse = !m.mapped; val = m.val; }
                 else { recurse = true; inherit val; })
              (removeAttrs toml [ "imports" "configVariables" ]);
        };

    in transformConfig;

  readAnyToml = filePath:
    let
      toml = builtins.fromTOML (builtins.readFile filePath);
      parsers = {
        "nixos-options" = {
          "0.0.1" = nixos-options-0_0_1;
        };
        "nixos-config" = {
          "0.0.1" = nixos-config-0_0_1;
        };
      };
       # TODO: these errors are gonna be horrible …
    in parsers.${toml.module.type}.${toml.module.version} {
        toml = (removeAttrs toml [ "module" ]);
        tomlFileDir = builtins.dirOf filePath;
    };

in {
  inherit
    readAnyToml
    ;
}