lib.modules.mapAttrsOfSubmodule: init

This commit is contained in:
Robert Hensing
2026-09-08 15:46:38 +02:00
parent 7974c6bb8c
commit c70d9ffe53
3 changed files with 152 additions and 0 deletions

View File

@@ -1601,6 +1601,78 @@ let
else
f def;
/**
Consume the `options` metadata of the submodules in `attrsOf submodule`.
It also works with `lazyAttrsOf` and `attrsWith`.
# Inputs
1. A function that takes `name: { cfg, opt }:` and returns anything.
`name` is the attribute name at the level of `attrsOf`.
`cfg` is the corresponding option *value*, as typically found in `config`.
`opt` is the corresponding evaluated option, as typically found in `options`.
2. An `options` attribute value, e.g. `options.users.groups`.
# Output
An attribute set whose attribute names correspond to the definitions of the
option, and whose values are the return value of the passed function.
# Type
```
mapAttrsOfSubmodule :: (String -> { cfg :: AttrSet, opt :: AttrSet } -> a) -> Option -> AttrSetOf a
```
`Option` refers to an evaluated option,
retrievable from the `options` module argument
or the `options` configuration attribute returned by `evalModules`.
It carries attribute `_type = "option";`.
# Example
:::{.example}
## Use `mapAttrsOfSubmodule` to distinguish which NixOS system services are explicitly enabled or disabled
```nix
lib.modules.mapAttrsOfSubmodule
(name: { cfg, opt }:
{
isExplicit = opt.enable.highestPrio < (lib.mkOptionDefault null).priority;
value = cfg.enable;
})
(pkgs.nixos { }).options.systemd.services
=>
{
console-getty = {
isExplicit = true;
value = false;
};
"container-getty@" = {
isExplicit = false;
value = true;
};
# ...
}
```
:::
*/
mapAttrsOfSubmodule =
f: opt:
assert opt._type or null == "option";
assert opt.type.name == "attrsOf" || opt.type.name == "lazyAttrsOf";
assert opt.type.nestedTypes.elemType.name == "submodule";
mapAttrs (
name: attrMeta:
f name {
cfg = attrMeta.configuration.config;
opt = attrMeta.configuration.options;
}
) opt.valueMeta.attrs;
mkBefore = mkOrder 500;
defaultOrderPriority = 1000;
mkAfter = mkOrder 1500;
@@ -2305,6 +2377,7 @@ private
importApply
importJSON
importTOML
mapAttrsOfSubmodule
mapDefinitionValue
mergeDefinitions
mergeAttrDefinitionsWithPrio

View File

@@ -859,6 +859,8 @@ checkConfigError 'the-defs-file\.nix' config.argv ./attrList-valueMeta-definitio
# attrListOf does not support type merging
checkConfigError 'The option .merged. in .*/declare-attrList-type-merge.nix. is already declared in .*/declare-attrList-type-merge.nix' config.merged ./declare-attrList-type-merge.nix
checkConfigOutput '"ok"' config.result ./mapAttrsOfSubmodule.nix
cat <<EOF
====== module tests ======
$pass Pass

View File

@@ -0,0 +1,77 @@
{
lib,
options,
...
}:
let
inherit (lib) types mkOption;
inherit (types)
submodule
attrsOf
lazyAttrsOf
attrsWith
;
def = (lib.mkDefault null).priority;
optdef = (lib.mkOptionDefault null).priority;
enabledModule = {
options.enable = mkOption { default = true; };
};
in
{
options.services = mkOption {
type = attrsOf (submodule enabledModule);
};
options.files = mkOption {
type = lazyAttrsOf (submodule enabledModule);
};
options.noDefs = mkOption {
type = attrsWith { elemType = submodule enabledModule; };
};
options.result = mkOption { };
config.services.empty = { };
config.services.default.enable = lib.mkDefault false;
config.files.empty = { };
config.files.default.enable = lib.mkDefault false;
config.files.quirk = lib.mkIf false { };
config.result =
assert
lib.modules.mapAttrsOfSubmodule (name: { cfg, opt }: {
inherit name cfg;
prio = opt.enable.highestPrio;
}) options.services == {
empty = {
name = "empty";
cfg.enable = true;
prio = optdef;
};
default = {
name = "default";
cfg.enable = false;
prio = def;
};
};
assert
lib.modules.mapAttrsOfSubmodule (name: { cfg, opt }: {
inherit name cfg;
prio = opt.enable.highestPrio;
}) options.files == {
empty = {
name = "empty";
cfg.enable = true;
prio = optdef;
};
default = {
name = "default";
cfg.enable = false;
prio = def;
};
quirk = {
name = "quirk";
cfg.enable = true; # unmitigated flaw; could add emptyValue to attrsWith
prio = optdef;
};
};
assert lib.modules.mapAttrsOfSubmodule (abort "unused") options.noDefs == { };
"ok";
}