diff --git a/nix/lib/can-take.nix b/nix/lib/can-take.nix index 99bb425..b12e567 100644 --- a/nix/lib/can-take.nix +++ b/nix/lib/can-take.nix @@ -15,5 +15,6 @@ in { __functor = self: self.atLeast; atLeast = params: func: (canTake params func).satisfied; + upTo = params: func: (canTake params func).satisfied; exactly = params: func: (canTake params func).exactly; } diff --git a/nix/lib/default.nix b/nix/lib/default.nix index 101373c..f50a005 100644 --- a/nix/lib/default.nix +++ b/nix/lib/default.nix @@ -23,6 +23,8 @@ let ctxApply = ./ctx-apply.nix; ctxTypes = ./ctx-types.nix; __findFile = ./den-brackets.nix; + recursiveFunctor = ./recursive-functor.nix; + fwTypes = ./types.nix; forward = ./forward.nix; home-env = ./home-env.nix; nh = ./nh.nix; diff --git a/nix/lib/parametric.nix b/nix/lib/parametric.nix index cdbdc98..2ace809 100644 --- a/nix/lib/parametric.nix +++ b/nix/lib/parametric.nix @@ -1,6 +1,6 @@ { lib, den, ... }: let - inherit (den.lib) take; + inherit (den.lib) take recursiveFunctor; inherit (den.lib.statics) owned statics isCtxStatic; parametric.applyIncludes = @@ -44,7 +44,11 @@ let }; }; - parametric.fixedTo = parametric.deep parametric.atLeast; + parametric.fixedTo = parametric.deep parametric.atLeast // { + atLeast = recursiveFunctor (lib.flip take.atLeast); + exactly = recursiveFunctor (lib.flip take.exactly); + upTo = recursiveFunctor (lib.flip take.upTo); + }; parametric.withOwn = functor: aspect: diff --git a/nix/lib/recursive-functor.nix b/nix/lib/recursive-functor.nix new file mode 100644 index 0000000..81dd956 --- /dev/null +++ b/nix/lib/recursive-functor.nix @@ -0,0 +1,21 @@ +# "Just Give 'Em One of These" - Moe Szyslak +# A __functor that applies context to parametric includes (functions) and recurses into other included aspects +{ lib, ... }: +let + recursiveApply = + apply: ctx: include: + if include ? includes then recursiveFunctor apply include ctx else apply ctx include; + recursiveFunctor = + apply: aspect: + aspect + // { + __functor = self: ctx: { + includes = + self.includes or [ ] + |> builtins.filter lib.isFunction + |> map (recursiveApply apply ctx) + |> builtins.filter (x: x != { }); + }; + }; +in +recursiveFunctor diff --git a/nix/lib/take.nix b/nix/lib/take.nix index be7ed21..cef8459 100644 --- a/nix/lib/take.nix +++ b/nix/lib/take.nix @@ -1,10 +1,11 @@ -{ den, ... }: +{ den, lib, ... }: let take.unused = _unused: used: used; - take.exactly = take den.lib.canTake.exactly; - take.atLeast = take den.lib.canTake.atLeast; + take.exactly = take (_fn: ctx: ctx) den.lib.canTake.exactly; + take.atLeast = take (_fn: ctx: ctx) den.lib.canTake.atLeast; + take.upTo = take (fn: fn |> lib.functionsArgs |> builtins.intersectAttrs) den.lib.canTake.upTo; take.__functor = - _: takes: fn: ctx: - if takes ctx fn then fn ctx else { }; + _: takes: adapter: fn: ctx: + if takes ctx fn then fn (adapter fn ctx) else { }; in take diff --git a/templates/ci/modules/features/parametric.nix b/templates/ci/modules/features/parametric.nix index c99b4c6..5ecbfd1 100644 --- a/templates/ci/modules/features/parametric.nix +++ b/templates/ci/modules/features/parametric.nix @@ -1,9 +1,12 @@ { denTest, ... }: { flake.tests.parametric = { - test-parametric-forwards-context = denTest ( - { den, igloo, ... }: + { + den, + igloo, + ... + }: let foo = den.lib.parametric { includes = [ @@ -26,7 +29,11 @@ ); test-parametric-owned-config = denTest ( - { den, igloo, ... }: + { + den, + igloo, + ... + }: let foo = den.lib.parametric { nixos.networking.hostName = "from-parametric-owned"; @@ -43,7 +50,11 @@ ); test-parametric-fixedTo = denTest ( - { den, igloo, ... }: + { + den, + igloo, + ... + }: let foo = { host, ... }: @@ -68,12 +79,20 @@ ); test-parametric-expands = denTest ( - { den, igloo, ... }: + { + den, + igloo, + ... + }: let foo = den.lib.parametric.expands { planet = "Earth"; } { includes = [ ( - { host, planet, ... }: + { + host, + planet, + ... + }: { nixos.users.users.tux.description = "${host.name}/${planet}"; } @@ -91,7 +110,11 @@ ); test-never-matches-aspect-skipped = denTest ( - { den, igloo, ... }: + { + den, + igloo, + ... + }: let never-matches = { never-exists, ... }: @@ -113,5 +136,145 @@ } ); + test-parametric-fixedTo-atLeast = denTest ( + { + den, + lib, + inputs, + ... + }: + let + inherit (den.lib.parametric) fixedTo; + testAspect = name: include: { + nixos.test = [ "excluded-owned-${name}" ]; + + _.host = + { host }: + { + nixos.test = [ "${host}-${name}" ]; + }; + + _.host-user = + { host, user }: + { + nixos.test = [ "${host}-${user}-${name}" ]; + }; + + _.static = + { class, ... }: + { + ${class}.test = [ "excluded-static-${name}" ]; + }; + + includes = include ++ [ + den.aspects.${name}._.host + den.aspects.${name}._.host-user + den.aspects.${name}._.static + ]; + }; + testOptionProvider = args: aspect: { + includes = [ + aspect + { + __functor = self: _: { + nixos.options.test = lib.mkOption { type = lib.types.listOf lib.types.str; }; + }; + __functionArgs = + args + |> map (arg: { + name = arg; + value = false; + }) + |> builtins.listToAttrs; + } + ]; + }; + in + { + den.aspects.inner = testAspect "inner" [ ]; + den.aspects.outer = testAspect "outer" [ den.aspects.inner ]; + + expr = + { + exactlyHost = { + ctx = { + host = "igloo"; + }; + functor = fixedTo.exactly; + }; + exactlyHostUser = { + ctx = { + host = "igloo"; + user = "tux"; + }; + functor = fixedTo.exactly; + }; + upToHost = { + ctx = { + host = "igloo"; + }; + functor = fixedTo.upTo; + }; + upToHostUser = { + ctx = { + host = "igloo"; + user = "tux"; + }; + functor = fixedTo.upTo; + }; + atLeastHost = { + ctx = { + host = "igloo"; + }; + functor = fixedTo.atLeast; + }; + # This test case errors because atLeast tries to call { host }: with { host, user } causing an error + # this is IMO incorrect behaviour, but would technically be a breaking change if people are using + # args@{ host, ... } which is why I introduced a new kind "upTo" which uses the canTake.atLeast + # predicate but only calls the function with the attributes it expects + # atLeastHostUser = { + # ctx = { + # host = "igloo"; + # user = "tux"; + # }; + # parametricFunctor = atLeast.fixed; + # }; + } + |> lib.mapAttrs ( + _: test: + den.aspects.outer + |> testOptionProvider (builtins.attrNames test.ctx) + |> (lib.flip test.functor) test.ctx + |> den.lib.aspects.resolve "nixos" [ ] + |> (nixos: inputs.nixpkgs.lib.evalModules { modules = [ nixos ]; }) + |> (x: x.config.test) + ); + + expected = { + atLeastHost = [ + "igloo-inner" + "igloo-outer" + ]; + exactlyHost = [ + "igloo-inner" + "igloo-outer" + ]; + exactlyHostUser = [ + "igloo-tux-inner" + "igloo-tux-outer" + ]; + upToHost = [ + "igloo-inner" + "igloo-outer" + ]; + upToHostUser = [ + "igloo-tux-inner" + "igloo-inner" + "igloo-tux-outer" + "igloo-outer" + ]; + }; + } + ); }; }