about summary refs log tree commit diff
path: root/pkgs/profpatsch/nixos-toml-modules.nix
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2020-12-30 18:30:53 +0100
committerProfpatsch <mail@profpatsch.de>2020-12-30 18:33:31 +0100
commit47a8bf4dd2cb1f6778b38bc2f4990ba72366d5a9 (patch)
tree51b7b6e249c32b8d466de66b1e421ebf89d1999d /pkgs/profpatsch/nixos-toml-modules.nix
parent2c24ba1d864d98d4c2c3a50501a41b61b43f854a (diff)
machines/profpatsch/base-server: convert module to toml definition
This is a working PoC of specifying module configs as toml
configuration with simple to understand semantics.

Both the option definitions and the actual config values can be
specified via the toml DSL.

This is extremely happy-path for now, so errors are gonna be horrible.
Diffstat (limited to 'pkgs/profpatsch/nixos-toml-modules.nix')
-rw-r--r--pkgs/profpatsch/nixos-toml-modules.nix119
1 files changed, 119 insertions, 0 deletions
diff --git a/pkgs/profpatsch/nixos-toml-modules.nix b/pkgs/profpatsch/nixos-toml-modules.nix
new file mode 100644
index 00000000..1424292a
--- /dev/null
+++ b/pkgs/profpatsch/nixos-toml-modules.nix
@@ -0,0 +1,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
+    ;
+}