Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ccfc2e4415 | ||
![]() |
48f41cb90e | ||
![]() |
33443d15e5 |
|
@ -106,12 +106,9 @@ rec {
|
||||||
else []
|
else []
|
||||||
) configs);
|
) configs);
|
||||||
nrOptions = count (m: isOption m.options) decls;
|
nrOptions = count (m: isOption m.options) decls;
|
||||||
# Process mkMerge and mkIf properties.
|
# Extract the definitions for this loc
|
||||||
defns' = concatMap (m:
|
defns' = map (m: { inherit (m) file; value = getAttr name m.config; })
|
||||||
if hasAttr name m.config
|
(filter (m: hasAttr name m.config) configs);
|
||||||
then map (m': { inherit (m) file; value = m'; }) (dischargeProperties (getAttr name m.config))
|
|
||||||
else []
|
|
||||||
) configs;
|
|
||||||
in
|
in
|
||||||
if nrOptions == length decls then
|
if nrOptions == length decls then
|
||||||
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
let opt = fixupOptionType loc (mergeOptionDecls loc decls);
|
||||||
|
@ -153,32 +150,46 @@ rec {
|
||||||
config value. */
|
config value. */
|
||||||
evalOptionValue = loc: opt: defs:
|
evalOptionValue = loc: opt: defs:
|
||||||
let
|
let
|
||||||
# Process mkOverride properties, adding in the default
|
# Add in the default value specified in the option declaration
|
||||||
# value specified in the option declaration (if any).
|
defsWithDefault = (optional (opt ? default)
|
||||||
defsFinal = filterOverrides
|
{ file = head opt.declarations; value = mkOptionDefault opt.default; }) ++ defs;
|
||||||
((if opt ? default then [{ file = head opt.declarations; value = mkOptionDefault opt.default; }] else []) ++ defs);
|
inherit (mergeDefinitions loc opt.type defsWithDefault) defsFinal mergedValue;
|
||||||
files = map (def: def.file) defsFinal;
|
|
||||||
# Type-check the remaining definitions, and merge them if
|
|
||||||
# possible.
|
|
||||||
merged =
|
merged =
|
||||||
if defsFinal == [] then
|
if defsFinal == [] then
|
||||||
throw "The option `${showOption loc}' is used but not defined."
|
throw "The option `${showOption loc}' is used but not defined."
|
||||||
else
|
else
|
||||||
fold (def: res:
|
mergedValue;
|
||||||
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;
|
|
||||||
# Finally, apply the ‘apply’ function to the merged
|
# Finally, apply the ‘apply’ function to the merged
|
||||||
# value. This allows options to yield a value computed
|
# value. This allows options to yield a value computed
|
||||||
# from the definitions.
|
# from the definitions.
|
||||||
value = (opt.apply or id) merged;
|
value = (opt.apply or id) merged;
|
||||||
in opt //
|
in opt //
|
||||||
{ value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
{ 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;
|
definitions = map (def: def.value) defsFinal;
|
||||||
isDefined = 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 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
|
/* Given a config set, expand mkMerge properties, and push down the
|
||||||
mkIf properties into the children. The result is a list of
|
mkIf properties into the children. The result is a list of
|
||||||
config sets that do not have properties at top-level. For
|
config sets that do not have properties at top-level. For
|
||||||
|
@ -302,6 +313,16 @@ rec {
|
||||||
|
|
||||||
mkFixStrictness = id; # obsolete, no-op
|
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
|
# FIXME: Add mkOrder back in. It's not currently used anywhere in
|
||||||
# NixOS, but it should be useful.
|
# NixOS, but it should be useful.
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,13 @@ with import ./options.nix;
|
||||||
with import ./trivial.nix;
|
with import ./trivial.nix;
|
||||||
with import ./strings.nix;
|
with import ./strings.nix;
|
||||||
|
|
||||||
rec {
|
let
|
||||||
|
inherit (import ./modules.nix) mergeDefinitions evalModules;
|
||||||
|
|
||||||
|
innerMerge = loc: type: defs: let
|
||||||
|
inherit (mergeDefinitions loc type defs) mergedValue defsFinal;
|
||||||
|
in if defsFinal == [] then {} else { value = mergedValue; };
|
||||||
|
in rec {
|
||||||
|
|
||||||
isType = type: x: (x._type or "") == type;
|
isType = type: x: (x._type or "") == type;
|
||||||
|
|
||||||
|
@ -33,9 +39,11 @@ rec {
|
||||||
, # Return a flat list of sub-options. Used to generate
|
, # Return a flat list of sub-options. Used to generate
|
||||||
# documentation.
|
# documentation.
|
||||||
getSubOptions ? prefix: {}
|
getSubOptions ? prefix: {}
|
||||||
|
, # A mappable type can handle mkMap defs when merging
|
||||||
|
mappable ? false
|
||||||
}:
|
}:
|
||||||
{ _type = "option-type";
|
{ _type = "option-type";
|
||||||
inherit name check merge getSubOptions;
|
inherit name check merge getSubOptions mappable;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,23 +112,48 @@ rec {
|
||||||
|
|
||||||
listOf = elemType: mkOptionType {
|
listOf = elemType: mkOptionType {
|
||||||
name = "list of ${elemType.name}s";
|
name = "list of ${elemType.name}s";
|
||||||
check = value: isList value && all elemType.check value;
|
check = isList;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
concatLists (imap (n: def: imap (m: def':
|
let
|
||||||
elemType.merge (loc ++ ["[${toString n}-${toString m}]"])
|
nonMaps = filter (d: d.value._type or "" != "map") defs;
|
||||||
[{ inherit (def) file; value = def'; }]) def.value) 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 map (getAttr "value") (filter (hasAttr "value") (
|
||||||
|
concatLists (imap (n: def: imap (m: def':
|
||||||
|
innerMerge (loc ++ ["[${toString n}-${toString m}]"]) elemType
|
||||||
|
([{ inherit (def) file; value = def'; }] ++ elemAt (elemAt mapResults (n - 1)) (m - 1))
|
||||||
|
) def.value) nonMaps)
|
||||||
|
));
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||||||
|
mappable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
attrsOf = elemType: mkOptionType {
|
attrsOf = elemType: mkOptionType {
|
||||||
name = "attribute set of ${elemType.name}s";
|
name = "attribute set of ${elemType.name}s";
|
||||||
check = x: isAttrs x && all elemType.check (attrValues x);
|
check = isAttrs;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
zipAttrsWith (name: elemType.merge (loc ++ [name]))
|
let
|
||||||
# Push down position info.
|
nonMaps = filter (d: d.value._type or "" != "map") defs;
|
||||||
(map (def: listToAttrs (mapAttrsToList (n: def':
|
maps = filter (d: d.value._type or "" == "map") defs;
|
||||||
{ name = n; value = { inherit (def) file; value = def'; }; }) def.value)) 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 mapAttrs (n: getAttr "value") (filterAttrs (n: hasAttr "value") (
|
||||||
|
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)) nonMaps)
|
||||||
|
));
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
||||||
|
mappable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# List or attribute set of ...
|
# List or attribute set of ...
|
||||||
|
@ -141,12 +174,11 @@ rec {
|
||||||
attrOnly = attrsOf elemType;
|
attrOnly = attrsOf elemType;
|
||||||
in mkOptionType {
|
in mkOptionType {
|
||||||
name = "list or attribute set of ${elemType.name}s";
|
name = "list or attribute set of ${elemType.name}s";
|
||||||
check = x:
|
check = x: isList x || isAttrs x;
|
||||||
if isList x then listOnly.check x
|
|
||||||
else if isAttrs x then attrOnly.check x
|
|
||||||
else false;
|
|
||||||
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
||||||
|
# maps over the post-convertifList defs
|
||||||
|
mappable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniq = elemType: mkOptionType {
|
uniq = elemType: mkOptionType {
|
||||||
|
@ -170,15 +202,17 @@ rec {
|
||||||
functionTo = elemType: mkOptionType {
|
functionTo = elemType: mkOptionType {
|
||||||
name = "function that evaluates to a(n) ${elemType.name}";
|
name = "function that evaluates to a(n) ${elemType.name}";
|
||||||
check = isFunction;
|
check = isFunction;
|
||||||
merge = loc: defs:
|
merge = loc: defs: fnArgs:
|
||||||
fnArgs: elemType.merge loc (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs);
|
(innerMerge loc elemType (map (fn:
|
||||||
|
{ inherit (fn) file; value = fn.value fnArgs; }
|
||||||
|
) defs)).value or (throw
|
||||||
|
"The option `${showOption loc}' is defined as a function that doesn't return any value (using mkIf or mkMerge), in ${showFiles (getFiles defs)}");
|
||||||
getSubOptions = elemType.getSubOptions;
|
getSubOptions = elemType.getSubOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
submodule = opts:
|
submodule = opts:
|
||||||
let
|
let
|
||||||
opts' = toList opts;
|
opts' = toList opts;
|
||||||
inherit (import ./modules.nix) evalModules;
|
|
||||||
in
|
in
|
||||||
mkOptionType rec {
|
mkOptionType rec {
|
||||||
name = "submodule";
|
name = "submodule";
|
||||||
|
|
Loading…
Reference in New Issue
Block a user