Add mkMap property to the module system
This allows specifying configuration that should be merged in with some or all elements of a container option (e.g. listOf, attrsOf) without needing to know whether those elements are actually defined elsewhere. For example, this will default the fsType of all filesystems except /boot to btrfs (and leaves /boot alone): fileSystems = mkMap (name: mkIf (name != "/boot") { fsType = mkDefault "btrfs"; }); mkMap takes a function which takes an index of an element and returns a value to be merged in with the other definitions for that element. For sets, the index is just the attribute name. For lists, the mkMap function is passed two arguments: The index of the relevant definition in the list of all definitions for that option, and the index of the element within that definition.
This commit is contained in:
parent
33443d15e5
commit
48f41cb90e
|
@ -165,6 +165,9 @@ rec {
|
|||
value = (opt.apply or id) merged;
|
||||
in opt //
|
||||
{ value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
# Note that definitions may contain undischarged mkMap properties,
|
||||
# as mkMap can only be discharged in the merge function of the
|
||||
# relevant (mappable) type.
|
||||
definitions = map (def: def.value) defsFinal;
|
||||
isDefined = defsFinal != [];
|
||||
files = map (def: def.file) defsFinal;
|
||||
|
@ -179,9 +182,12 @@ rec {
|
|||
|
||||
# Merge everything, but check that types match first
|
||||
mergedValue = fold (def: res:
|
||||
if type.check def.value then res
|
||||
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}.")
|
||||
(type.merge loc defsFinal) defsFinal;
|
||||
if def.value._type or "" == "map"
|
||||
then if type.mappable then res
|
||||
else throw "Option value `${showOption loc}' in `${def.file}' is a mkMap but ${type.name} is not mappable"
|
||||
else if type.check def.value then res
|
||||
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${type.name}."
|
||||
) (type.merge loc defsFinal) defsFinal;
|
||||
};
|
||||
|
||||
/* Given a config set, expand mkMerge properties, and push down the
|
||||
|
@ -307,6 +313,16 @@ rec {
|
|||
|
||||
mkFixStrictness = id; # obsolete, no-op
|
||||
|
||||
# Map a function, which takes an index ((defnNum, index) for lists, attrname
|
||||
# for sets, etc) and returns a value to be merged with the other values
|
||||
# defined for that index, over a collection. This can for example be used to
|
||||
# apply some config to *every* submodule in an attrsOf submodule without
|
||||
# needing to know which attrs are actually defined elsewhere
|
||||
mkMap = f:
|
||||
{ _type = "map";
|
||||
inherit f;
|
||||
};
|
||||
|
||||
# FIXME: Add mkOrder back in. It's not currently used anywhere in
|
||||
# NixOS, but it should be useful.
|
||||
|
||||
|
|
|
@ -37,9 +37,11 @@ in rec {
|
|||
, # Return a flat list of sub-options. Used to generate
|
||||
# documentation.
|
||||
getSubOptions ? prefix: {}
|
||||
, # A mappable type can handle mkMap defs when merging
|
||||
mappable ? false
|
||||
}:
|
||||
{ _type = "option-type";
|
||||
inherit name check merge getSubOptions;
|
||||
inherit name check merge getSubOptions mappable;
|
||||
};
|
||||
|
||||
|
||||
|
@ -110,21 +112,42 @@ in rec {
|
|||
name = "list of ${elemType.name}s";
|
||||
check = isList;
|
||||
merge = loc: defs:
|
||||
concatLists (imap (n: def: imap (m: def':
|
||||
let
|
||||
nonMaps = filter (d: d.value._type or "" != "map") defs;
|
||||
maps = filter (d: d.value._type or "" == "map") defs;
|
||||
mapResults = fold (m: imap (x: l: imap (y: l: l ++ [ {
|
||||
inherit (m) file;
|
||||
value = m.value.f x y;
|
||||
} ]) l))
|
||||
(map (d: map (x: []) d.value) nonMaps) maps;
|
||||
in concatLists (imap (n: def: imap (m: def':
|
||||
innerMerge (loc ++ ["[${toString n}-${toString m}]"]) elemType
|
||||
[{ inherit (def) file; value = def'; }]) def.value) defs);
|
||||
([{ inherit (def) file; value = def'; }] ++ elemAt (elemAt mapResults (n - 1)) (m - 1))
|
||||
) def.value) nonMaps);
|
||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||||
mappable = true;
|
||||
};
|
||||
|
||||
attrsOf = elemType: mkOptionType {
|
||||
name = "attribute set of ${elemType.name}s";
|
||||
check = isAttrs;
|
||||
merge = loc: defs:
|
||||
zipAttrsWith (name: innerMerge (loc ++ [name]) elemType)
|
||||
let
|
||||
nonMaps = filter (d: d.value._type or "" != "map") defs;
|
||||
maps = filter (d: d.value._type or "" == "map") defs;
|
||||
names = concatMap (d: attrNames d.value) nonMaps;
|
||||
mapResults = listToAttrs (map (name: {
|
||||
inherit name;
|
||||
value = map (m: { inherit (m) file; value = m.value.f name; }) maps;
|
||||
}) names);
|
||||
in zipAttrsWith (name: defs:
|
||||
innerMerge (loc ++ [name]) elemType (defs ++ getAttr name mapResults)
|
||||
)
|
||||
# Push down position info.
|
||||
(map (def: listToAttrs (mapAttrsToList (n: def':
|
||||
{ name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs);
|
||||
{ name = n; value = { inherit (def) file; value = def'; }; }) def.value)) nonMaps);
|
||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
||||
mappable = true;
|
||||
};
|
||||
|
||||
# List or attribute set of ...
|
||||
|
@ -148,6 +171,8 @@ in rec {
|
|||
check = x: isList x || isAttrs x;
|
||||
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
||||
# maps over the post-convertifList defs
|
||||
mappable = true;
|
||||
};
|
||||
|
||||
uniq = elemType: mkOptionType {
|
||||
|
|
Loading…
Reference in New Issue
Block a user