* Refactor mkIf to extract the concept of properties.
Properties can be delay on any attribute set and have functions which are used to define the semantic of each property. * Introduce the mkOverride property. svn path=/nixpkgs/trunk/; revision=14285
This commit is contained in:
parent
02b2540b15
commit
e18a10d0ae
1 changed files with 362 additions and 46 deletions
|
@ -3,7 +3,9 @@
|
|||
let lib = import ./default.nix; in
|
||||
|
||||
with { inherit (builtins) head tail; };
|
||||
with import ./trivial.nix;
|
||||
with import ./lists.nix;
|
||||
with import ./misc.nix;
|
||||
with import ./attrsets.nix;
|
||||
|
||||
rec {
|
||||
|
@ -11,7 +13,8 @@ rec {
|
|||
|
||||
mkOption = attrs: attrs // {_type = "option";};
|
||||
|
||||
typeOf = x: if (__isAttrs x && x ? _type) then x._type else "";
|
||||
hasType = x: __isAttrs x && x ? _type;
|
||||
typeOf = x: if hasType x then x._type else "";
|
||||
|
||||
isOption = attrs: (typeOf attrs) == "option";
|
||||
|
||||
|
@ -97,11 +100,11 @@ rec {
|
|||
} // (head decls);
|
||||
|
||||
# Return the list of option sets.
|
||||
optAttrs = map delayIf defs;
|
||||
optAttrs = map delayProperties defs;
|
||||
|
||||
# return the list of option values.
|
||||
# Remove undefined values that are coming from evalIf.
|
||||
optValues = filter (x: !isNotdef x) (map evalIf defs);
|
||||
optValues = evalProperties defs;
|
||||
in
|
||||
if decls == [] then handleOptionSets optionHandler name optAttrs
|
||||
else lib.addErrorContext "while evaluating the option ${name}:" (
|
||||
|
@ -150,9 +153,9 @@ rec {
|
|||
let
|
||||
# remove possible mkIf to access the require attribute.
|
||||
noImportConditions = cfgSet0:
|
||||
let cfgSet1 = delayIf cfgSet0; in
|
||||
let cfgSet1 = delayProperties cfgSet0; in
|
||||
if cfgSet1 ? require then
|
||||
cfgSet1 // { require = rmIf cfgSet1.require; }
|
||||
cfgSet1 // { require = rmProperties cfgSet1.require; }
|
||||
else
|
||||
cfgSet1;
|
||||
|
||||
|
@ -176,12 +179,39 @@ rec {
|
|||
cfg2 = noImportConditions cfg1;
|
||||
in cfg2;
|
||||
|
||||
getRequire = x: toList (getAttr ["require"] [] (preprocess x));
|
||||
getRequire = x:
|
||||
toList (getAttr ["require"] [] (preprocess x));
|
||||
getCleanRequire = x: map rmProperties (getRequire x);
|
||||
rmRequire = x: removeAttrs (preprocess x) ["require"];
|
||||
|
||||
duplicateIncludeProperties = list:
|
||||
# iterate on all configurations
|
||||
fold (cfg: l:
|
||||
# iterate on all imported configuration from cfg
|
||||
fold (include: l:
|
||||
# clean up the included cfg to get the same result
|
||||
let includedCfg = rmProperties include; in
|
||||
# if the include has properties
|
||||
if include != includedCfg then
|
||||
# iterate on all configurations
|
||||
map (cfg:
|
||||
# if the imported configuration is seen
|
||||
if (rmProperties cfg) == includedCfg then
|
||||
# copy the properties from the import to the configuration.
|
||||
delayProperties (copyProperties include cfg)
|
||||
else
|
||||
cfg
|
||||
) l
|
||||
else
|
||||
l
|
||||
) l (getRequire cfg)
|
||||
) list list;
|
||||
in
|
||||
merge "" (
|
||||
map rmRequire (
|
||||
lib.uniqFlatten getRequire [] [] (toList opts)
|
||||
duplicateIncludeProperties (
|
||||
lib.uniqFlatten getCleanRequire [] [] (toList opts)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -203,65 +233,351 @@ rec {
|
|||
(l + (if l=="" then "" else ".") + s) (builtins.getAttr s attrs)))
|
||||
(builtins.attrNames attrs)));
|
||||
|
||||
|
||||
/* Option Properties */
|
||||
# Generalize the problem of delayable properties. Any property can be created
|
||||
|
||||
|
||||
# Tell that nothing is defined. When properties are evaluated, this type
|
||||
# is used to remove an entry. Thus if your property evaluation semantic
|
||||
# implies that you have to mute the content of an attribute, then your
|
||||
# property should produce this value.
|
||||
isNotdef = attrs: (typeOf attrs) == "notdef";
|
||||
mkNotdef = {_type = "notdef";};
|
||||
|
||||
# General property type, it has a property attribute and a content
|
||||
# attribute. The property attribute refer to an attribute set which
|
||||
# contains a _type attribute and a list of functions which are used to
|
||||
# evaluate this property. The content attribute is used to stack property
|
||||
# on top of each other.
|
||||
#
|
||||
# The optional functions which may be contained in the property attribute
|
||||
# are:
|
||||
# - onDelay: run on a copied property.
|
||||
# - onGlobalDelay: run on all copied properties.
|
||||
# - onEval: run on an evaluated property.
|
||||
# - onGlobalEval: run on a list of property stack on top of their values.
|
||||
isProperty = attrs: (typeOf attrs) == "property";
|
||||
mkProperty = p@{property, content, ...}: p // {
|
||||
_type = "property";
|
||||
};
|
||||
|
||||
# Go throw the stack of properties and apply the function `op' on all
|
||||
# property and call the function `nul' on the final value which is not a
|
||||
# property. The stack is traversed in reversed order. The `op' function
|
||||
# should expect a property with a content which have been modified.
|
||||
#
|
||||
# Warning: The `op' function expects only one argument in order to avoid
|
||||
# calls to mkProperties as the argument is already a valid property which
|
||||
# contains the result of the folding inside the content attribute.
|
||||
foldProperty = op: nul: attrs:
|
||||
if isProperty attrs then
|
||||
op (attrs // {
|
||||
content = foldProperty op nul attrs.content;
|
||||
})
|
||||
else
|
||||
nul attrs;
|
||||
|
||||
# Simple function which can be used as the `op' argument of the
|
||||
# foldProperty function. Properties that you don't want to handle can be
|
||||
# ignored with the `id' function. `isSearched' is a function which should
|
||||
# check the type of a property and return a boolean value. `thenFun' and
|
||||
# `elseFun' are functions which behave as the `op' argument of the
|
||||
# foldProperty function.
|
||||
foldFilter = isSearched: thenFun: elseFun: attrs:
|
||||
if isSearched attrs.property then
|
||||
thenFun attrs
|
||||
else
|
||||
elseFun attrs;
|
||||
|
||||
|
||||
# Move properties from the current attribute set to the attribute
|
||||
# contained in this attribute set. This trigger property handlers called
|
||||
# `onDelay' and `onGlobalDelay'.
|
||||
delayProperties = attrs:
|
||||
let cleanAttrs = rmProperties attrs; in
|
||||
if cleanAttrs != attrs then
|
||||
lib.mapAttrs (a: v:
|
||||
lib.addErrorContext "while moving properties on the attribute `${a}'." (
|
||||
triggerPropertiesGlobalDelay a (
|
||||
triggerPropertiesDelay a (
|
||||
copyProperties attrs v
|
||||
)))) cleanAttrs
|
||||
else
|
||||
attrs;
|
||||
|
||||
# Call onDelay functions.
|
||||
triggerPropertiesDelay = name: attrs:
|
||||
let
|
||||
callOnDelay = p@{property, ...}:
|
||||
lib.addErrorContext "while calling a onDelay function." (
|
||||
if property ? onDelay then
|
||||
property.onDelay name p
|
||||
else
|
||||
p
|
||||
);
|
||||
in
|
||||
foldProperty callOnDelay id attrs;
|
||||
|
||||
# Call onGlobalDelay functions.
|
||||
triggerPropertiesGlobalDelay = name: attrs:
|
||||
let
|
||||
globalDelayFuns = uniqListExt {
|
||||
getter = property: property._type;
|
||||
inputList = foldProperty (p@{property, content, ...}:
|
||||
if property ? onGlobalDelay then
|
||||
[ property ] ++ content
|
||||
else
|
||||
content
|
||||
) (a: []) attrs;
|
||||
};
|
||||
|
||||
callOnGlobalDelay = property: content:
|
||||
lib.addErrorContext "while calling a onGlobalDelay function." (
|
||||
property.onGlobalDelay name content
|
||||
);
|
||||
in
|
||||
fold callOnGlobalDelay attrs globalDelayFuns;
|
||||
|
||||
# Expect a list of values which may have properties and return the same
|
||||
# list of values where all properties have been evaluated and where all
|
||||
# ignored values are removed. This trigger property handlers called
|
||||
# `onEval' and `onGlobalEval'.
|
||||
evalProperties = valList:
|
||||
if valList != [] then
|
||||
filter (x: !isNotdef x) (
|
||||
lib.addErrorContext "while evaluating properties an attribute." (
|
||||
triggerPropertiesGlobalEval (
|
||||
map triggerPropertiesEval valList
|
||||
)))
|
||||
else
|
||||
valList;
|
||||
|
||||
# Call onEval function
|
||||
triggerPropertiesEval = val:
|
||||
foldProperty (p@{property, ...}:
|
||||
lib.addErrorContext "while calling a onEval function." (
|
||||
if property ? onEval then
|
||||
property.onEval p
|
||||
else
|
||||
p
|
||||
)
|
||||
) id val;
|
||||
|
||||
# Call onGlobalEval function
|
||||
triggerPropertiesGlobalEval = valList:
|
||||
let
|
||||
globalEvalFuns = uniqListExt {
|
||||
getter = property: property._type;
|
||||
inputList =
|
||||
fold (attrs: list:
|
||||
foldProperty (p@{property, content, ...}:
|
||||
if property ? onGlobalEval then
|
||||
[ property ] ++ content
|
||||
else
|
||||
content
|
||||
) (a: list) attrs
|
||||
) [] valList;
|
||||
};
|
||||
|
||||
callOnGlobalEval = property: valList:
|
||||
lib.addErrorContext "while calling a onGlobalEval function." (
|
||||
property.onGlobalEval valList
|
||||
);
|
||||
in
|
||||
fold callOnGlobalEval valList globalEvalFuns;
|
||||
|
||||
# Remove all properties on top of a value and return the value.
|
||||
rmProperties =
|
||||
foldProperty (p@{content, ...}: content) id;
|
||||
|
||||
# Copy properties defined on a value on another value.
|
||||
copyProperties = attrs: newAttrs:
|
||||
foldProperty id (x: newAttrs) attrs;
|
||||
|
||||
/* If. ThenElse. Always. */
|
||||
# !!! cleanup needed
|
||||
|
||||
# create "if" statement that can be dealyed on sets until a "then-else" or
|
||||
# "always" set is reached. When an always set is reached the condition
|
||||
# is ignore.
|
||||
|
||||
# Create a "If" property which only contains a condition.
|
||||
isIf = attrs: (typeOf attrs) == "if";
|
||||
mkIf = condition: thenelse:
|
||||
if isIf thenelse then
|
||||
mkIf (condition && thenelse.condition) thenelse.thenelse
|
||||
else {
|
||||
mkIf = condition: content: mkProperty {
|
||||
property = {
|
||||
_type = "if";
|
||||
inherit condition thenelse;
|
||||
onGlobalDelay = onIfGlobalDelay;
|
||||
onEval = onIfEval;
|
||||
inherit condition;
|
||||
};
|
||||
inherit content;
|
||||
};
|
||||
|
||||
|
||||
isNotdef = attrs: (typeOf attrs) == "notdef";
|
||||
mkNotdef = {_type = "notdef";};
|
||||
|
||||
|
||||
# Create a "ThenElse" property which contains choices which can choosed by
|
||||
# the evaluation of an "If" statement.
|
||||
isThenElse = attrs: (typeOf attrs) == "then-else";
|
||||
mkThenElse = attrs:
|
||||
assert attrs ? thenPart && attrs ? elsePart;
|
||||
attrs // { _type = "then-else"; };
|
||||
|
||||
mkProperty {
|
||||
property = {
|
||||
_type = "then-else";
|
||||
onEval = val: throw "Missing mkIf statement.";
|
||||
inherit (attrs) thenPart elsePart;
|
||||
};
|
||||
content = mkNotdef;
|
||||
};
|
||||
|
||||
# Create an "Always" property remove ignore all "If" statement.
|
||||
isAlways = attrs: (typeOf attrs) == "always";
|
||||
mkAlways = value: { inherit value; _type = "always"; };
|
||||
mkAlways = value:
|
||||
mkProperty {
|
||||
property = {
|
||||
_type = "always";
|
||||
onEval = p@{content, ...}: content;
|
||||
inherit value;
|
||||
};
|
||||
content = mkNotdef;
|
||||
};
|
||||
|
||||
pushIf = f: attrs:
|
||||
if isIf attrs then pushIf f (
|
||||
let val = attrs.thenelse; in
|
||||
# evaluate the condition.
|
||||
if isThenElse val then
|
||||
if attrs.condition then
|
||||
val.thenPart
|
||||
# Remove all "If" statement defined on a value.
|
||||
rmIf = foldProperty (
|
||||
foldFilter isIf
|
||||
({content, ...}: content)
|
||||
id
|
||||
) id;
|
||||
|
||||
# Evaluate the "If" statements when either "ThenElse" or "Always"
|
||||
# statement is encounter. Otherwise it remove multiple If statement and
|
||||
# replace them by one "If" staement where the condition is the list of all
|
||||
# conditions joined with a "and" operation.
|
||||
onIfGlobalDelay = name: content:
|
||||
let
|
||||
# extract if statements and non-if statements and repectively put them
|
||||
# in the attribute list and attrs.
|
||||
ifProps =
|
||||
foldProperty
|
||||
(foldFilter (p: isIf p || isThenElse p || isAlways p)
|
||||
# then, push the codition inside the list list
|
||||
(p@{property, content, ...}:
|
||||
{ inherit (content) attrs;
|
||||
list = [property] ++ content.list;
|
||||
}
|
||||
)
|
||||
# otherwise, add the propertie.
|
||||
(p@{property, content, ...}:
|
||||
{ inherit (content) list;
|
||||
attrs = p // { content = content.attrs; };
|
||||
}
|
||||
)
|
||||
)
|
||||
(attrs: { list = []; inherit attrs; })
|
||||
content;
|
||||
|
||||
# compute the list of if statements.
|
||||
evalIf = content: condition: list:
|
||||
if list == [] then
|
||||
mkIf condition content
|
||||
else
|
||||
val.elsePart
|
||||
# ignore the condition.
|
||||
else if isAlways val then
|
||||
val.value
|
||||
# otherwise
|
||||
else
|
||||
f attrs.condition val)
|
||||
let p = head list; in
|
||||
|
||||
# evaluate the condition.
|
||||
if isThenElse p then
|
||||
if condition then
|
||||
foldProperty (a: p.thenPart) id content
|
||||
else
|
||||
foldProperty (a: p.elsePart) id content
|
||||
# ignore the condition.
|
||||
else if isAlways p then
|
||||
foldProperty (a: p.value) id content
|
||||
# otherwise (isIf)
|
||||
else
|
||||
evalIf content (condition && p.condition) (tail list);
|
||||
in
|
||||
evalIf ifProps.attrs true ifProps.list;
|
||||
|
||||
# Evaluate the condition of the "If" statement to either get the value or
|
||||
# to ignore the value.
|
||||
onIfEval = p@{property, content, ...}:
|
||||
if property.condition then
|
||||
content
|
||||
else
|
||||
attrs;
|
||||
mkNotdef;
|
||||
|
||||
# take care otherwise you will have to handle this by hand.
|
||||
rmIf = pushIf (condition: val: val);
|
||||
/* mkOverride */
|
||||
|
||||
evalIf = pushIf (condition: val:
|
||||
if condition then val else mkNotdef
|
||||
);
|
||||
# Create an "Override" statement which allow the user to define
|
||||
# prioprities between values. The default priority is 100 and the lowest
|
||||
# priorities are kept. The template argument must reproduce the same
|
||||
# attribute set hierachy to override leaves of the hierarchy.
|
||||
isOverride = attrs: (typeOf attrs) == "override";
|
||||
mkOverride = priority: template: content: mkProperty {
|
||||
property = {
|
||||
_type = "override";
|
||||
onDelay = onOverrideDelay;
|
||||
onGlobalEval = onOverrideGlobalEval;
|
||||
inherit priority template;
|
||||
};
|
||||
inherit content;
|
||||
};
|
||||
|
||||
delayIf = pushIf (condition: val:
|
||||
# rewrite the condition on sub-attributes.
|
||||
lib.mapAttrs (name: mkIf condition) val
|
||||
);
|
||||
# Make the template traversal in function of the property traversal. If
|
||||
# the template define a non-empty attribute set, then the property is
|
||||
# copied only on all mentionned attributes inside the template.
|
||||
# Otherwise, the property is kept on all sub-attribute definitions.
|
||||
onOverrideDelay = name: p@{property, content, ...}:
|
||||
let inherit (property) template; in
|
||||
if builtins.isAttrs template && template != {} then
|
||||
if builtins.hasAttr name template then
|
||||
p // {
|
||||
property = p.property // {
|
||||
template = builtins.getAttr name template;
|
||||
};
|
||||
}
|
||||
# Do not override the attribute \name\
|
||||
else
|
||||
content
|
||||
# Override values defined inside the attribute \name\.
|
||||
else
|
||||
p;
|
||||
|
||||
# Ignore all values which have a higher value of the priority number.
|
||||
onOverrideGlobalEval = valList:
|
||||
let
|
||||
defaultPrio = 100;
|
||||
|
||||
inherit (builtins) lessThan;
|
||||
|
||||
getPrioVal =
|
||||
foldProperty
|
||||
(foldFilter isOverride
|
||||
(p@{property, content, ...}:
|
||||
if lessThan content.priority property.priority then
|
||||
content
|
||||
else
|
||||
content // {
|
||||
inherit (property) priority;
|
||||
}
|
||||
)
|
||||
(p@{property, content, ...}:
|
||||
content // {
|
||||
value = p // { content = content.value; };
|
||||
}
|
||||
)
|
||||
) (value: { priority = defaultPrio; inherit value; });
|
||||
|
||||
prioValList = map getPrioVal valList;
|
||||
|
||||
higherPrio = fold (x: y:
|
||||
if lessThan x.priority y then
|
||||
x.priority
|
||||
else
|
||||
y
|
||||
) defaultPrio prioValList;
|
||||
in
|
||||
map (x:
|
||||
if x.priority == higherPrio then
|
||||
x.value
|
||||
else
|
||||
mkNotdef
|
||||
) prioValList;
|
||||
|
||||
}
|
Loading…
Reference in a new issue