-
-
Notifications
You must be signed in to change notification settings - Fork 6
allow setting aspect._file or generate one for better module location. #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| { lib, mkFlake, ... }: | ||
| { | ||
| flake.tests."test _file on simple aspect" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects.aspectOne.classOne.bar = [ "x" ]; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| in | ||
| { | ||
| expr = mod._file; | ||
| expected = "flake.aspects:aspectOne.classOne"; | ||
| }; | ||
|
|
||
| flake.tests."test _file overriden" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects.aspectOne = { | ||
| _file = "foo"; | ||
| classOne.bar = [ "x" ]; | ||
| }; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| in | ||
| { | ||
| expr = mod._file; | ||
| expected = "foo"; | ||
| }; | ||
|
|
||
| flake.tests."test _file respects custom aspect name" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects.aspectOne = { | ||
| name = "my-one"; | ||
| classOne.bar = [ "x" ]; | ||
| }; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| in | ||
| { | ||
| expr = mod._file; | ||
| expected = "flake.aspects:my-one.classOne"; | ||
| }; | ||
|
|
||
| flake.tests."test _file on named include has index" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects = | ||
| { aspects, ... }: | ||
| { | ||
| aspectOne = { | ||
| classOne.bar = [ "one" ]; | ||
| includes = [ aspects.aspectTwo ]; | ||
| }; | ||
| aspectTwo.classOne.bar = [ "two" ]; | ||
| }; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| include0 = lib.elemAt mod.imports 1; | ||
| in | ||
| { | ||
| expr = include0._file; | ||
| expected = "flake.aspects:aspectOne.includes[0].aspectTwo.classOne"; | ||
| }; | ||
|
|
||
| flake.tests."test _file on second include has index 1" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects = | ||
| { aspects, ... }: | ||
| { | ||
| aspectOne = { | ||
| classOne.bar = [ "one" ]; | ||
| includes = [ | ||
| aspects.aspectTwo | ||
| aspects.aspectThree | ||
| ]; | ||
| }; | ||
| aspectTwo.classOne.bar = [ "two" ]; | ||
| aspectThree.classOne.bar = [ "three" ]; | ||
| }; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| include1 = lib.elemAt mod.imports 2; | ||
| in | ||
| { | ||
| expr = include1._file; | ||
| expected = "flake.aspects:aspectOne.includes[1].aspectThree.classOne"; | ||
| }; | ||
|
|
||
| flake.tests."test _file on class config wrapper matches root" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects.aspectOne.classOne.bar = [ "x" ]; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| classConfigWrapper = lib.head mod.imports; | ||
| in | ||
| { | ||
| expr = classConfigWrapper._file; | ||
| expected = "flake.aspects:aspectOne.classOne"; | ||
| }; | ||
|
|
||
| flake.tests."test _file on anonymous function include is not function body" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects.aspectOne = { | ||
| classOne.bar = [ "x" ]; | ||
| includes = [ | ||
| ( | ||
| { ... }: | ||
| { | ||
| classOne.bar = [ "from-fn" ]; | ||
| } | ||
| ) | ||
| ]; | ||
| }; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| include0 = lib.elemAt mod.imports 1; | ||
| in | ||
| { | ||
| expr = lib.hasPrefix "flake.aspects:aspectOne.includes[0]." include0._file; | ||
| expected = true; | ||
| }; | ||
|
|
||
| flake.tests."test _file on deeply nested includes" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects = | ||
| { aspects, ... }: | ||
| { | ||
| aspectOne = { | ||
| classOne.bar = [ "one" ]; | ||
| includes = [ aspects.aspectTwo ]; | ||
| }; | ||
| aspectTwo = { | ||
| classOne.bar = [ "two" ]; | ||
| includes = [ aspects.aspectThree ]; | ||
| }; | ||
| aspectThree.classOne.bar = [ "three" ]; | ||
| }; | ||
| }; | ||
| modOne = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| modTwo = lib.elemAt modOne.imports 1; | ||
| modThree = lib.elemAt modTwo.imports 1; | ||
| in | ||
| { | ||
| expr = [ | ||
| modOne._file | ||
| modTwo._file | ||
| modThree._file | ||
| ]; | ||
| expected = [ | ||
| "flake.aspects:aspectOne.classOne" | ||
| "flake.aspects:aspectOne.includes[0].aspectTwo.classOne" | ||
| "flake.aspects:aspectOne.includes[0].aspectTwo.includes[0].aspectThree.classOne" | ||
| ]; | ||
| }; | ||
|
|
||
| flake.tests."test _file on provides include" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects = | ||
| { aspects, ... }: | ||
| { | ||
| aspectOne = { | ||
| classOne.bar = [ "one" ]; | ||
| includes = [ aspects.aspectTwo.provides.helper ]; | ||
| }; | ||
| aspectTwo.provides.helper = { | ||
| name = "helper"; | ||
| classOne.bar = [ "help" ]; | ||
| }; | ||
| }; | ||
| }; | ||
| mod = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| include0 = lib.elemAt mod.imports 1; | ||
| in | ||
| { | ||
| expr = include0._file; | ||
| expected = "flake.aspects:aspectOne.includes[0].helper.classOne"; | ||
| }; | ||
|
|
||
| flake.tests."test _file on deeply nested provides" = | ||
| let | ||
| flake = mkFlake { | ||
| flake.aspects = | ||
| { aspects, ... }: | ||
| { | ||
| aspectOne = { | ||
| classOne.bar = [ "one" ]; | ||
| includes = [ aspects.aspectTwo.provides.helper ]; | ||
| }; | ||
| aspectTwo.provides.helper = { | ||
| name = "helper"; | ||
| classOne.bar = [ "help" ]; | ||
| includes = [ aspects.aspectThree.provides.sub ]; | ||
| }; | ||
| aspectThree.provides.sub = { | ||
| name = "sub"; | ||
| classOne.bar = [ "sub" ]; | ||
| }; | ||
| }; | ||
| }; | ||
| modOne = flake.aspects.aspectOne.resolve { class = "classOne"; }; | ||
| helper = lib.elemAt modOne.imports 1; | ||
| sub = lib.elemAt helper.imports 1; | ||
| in | ||
| { | ||
| expr = [ | ||
| helper._file | ||
| sub._file | ||
| ]; | ||
| expected = [ | ||
| "flake.aspects:aspectOne.includes[0].helper.classOne" | ||
| "flake.aspects:aspectOne.includes[0].helper.includes[0].sub.classOne" | ||
| ]; | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,51 @@ | ||
| # Core aspect resolution algorithm | ||
| # Resolves aspect definitions into nixpkgs modules with dependency resolution | ||
|
|
||
| lib: | ||
| lib: namespace: | ||
| let | ||
| # Process a single provider: invoke with context and resolve | ||
| include = | ||
| class: aspect-chain: provider: | ||
| filePath = class: segments: "${namespace}:${lib.concatStringsSep "." segments}.${class}"; | ||
|
|
||
| build = | ||
| file: class: chain: segments: provided: | ||
| let | ||
| provided = provider { inherit aspect-chain class; }; | ||
| fileAttr = if provided ? _file then provided._file else null; | ||
| computedFile = if fileAttr == null then file else fileAttr; | ||
| in | ||
| resolve class aspect-chain provided; | ||
| { | ||
| _file = computedFile; | ||
| imports = lib.flatten [ | ||
| { | ||
| _file = computedFile; | ||
| imports = [ (provided.${class} or { }) ]; | ||
| } | ||
| (lib.imap0 (includeAt class chain segments) (provided.includes or [ ])) | ||
| ]; | ||
| }; | ||
|
|
||
| # Main resolution: extract class config and recursively resolve includes | ||
| resolve = class: aspect-chain: provided: { | ||
| imports = | ||
| let | ||
| config = provided.${class} or { }; | ||
| includes = provided.includes or [ ]; | ||
| in | ||
| lib.flatten [ | ||
| config | ||
| (lib.map (include class (aspect-chain ++ [ provided ])) includes) | ||
| includeAt = | ||
| class: chain: segments: idx: provider: | ||
| let | ||
| provided = provider { | ||
| aspect-chain = chain; | ||
| inherit class; | ||
| }; | ||
| chain' = chain ++ [ provided ]; | ||
| name = provided.name or "<anonymous>"; | ||
| segments' = segments ++ [ | ||
| "includes[${toString idx}]" | ||
| name | ||
| ]; | ||
| }; | ||
| file = filePath class segments'; | ||
| in | ||
| build file class chain' segments' provided; | ||
|
|
||
| resolve = | ||
| class: aspect-chain: provided: | ||
| let | ||
| chain = aspect-chain ++ [ provided ]; | ||
| segments = [ (provided.name or "<anonymous>") ]; | ||
| in | ||
| build (filePath class segments) class chain segments provided; | ||
|
|
||
| in | ||
| resolve |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| # Core type system for aspect-oriented configuration | ||
|
|
||
| lib: | ||
| lib: namespace: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about instead of passing namespace here, and causing API breakage, we use the _file option in the namespace itself. flake.aspects._file = "flake.aspects"; and this should be propagated to aspects inside of it. default _file is null, but users can override it per aspect or per sub-namespace (aka provides) |
||
| let | ||
| resolve = import ./resolve.nix lib; | ||
| resolve = import ./resolve.nix lib namespace; | ||
|
|
||
| # Type for computed values that only exist during evaluation | ||
| ignoredType = lib.types.mkOptionType { | ||
|
|
@@ -73,6 +73,12 @@ let | |
| type = lib.types.str; | ||
| }; | ||
|
|
||
| _file = lib.mkOption { | ||
| description = "Aspect file location"; | ||
| default = null; | ||
| type = lib.types.nullOr lib.types.str; | ||
| }; | ||
|
|
||
| description = lib.mkOption { | ||
| description = "Aspect description"; | ||
| default = "Aspect ${name}"; | ||
|
|
@@ -102,7 +108,7 @@ let | |
| visible = false; | ||
| description = "Functor to default provider"; | ||
| type = lib.types.functionTo providerType; | ||
| default = aspect: { class, aspect-chain }: if true || (class aspect-chain) then aspect else aspect; | ||
| default = aspect: { class, aspect-chain }: if true then aspect else class aspect-chain; | ||
| }; | ||
|
|
||
| modules = mkInternal "resolved modules from this aspect" ignoredType ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont like this! revert, see comment about not passing namespace string around.