WIP: feat: Namespaced includes #3
|
@ -168,12 +168,69 @@ lib: {
|
||||||
let
|
let
|
||||||
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
|
invalid = builtins.removeAttrs module lib.modules.VALID_KEYS;
|
||||||
invalidKeys = builtins.concatStringsSep ", " (builtins.attrNames invalid);
|
invalidKeys = builtins.concatStringsSep ", " (builtins.attrNames invalid);
|
||||||
|
|
||||||
|
__key__ = builtins.toString module.__key__ or key;
|
||||||
|
|
||||||
|
normalizeIncludes =
|
||||||
|
includes:
|
||||||
|
let
|
||||||
|
flattened = builtins.concatMap (
|
||||||
|
include:
|
||||||
|
if
|
||||||
|
builtins.isAttrs include
|
||||||
austreelis marked this conversation as resolved
|
|||||||
|
&& include != { }
|
||||||
|
&& !builtins.any (n: builtins.elem n lib.modules.VALID_KEYS) (builtins.attrNames include)
|
||||||
|
then
|
||||||
|
lib.attrs.mapToList (namespace: module: {
|
||||||
|
inherit namespace;
|
||||||
|
include = module;
|
||||||
|
}) include
|
||||||
|
else
|
||||||
|
[
|
||||||
|
{
|
||||||
|
inherit include;
|
||||||
|
namespace = null;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
) (lib.lists.when (includes != { }) includes);
|
||||||
|
|
||||||
|
throwOnConflict =
|
||||||
|
let
|
||||||
|
filter =
|
||||||
|
namespaces:
|
||||||
|
{ namespace, include }:
|
||||||
|
if namespace != null && builtins.elem namespace namespaces then
|
||||||
|
builtins.throw "Module ${key} declares several includes under the same namespace '${namespace}'"
|
||||||
|
else
|
||||||
|
namespaces ++ [ namespace ];
|
||||||
|
in
|
||||||
|
builtins.foldl' filter [ ] flattened;
|
||||||
austreelis marked this conversation as resolved
jakehamilton
commented
nit: maybe a name like nit: maybe a name like `createNamespacedModule` would be informative here?
|
|||||||
|
|
||||||
|
doNamespace =
|
||||||
|
{ namespace, include }:
|
||||||
|
if namespace == null then
|
||||||
|
include
|
||||||
|
else
|
||||||
|
{
|
||||||
|
__key__ = "${__key__}:include-${namespace}";
|
||||||
|
options.${namespace} = lib.options.create {
|
||||||
|
description = "options and configuration included from ${namespace}";
|
||||||
|
default.value = { };
|
||||||
|
type = lib.types.submodules.of {
|
||||||
|
modules = [ include ];
|
||||||
|
description = "include ${namespace}";
|
||||||
|
};
|
||||||
austreelis marked this conversation as resolved
jakehamilton
commented
Is this comment needed? Is this comment needed?
austreelis
commented
No, I thought I left it out when cleaning up my patch. No, I thought I left it out when cleaning up my patch.
|
|||||||
|
};
|
||||||
|
# config.${namespace} = lib.modules.override 1000 {};
|
||||||
|
};
|
||||||
austreelis marked this conversation as resolved
jakehamilton
commented
Can we extract the Can we extract the `builtins.map doNamespace flattened` bit to a variable so that this value is easier to read? Something like `builtins.seq throwOnConflict namespacedModules` might make it easier to understand.
|
|||||||
|
in
|
||||||
|
builtins.seq throwOnConflict builtins.map doNamespace flattened;
|
||||||
in
|
in
|
||||||
if lib.modules.validate.keys module then
|
if lib.modules.validate.keys module then
|
||||||
{
|
{
|
||||||
|
inherit __key__;
|
||||||
austreelis marked this conversation as resolved
jakehamilton
commented
Can we wrap usage of Can we wrap usage of `or` in parens here? It can be a bit confusing otherwise.
|
|||||||
__file__ = builtins.toString module.__file__ or file;
|
__file__ = builtins.toString module.__file__ or file;
|
||||||
__key__ = builtins.toString module.__key__ or key;
|
includes = normalizeIncludes module.includes or [ ];
|
||||||
includes = module.includes or [ ];
|
|
||||||
excludes = module.excludes or [ ];
|
excludes = module.excludes or [ ];
|
||||||
options = module.options or { };
|
options = module.options or { };
|
||||||
config =
|
config =
|
||||||
|
|
|
@ -236,6 +236,81 @@ in
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
actual == expected;
|
actual == expected;
|
||||||
|
|
||||||
|
"includes" = {
|
||||||
|
|
||||||
|
"handles an empty list" =
|
||||||
|
let
|
||||||
|
expected = [ ];
|
||||||
|
actual = (lib.modules.normalize "/aux/example.nix" "example" { includes = [ ]; }).includes;
|
||||||
|
in
|
||||||
|
expected == actual;
|
||||||
|
|
||||||
|
"handles an empty set" =
|
||||||
|
let
|
||||||
|
expected = [ ];
|
||||||
|
actual = (lib.modules.normalize "/aux/example.nix" "example" { includes = { }; }).includes;
|
||||||
|
in
|
||||||
|
expected == actual;
|
||||||
|
|
||||||
|
"handles a mixed list" =
|
||||||
|
let
|
||||||
|
expected = [
|
||||||
|
{ }
|
||||||
|
{ a = null; }
|
||||||
|
];
|
||||||
|
actual =
|
||||||
|
# Because includes leverage submodules, we can't match the actual
|
||||||
|
# included namespaced submodule under "a". So we just assert the
|
||||||
|
# namespace was gotten right and do not evaluate the included value.
|
||||||
|
builtins.map (include: builtins.mapAttrs (_: _: null) include.options or include)
|
||||||
|
(lib.modules.normalize "/aux/example.nix" "example" {
|
||||||
|
includes = [
|
||||||
|
{ }
|
||||||
|
{ a = null; }
|
||||||
|
];
|
||||||
|
}).includes;
|
||||||
|
in
|
||||||
|
expected == actual;
|
||||||
|
|
||||||
|
"rejects conflicting namespaces" =
|
||||||
|
let
|
||||||
|
normalized = lib.modules.normalize "/aux/example.nix" "example" {
|
||||||
|
includes = [
|
||||||
|
{ a = { }; }
|
||||||
|
{ a = { }; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
!(builtins.tryEval normalized.includes).success;
|
||||||
|
|
||||||
|
"allows multiple without namespace" =
|
||||||
|
let
|
||||||
|
normalized = lib.modules.normalize "/aux/example.nix" "example" {
|
||||||
|
includes = [
|
||||||
|
{ }
|
||||||
|
{ }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
(builtins.tryEval normalized.includes).success;
|
||||||
|
|
||||||
|
"handles multiple without namespace" =
|
||||||
|
let
|
||||||
|
expected = [
|
||||||
|
{ }
|
||||||
|
{ }
|
||||||
|
];
|
||||||
|
actual =
|
||||||
|
(lib.modules.normalize "/aux/example.nix" "example" {
|
||||||
|
includes = [
|
||||||
|
{ }
|
||||||
|
{ }
|
||||||
|
];
|
||||||
|
}).includes;
|
||||||
|
in
|
||||||
|
expected == actual;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
"resolve" = {
|
"resolve" = {
|
||||||
|
@ -699,5 +774,29 @@ in
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
(evaluated.config.aux.message == evaluated.config.aux.message2) && evaluated.config.aux.exists;
|
(evaluated.config.aux.message == evaluated.config.aux.message2) && evaluated.config.aux.exists;
|
||||||
|
|
||||||
|
"namespaced includes" =
|
||||||
|
let
|
||||||
|
expected = {
|
||||||
|
msg = "hello";
|
||||||
|
myinclude.msg = "hello";
|
||||||
|
};
|
||||||
|
evaluated = lib.modules.run {
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
includes = [
|
||||||
|
{
|
||||||
|
myinclude = {
|
||||||
|
options.msg = lib.options.create { default.value = "hello"; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{ options.msg = lib.options.create { default.value = "hello"; }; }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
actual = evaluated.config;
|
||||||
|
in
|
||||||
|
expected == actual;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue
There may be issues with this approach if we end up doing shorthand syntax for all modules. I don't think that we will, but submodules do support it for convenience. I wonder if we can have a different kind of object used for namespaces includes. What about something like:
At the very least we can switch on whether
include ? namespace
is a thing.