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:
Shea Levy 2014-02-13 22:55:57 -05:00
parent 33443d15e5
commit 48f41cb90e
2 changed files with 49 additions and 8 deletions

View File

@ -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.

View File

@ -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 {