main
1# Settings surface shared by hosts and users: aspects declare
2# `settings.{host,user}`, entities supply values, aspects read them back.
3{
4 config,
5 den,
6 lib,
7 ...
8}: let
9 inherit (den.lib.aspects.fx.keyClassification) isStructuralKey;
10
11 skipKey = name:
12 isStructuralKey name
13 || (den.classes or {}) ? ${name}
14 || (den.quirks or {}) ? ${name};
15
16 # Root names are aspect names, so a reserved one such as `themes` stays
17 # walkable there, and only keys inside an aspect are classified.
18 isChildKey = prefix: name: name != "settings" && (prefix == [] || !(skipKey name));
19
20 reshape = raw:
21 if raw ? options || raw ? config || raw ? imports
22 then raw
23 else {options = raw;};
24
25 # Bare-function aspects cannot carry a settings attr and are skipped.
26 collect = prefix: node:
27 if !(builtins.isAttrs node)
28 then []
29 else let
30 settings = let
31 s = node.settings or null;
32 in
33 if builtins.isAttrs s
34 then s
35 else null;
36 isDecl = v: builtins.isAttrs v || builtins.isFunction v;
37 hasHost = isDecl (settings.host or null);
38 hasUser = isDecl (settings.user or null);
39 mine = lib.optional (hasHost || hasUser) {
40 inherit prefix;
41 host =
42 if hasHost
43 then settings.host
44 else {};
45 user =
46 if hasUser
47 then settings.user
48 else {};
49 };
50 children = lib.concatMap (name: collect (prefix ++ [name]) node.${name}) (
51 builtins.filter (isChildKey prefix) (builtins.attrNames node)
52 );
53 in
54 mine ++ children;
55
56 collected = collect [] config.den.aspects;
57
58 # `ctx` is the entity instance, and forcing it here would recurse (gen-schema ADR-0033),
59 # so only lazy reads such as defaults may touch it.
60 mkOptions = ctx: nodes: let
61 grouped = lib.groupBy (n: builtins.head n.path) nodes;
62 in
63 lib.mapAttrs (
64 name: group: let
65 own = lib.filter (n: builtins.length n.path == 1) group;
66 children = lib.filter (n: builtins.length n.path > 1) group;
67 ownModule =
68 if own == []
69 then {}
70 else let
71 raw = (builtins.head own).decls;
72 in
73 reshape (
74 if builtins.isFunction raw
75 then raw ctx
76 else raw
77 );
78 childOptions =
79 if children == []
80 then {}
81 else mkOptions ctx (map (n: n // {path = lib.drop 1 n.path;}) children);
82 in
83 lib.mkOption {
84 type = lib.types.submodule (ownModule // {options = (ownModule.options or {}) // childOptions;});
85 default = {};
86 description = "Settings for aspect `${name}'.";
87 }
88 )
89 grouped;
90
91 hostNodes = map (n: {
92 path = n.prefix;
93 decls = n.host;
94 }) (lib.filter (n: n.host != {}) collected);
95
96 userNodes = map (n: {
97 path = n.prefix;
98 decls = n.user;
99 }) (lib.filter (n: n.user != {}) collected);
100
101 userSettingsType = ctx:
102 lib.types.submodule {
103 # The shared registry may carry keys outside `settings`.
104 freeformType = lib.types.attrsOf lib.types.anything;
105 options.settings = mkOptions ctx userNodes;
106 };
107
108 # `den.users.<username>` values merge as the lowest-priority definition.
109 registryAware = inner:
110 lib.mkOptionType {
111 name = "den-shared-user-system-settings";
112 check = v: builtins.isAttrs v;
113 merge = loc: defs: let
114 username = lib.elemAt loc (builtins.length loc - 2);
115 shared = config.den.users.${username}.system or {};
116 in
117 inner.merge loc ([
118 {
119 file = "<den.users>";
120 value = shared;
121 }
122 ]
123 ++ defs);
124 };
125in {
126 config = {
127 den.reservedKeys = ["settings"];
128
129 # Built per instance through imports so function declarations receive entity.
130 den.schema.host.imports = [
131 ({host, ...}: {
132 options.settings = lib.mkOption {
133 type = lib.types.submodule {
134 options = mkOptions {inherit host;} hostNodes;
135 };
136 default = {};
137 description = "Host-provided settings for den aspects.";
138 };
139 })
140 ];
141
142 den.schema.user.imports = [
143 ({
144 host,
145 user,
146 ...
147 }: {
148 options.system = lib.mkOption {
149 type = registryAware (userSettingsType {inherit host user;});
150 default = {};
151 description = "User-provided settings for den aspects.";
152 };
153 })
154 ];
155 };
156}