···1-# den.ctx — Declarative context definitions.
2-#
3-# A context is an attribute set whose attrNames (not values) determine
4-# which parametric functions match. den.ctx provides named context
5-# types with declarative transformations and aspect lookup.
6-#
7-# Named contexts carry semantic meaning beyond their structure.
8-# ctx.host { host } and ctx.hm-host { host } hold the same data,
9-# but hm-host guarantees home-manager support was validated —
10-# following transform-don't-validate: you cannot obtain an hm-host
11-# unless all detection criteria passed.
12-#
13-# Context types are independent of NixOS. Den can be used as a library
14-# for any domain configurable through Nix (cloud infra, containers,
15-# helm charts, etc). The OS framework is one implementation on top of
16-# this context+aspects library.
17-#
18-# Shape of a context definition:
19-#
20-# den.ctx.foobar = {
21-# desc = "The {foo,bar} context";
22-# conf = { foo, bar }: den.aspects.${foo}._.${bar};
23-# includes = [ <parametric aspects for this context> ];
24-# into = {
25-# baz = { foo, bar }: lib.singleton { baz = 22; };
26-# };
27-# };
28-#
29-# A context type is callable (it is a functor):
30-#
31-# aspect = den.ctx.foobar { foo = "hello"; bar = "world"; };
32-#
33-# ctxApply produces an aspect that includes: owned configs from the
34-# context type itself, the located aspect via conf, and all recursive
35-# transformations via into.
36-#
37-# Transformations have type: source -> [ target ]. This allows fan-out
38-# (one host producing many { host, user } pairs via map) and conditional
39-# propagation (lib.optional for detection gates like hm-host).
40-#
41-# den.default is an alias for den.ctx.default. Every context type
42-# transforms into default, so den.default.includes runs at every
43-# pipeline stage. Use take.exactly to restrict matching.
44-#
45-# See os.nix, defaults.nix and provides/home-manager/ for built-in
46-# context types.
47{ den, lib, ... }:
48let
49- inherit (den.lib) parametric take;
50 inherit (den.lib.aspects.types) providerType;
5152- ctxType = lib.types.submodule {
53- freeformType = lib.types.lazyAttrsOf lib.types.deferredModule;
54- options = {
55- desc = lib.mkOption {
56- description = "Context description";
57- type = lib.types.str;
58- default = "";
59- };
60- conf = lib.mkOption {
61- description = "Obtain a configuration aspect for context";
62- type = lib.types.functionTo providerType;
63- default = { };
64- };
65- into = lib.mkOption {
66- description = "Context transformations";
67- type = lib.types.lazyAttrsOf (lib.types.functionTo (lib.types.listOf lib.types.raw));
68- default = { };
69- };
70- includes = lib.mkOption {
71- description = "List of parametric aspects to include for this context";
72- type = lib.types.listOf providerType;
73- default = [ ];
74- };
75- __functor = lib.mkOption {
76- description = "Apply context. Returns a aspect with all dependencies.";
77- type = lib.types.functionTo (lib.types.functionTo providerType);
78- readOnly = true;
79- internal = true;
80- visible = false;
81- default = ctxApply;
0000000082 };
83- };
84- };
8586 cleanCtx =
87 ctx:
88 builtins.removeAttrs ctx [
089 "desc"
90 "conf"
91 "into"
92 "__functor"
93 ];
9495- # Given a context, returns an aspect that also includes
96- # the result of all context propagation.
97- ctxApply =
98 self: ctx:
0000000000099 let
100- myself = parametric.fixedTo ctx (cleanCtx self);
101- located = self.conf ctx;
102- adapted = lib.mapAttrsToList (name: into: map den.ctx.${name} (into ctx)) self.into;
00000000000000000000000000103 in
104- {
105- includes = lib.flatten [
106- myself
107- located
108- adapted
109- ];
110- };
111112in
113{
···4 # This test uses the `funny.names` test option to
5 # demostrate different places and context-aspects that
6 # can contribute configurations to the host.
7- #
8- # Note that both host and user aspects include default
9- # and because of that, default owned and static values
10- # might be duplicated. This is why users are NOT adviced
11- # to abuse den.default.
12- #
13- # The behaviour is correct, but using den.default for
14- # everything without care will cause deplication problems.
15- # Instead, users should include either directly on the host
16- # or by using `den.ctx.host` or `den.ctx.user`.
17 flake.tests.ctx-transformation.test-host = denTest (
18 {
19 den,
···161162 expected = [
163 "default-anyctx {aspect-chain,class}"
164- "default-anyctx {aspect-chain,class}"
165 "default-anyctx {host,user}"
166 "default-anyctx {host}"
167···170 "default-host-lax {host}"
171172 "default-owned"
173- "default-owned"
174175- "default-static"
176 "default-static"
177178 "default-user-lax {host,user}"
···4 # This test uses the `funny.names` test option to
5 # demostrate different places and context-aspects that
6 # can contribute configurations to the host.
00000000007 flake.tests.ctx-transformation.test-host = denTest (
8 {
9 den,
···151152 expected = [
153 "default-anyctx {aspect-chain,class}"
0154 "default-anyctx {host,user}"
155 "default-anyctx {host}"
156···159 "default-host-lax {host}"
160161 "default-owned"
01620163 "default-static"
164165 "default-user-lax {host,user}"