Reuse merge code to merge inner values of container option types
In particular, this allows use of properties on individual elements of a container type. For example, fileSystems."/" = mkMerge [ a b ]; is now legal. Also, checking the type of the inner value is now handled by the merge code, reducing duplication.
This commit is contained in:
parent
f1a707fc74
commit
33443d15e5
|
@ -106,12 +106,9 @@ rec {
|
|||
else []
|
||||
) configs);
|
||||
nrOptions = count (m: isOption m.options) decls;
|
||||
# Process mkMerge and mkIf properties.
|
||||
defns' = concatMap (m:
|
||||
if hasAttr name m.config
|
||||
then map (m': { inherit (m) file; value = m'; }) (dischargeProperties (getAttr name m.config))
|
||||
else []
|
||||
) configs;
|
||||
# Extract the definitions for this loc
|
||||
defns' = map (m: { inherit (m) file; value = getAttr name m.config; })
|
||||
(filter (m: hasAttr name m.config) configs);
|
||||
in
|
||||
if nrOptions == length decls then
|
||||
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
||||
|
@ -153,21 +150,15 @@ rec {
|
|||
config value. */
|
||||
evalOptionValue = loc: opt: defs:
|
||||
let
|
||||
# Process mkOverride properties, adding in the default
|
||||
# value specified in the option declaration (if any).
|
||||
defsFinal = filterOverrides
|
||||
((if opt ? default then [{ file = head opt.declarations; value = mkOptionDefault opt.default; }] else []) ++ defs);
|
||||
files = map (def: def.file) defsFinal;
|
||||
# Type-check the remaining definitions, and merge them if
|
||||
# possible.
|
||||
# Add in the default value specified in the option declaration
|
||||
defsWithDefault = (optional (opt ? default)
|
||||
{ file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
|
||||
inherit (mergeDefinitions loc opt.type defsWithDefault) defsFinal mergedValue;
|
||||
merged =
|
||||
if defsFinal == [] then
|
||||
throw "The option `${showOption loc}' is used but not defined."
|
||||
else
|
||||
fold (def: res:
|
||||
if opt.type.check def.value then res
|
||||
else throw "The option value `${showOption loc}' in `${def.file}' is not a ${opt.type.name}.")
|
||||
(opt.type.merge loc defsFinal) defsFinal;
|
||||
mergedValue;
|
||||
# Finally, apply the ‘apply’ function to the merged
|
||||
# value. This allows options to yield a value computed
|
||||
# from the definitions.
|
||||
|
@ -176,9 +167,23 @@ rec {
|
|||
{ value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
definitions = map (def: def.value) defsFinal;
|
||||
isDefined = defsFinal != [];
|
||||
inherit files;
|
||||
files = map (def: def.file) defsFinal;
|
||||
};
|
||||
|
||||
# Merge definitions of a value of a given type
|
||||
mergeDefinitions = loc: type: defs: rec {
|
||||
# Process mkOverride, mkIf, and mkMerge properties
|
||||
defsFinal = filterOverrides (concatMap (m:
|
||||
map (value: { inherit (m) file; inherit value; }) (dischargeProperties m.value)
|
||||
) defs);
|
||||
|
||||
# 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;
|
||||
};
|
||||
|
||||
/* Given a config set, expand mkMerge properties, and push down the
|
||||
mkIf properties into the children. The result is a list of
|
||||
config sets that do not have properties at top-level. For
|
||||
|
|
|
@ -7,7 +7,11 @@ with import ./options.nix;
|
|||
with import ./trivial.nix;
|
||||
with import ./strings.nix;
|
||||
|
||||
rec {
|
||||
let
|
||||
inherit (import ./modules.nix) mergeDefinitions evalModules;
|
||||
|
||||
innerMerge = loc: type: defs: (mergeDefinitions loc type defs).mergedValue;
|
||||
in rec {
|
||||
|
||||
isType = type: x: (x._type or "") == type;
|
||||
|
||||
|
@ -104,19 +108,19 @@ rec {
|
|||
|
||||
listOf = elemType: mkOptionType {
|
||||
name = "list of ${elemType.name}s";
|
||||
check = value: isList value && all elemType.check value;
|
||||
check = isList;
|
||||
merge = loc: defs:
|
||||
concatLists (imap (n: def: imap (m: def':
|
||||
elemType.merge (loc ++ ["[${toString n}-${toString m}]"])
|
||||
innerMerge (loc ++ ["[${toString n}-${toString m}]"]) elemType
|
||||
[{ inherit (def) file; value = def'; }]) def.value) defs);
|
||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||||
};
|
||||
|
||||
attrsOf = elemType: mkOptionType {
|
||||
name = "attribute set of ${elemType.name}s";
|
||||
check = x: isAttrs x && all elemType.check (attrValues x);
|
||||
check = isAttrs;
|
||||
merge = loc: defs:
|
||||
zipAttrsWith (name: elemType.merge (loc ++ [name]))
|
||||
zipAttrsWith (name: innerMerge (loc ++ [name]) elemType)
|
||||
# Push down position info.
|
||||
(map (def: listToAttrs (mapAttrsToList (n: def':
|
||||
{ name = n; value = { inherit (def) file; value = def'; }; }) def.value)) defs);
|
||||
|
@ -141,10 +145,7 @@ rec {
|
|||
attrOnly = attrsOf elemType;
|
||||
in mkOptionType {
|
||||
name = "list or attribute set of ${elemType.name}s";
|
||||
check = x:
|
||||
if isList x then listOnly.check x
|
||||
else if isAttrs x then attrOnly.check x
|
||||
else false;
|
||||
check = x: isList x || isAttrs x;
|
||||
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
||||
};
|
||||
|
@ -171,14 +172,13 @@ rec {
|
|||
name = "function that evaluates to a(n) ${elemType.name}";
|
||||
check = isFunction;
|
||||
merge = loc: defs:
|
||||
fnArgs: elemType.merge loc (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs);
|
||||
fnArgs: innerMerge loc elemType (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs);
|
||||
getSubOptions = elemType.getSubOptions;
|
||||
};
|
||||
|
||||
submodule = opts:
|
||||
let
|
||||
opts' = toList opts;
|
||||
inherit (import ./modules.nix) evalModules;
|
||||
in
|
||||
mkOptionType rec {
|
||||
name = "submodule";
|
||||
|
|
Loading…
Reference in New Issue
Block a user