Commit 14d1c9a
Changed files (1)
modules
modules/mk-settings.nix
@@ -1,22 +1,27 @@
-# Settings surface shared by hosts and users. Aspects declare
-# `settings.{host,user}` as an option set or as a function of the entity
-# instance; entities supply values and aspects read them back.
+# Settings surface shared by hosts and users: aspects declare
+# `settings.{host,user}`, entities supply values, aspects read them back.
{
config,
+ den,
lib,
...
}: let
- # Pipeline-special keys; skip when walking for settings declarations.
- structuralKeys = [
- "excludes"
- "includes"
- "meta"
- "name"
- "provides"
- "themes" # See modules/users/theme.nix
- ];
+ inherit (den.lib.aspects.fx.keyClassification) isStructuralKey;
+
+ skipKey = name:
+ isStructuralKey name
+ || (den.classes or {}) ? ${name}
+ || (den.quirks or {}) ? ${name};
+
+ # Root names are aspect names, so a reserved one such as `themes` stays
+ # walkable there, and only keys inside an aspect are classified.
+ isChildKey = prefix: name: name != "settings" && (prefix == [] || !(skipKey name));
+
+ reshape = raw:
+ if raw ? options || raw ? config || raw ? imports
+ then raw
+ else {options = raw;};
- # Collect { prefix, host, user } for aspect nodes declaring settings.
# Bare-function aspects cannot carry a settings attr and are skipped.
collect = prefix: node:
if !(builtins.isAttrs node)
@@ -43,46 +48,43 @@
else {};
};
children = lib.concatMap (name: collect (prefix ++ [name]) node.${name}) (
- builtins.filter (name:
- !(builtins.elem name (
- if prefix == []
- then []
- else structuralKeys
- ))
- && name != "settings") (
- builtins.attrNames node
- )
+ builtins.filter (isChildKey prefix) (builtins.attrNames node)
);
in
mine ++ children;
collected = collect [] config.den.aspects;
- # Build nested option declarations, grouping shared path prefixes. `ctx` is the
- # entity instance, and forcing it here would recurse: the fixpoint is still
- # being built (gen-schema ADR-0033), so only lazy reads such as defaults may touch it.
+ # `ctx` is the entity instance, and forcing it here would recurse (gen-schema ADR-0033),
+ # so only lazy reads such as defaults may touch it.
mkOptions = ctx: nodes: let
grouped = lib.groupBy (n: builtins.head n.path) nodes;
in
lib.mapAttrs (
- name: group:
- if lib.all (n: builtins.length n.path == 1) group
- then let
- decls = (builtins.head group).decls;
- in
- lib.mkOption {
- type = lib.types.submodule {
- options =
- if builtins.isFunction decls
- then decls ctx
- else decls;
- };
- default = {};
- description = "Settings for aspect `${name}'.";
- }
- else if !(lib.all (n: builtins.length n.path > 1) group)
- then throw "aspect settings: aspect `${name}' is both a settings leaf and a container"
- else mkOptions ctx (map (n: n // {path = lib.drop 1 n.path;}) group)
+ name: group: let
+ own = lib.filter (n: builtins.length n.path == 1) group;
+ children = lib.filter (n: builtins.length n.path > 1) group;
+ ownModule =
+ if own == []
+ then {}
+ else let
+ raw = (builtins.head own).decls;
+ in
+ reshape (
+ if builtins.isFunction raw
+ then raw ctx
+ else raw
+ );
+ childOptions =
+ if children == []
+ then {}
+ else mkOptions ctx (map (n: n // {path = lib.drop 1 n.path;}) children);
+ in
+ lib.mkOption {
+ type = lib.types.submodule (ownModule // {options = (ownModule.options or {}) // childOptions;});
+ default = {};
+ description = "Settings for aspect `${name}'.";
+ }
)
grouped;
@@ -98,12 +100,12 @@
userSettingsType = ctx:
lib.types.submodule {
- # The shared registry and hosts also carry raw user-system values
+ # The shared registry may carry keys outside `settings`.
freeformType = lib.types.attrsOf lib.types.anything;
options.settings = mkOptions ctx userNodes;
};
- # Merge the shared `den.users.<username>` values as lowest-priority defs.
+ # `den.users.<username>` values merge as the lowest-priority definition.
registryAware = inner:
lib.mkOptionType {
name = "den-shared-user-system-settings";
@@ -124,8 +126,7 @@ in {
config = {
den.reservedKeys = ["settings"];
- # Through imports, not on the kind: the option type must be built inside the
- # instance for function declarations to receive it.
+ # Built per instance through imports so function declarations receive entity.
den.schema.host.imports = [
({host, ...}: {
options.settings = lib.mkOption {