forked from mirrors/nixpkgs
Add a new way to handle option sets.
svn path=/nixpkgs/trunk/; revision=12505
This commit is contained in:
parent
7abbe5889f
commit
0e25bb67cf
|
@ -287,12 +287,55 @@ rec {
|
|||
checker
|
||||
else condConcat
|
||||
name (tail (tail list)) checker;
|
||||
|
||||
# Merge sets of attributes and use the function f to merge
|
||||
# attributes values.
|
||||
zip = f: sets:
|
||||
builtins.listToAttrs (map (name: {
|
||||
inherit name;
|
||||
value =
|
||||
f name
|
||||
(map (__getAttr name)
|
||||
(filter (__hasAttr name) sets));
|
||||
}) (concatMap builtins.attrNames sets));
|
||||
|
||||
# divide a list in two depending on the evaluation of a predicate.
|
||||
partition = pred:
|
||||
fold (h: t:
|
||||
if pred h
|
||||
then { right = [h] ++ t.right; wrong = t.wrong; }
|
||||
else { right = t.right; wrong = [h] ++ t.wrong; }
|
||||
) { right = []; wrong = []; };
|
||||
|
||||
# Take a function and evaluate it with its own returned value.
|
||||
finalReference = f:
|
||||
(rec { result = f result; }).result;
|
||||
|
||||
# flatten a list of sets returned by 'f'.
|
||||
# f : function to evaluate each set.
|
||||
# attr : name of the attribute which contains more values.
|
||||
# default: result if 'x' is empty.
|
||||
# x : list of values that have to be processed.
|
||||
uniqFlattenAttr = f: attr: default: x:
|
||||
if x == []
|
||||
then default
|
||||
else let h = f (head x); t = tail x; in
|
||||
if elem h default
|
||||
then uniqFlattenAttr f attr default t
|
||||
else uniqFlattenAttr f attr (default ++ [h]) (toList (getAttr [attr] [] h) ++ t)
|
||||
;
|
||||
|
||||
/* Options. */
|
||||
|
||||
|
||||
mkOption = attrs: attrs // {_type = "option";};
|
||||
|
||||
typeOf = x: if x ? _type then x._type else "";
|
||||
|
||||
isOption = attrs:
|
||||
__isAttrs attrs
|
||||
&& attrs ? _type
|
||||
&& attrs._type == "option";
|
||||
|
||||
addDefaultOptionValues = defs: opts: opts //
|
||||
builtins.listToAttrs (map (defName:
|
||||
{ name = defName;
|
||||
|
@ -315,7 +358,67 @@ rec {
|
|||
else addDefaultOptionValues defValue {};
|
||||
}
|
||||
) (builtins.attrNames defs));
|
||||
|
||||
|
||||
mergeDefaultOption = name: list:
|
||||
if list != [] && tail list == [] then head list
|
||||
else if all __isFunction list then x: mergeDefaultOption (map (f: f x) list)
|
||||
else if all __isList list then concatLists list
|
||||
else if all __isAttrs list then mergeAttrs list
|
||||
else throw "Default merge method does not work on '${name}'.";
|
||||
|
||||
mergeEnableOption = name: fold logicalOR false;
|
||||
|
||||
mergeListOption = name: list:
|
||||
if all __isList list then list
|
||||
else throw "${name}: Expect a list.";
|
||||
|
||||
# Merge sets of options and bindings.
|
||||
# noOption: function to call if no option is declared.
|
||||
mergeOptionSets = noOption: path: opts:
|
||||
if all __isAttrs opts then
|
||||
zip (attr: opts:
|
||||
let
|
||||
name = if path == "" then attr else path + "." + attr;
|
||||
defaultOpt = { merge = mergeDefaultOption; };
|
||||
test = partition isOption opts;
|
||||
in
|
||||
if test.right == [] then mergeOptionSets noOption name test.wrong
|
||||
else if tail test.right != [] then throw "Multiple options for '${name}'."
|
||||
else if test.wrong == [] then (head test.right).default
|
||||
else (defaultOpt // head test.right).merge name test.wrong
|
||||
) opts
|
||||
else noOption path opts;
|
||||
|
||||
# Keep all option declarations and add an attribute "name" inside
|
||||
# each option which contains the path that has to be followed to
|
||||
# access it.
|
||||
filterOptionSets = path: opts:
|
||||
if all __isAttrs opts then
|
||||
zip (attr: opts:
|
||||
let
|
||||
name = if path == "" then attr else path + "." + attr;
|
||||
test = partition isOption opts;
|
||||
in
|
||||
if test.right == []
|
||||
then filterOptionSets name test.wrong
|
||||
else map (x: x // { inherit name; }) test.right
|
||||
) opts
|
||||
else {};
|
||||
|
||||
# Evaluate a list of option sets that would be merged with the
|
||||
# function "merge" which expects two arguments. The attribute named
|
||||
# "require" is used to imports option declarations and bindings.
|
||||
finalReferenceOptionSets = merge: pkgs: opts:
|
||||
let optionSet = final: configFun:
|
||||
if __isFunction configFun then configFun pkgs final
|
||||
else configFun; # backward compatibility.
|
||||
in
|
||||
finalReference (final: merge ""
|
||||
(map (x: removeAttrs x ["require"])
|
||||
(uniqFlattenAttr (optionSet final) "require" [] (toList opts))
|
||||
)
|
||||
);
|
||||
|
||||
optionAttrSetToDocList = (l: attrs:
|
||||
(if (getAttr ["_type"] "" attrs) == "option" then
|
||||
[({
|
||||
|
|
53
pkgs/test/mkOption/declare.nix
Normal file
53
pkgs/test/mkOption/declare.nix
Normal file
|
@ -0,0 +1,53 @@
|
|||
# sets of small configurations:
|
||||
# Each configuration
|
||||
rec {
|
||||
# has 2 arguments pkgs and this.
|
||||
configA = pkgs: this: {
|
||||
# Can depends on other configuration
|
||||
require = configB;
|
||||
|
||||
# Defines new options
|
||||
optionA = pkgs.lib.mkOption {
|
||||
# With default values
|
||||
default = false;
|
||||
# And merging functions.
|
||||
merge = pkgs.lib.mergeEnableOption;
|
||||
};
|
||||
|
||||
# Add a new definition to other options.
|
||||
optionB = this.optionA;
|
||||
};
|
||||
|
||||
# Can be used for option header.
|
||||
configB = pkgs: this: {
|
||||
# Can depends on more than one configuration.
|
||||
require = [ configC configD ];
|
||||
|
||||
optionB = pkgs.lib.mkOption {
|
||||
default = false;
|
||||
};
|
||||
|
||||
# Is not obliged to define other options.
|
||||
};
|
||||
|
||||
configC = pkgs: this: {
|
||||
require = [ configA ];
|
||||
|
||||
optionC = pkgs.lib.mkOption {
|
||||
default = false;
|
||||
};
|
||||
|
||||
# Use the default value if it is not overwritten.
|
||||
optionA = this.optionC;
|
||||
};
|
||||
|
||||
# Can also be used as option configuration only.
|
||||
# without any arguments (backward compatibility)
|
||||
configD = {
|
||||
# Is not forced to specify the require attribute.
|
||||
|
||||
# Is not force to make new options.
|
||||
optionA = true;
|
||||
optionD = false;
|
||||
};
|
||||
}
|
11
pkgs/test/mkOption/keep.nix
Normal file
11
pkgs/test/mkOption/keep.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
let
|
||||
pkgs = import ../../top-level/all-packages.nix {};
|
||||
config = import ./declare.nix;
|
||||
in
|
||||
with (pkgs.lib);
|
||||
|
||||
finalReferenceOptionSets
|
||||
filterOptionSets
|
||||
pkgs
|
||||
# List of main configurations.
|
||||
[ config.configB config.configC ]
|
57
pkgs/test/mkOption/keep.ref
Normal file
57
pkgs/test/mkOption/keep.ref
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<expr>
|
||||
<attrs>
|
||||
<attr name="optionA">
|
||||
<list>
|
||||
<attrs>
|
||||
<attr name="_type">
|
||||
<string value="option" />
|
||||
</attr>
|
||||
<attr name="default">
|
||||
<bool value="false" />
|
||||
</attr>
|
||||
<attr name="merge">
|
||||
<unevaluated />
|
||||
</attr>
|
||||
<attr name="name">
|
||||
<string value="optionA" />
|
||||
</attr>
|
||||
</attrs>
|
||||
</list>
|
||||
</attr>
|
||||
<attr name="optionB">
|
||||
<list>
|
||||
<attrs>
|
||||
<attr name="_type">
|
||||
<string value="option" />
|
||||
</attr>
|
||||
<attr name="default">
|
||||
<bool value="false" />
|
||||
</attr>
|
||||
<attr name="name">
|
||||
<string value="optionB" />
|
||||
</attr>
|
||||
</attrs>
|
||||
</list>
|
||||
</attr>
|
||||
<attr name="optionC">
|
||||
<list>
|
||||
<attrs>
|
||||
<attr name="_type">
|
||||
<string value="option" />
|
||||
</attr>
|
||||
<attr name="default">
|
||||
<bool value="false" />
|
||||
</attr>
|
||||
<attr name="name">
|
||||
<string value="optionC" />
|
||||
</attr>
|
||||
</attrs>
|
||||
</list>
|
||||
</attr>
|
||||
<attr name="optionD">
|
||||
<attrs>
|
||||
</attrs>
|
||||
</attr>
|
||||
</attrs>
|
||||
</expr>
|
15
pkgs/test/mkOption/merge.nix
Normal file
15
pkgs/test/mkOption/merge.nix
Normal file
|
@ -0,0 +1,15 @@
|
|||
let
|
||||
pkgs = import ../../top-level/all-packages.nix {};
|
||||
config = import ./declare.nix;
|
||||
|
||||
# Define the handler of unbound options.
|
||||
noOption = name: values:
|
||||
builtins.trace "Attribute named '${name}' does not match any option declaration." values;
|
||||
in
|
||||
with (pkgs.lib);
|
||||
|
||||
finalReferenceOptionSets
|
||||
(mergeOptionSets noOption)
|
||||
pkgs
|
||||
# List of main configurations.
|
||||
[ config.configB config.configC ]
|
20
pkgs/test/mkOption/merge.ref
Normal file
20
pkgs/test/mkOption/merge.ref
Normal file
|
@ -0,0 +1,20 @@
|
|||
trace: Str("Attribute named 'optionD' does not match any option declaration.",[])
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<expr>
|
||||
<attrs>
|
||||
<attr name="optionA">
|
||||
<bool value="true" />
|
||||
</attr>
|
||||
<attr name="optionB">
|
||||
<bool value="true" />
|
||||
</attr>
|
||||
<attr name="optionC">
|
||||
<bool value="false" />
|
||||
</attr>
|
||||
<attr name="optionD">
|
||||
<list>
|
||||
<bool value="false" />
|
||||
</list>
|
||||
</attr>
|
||||
</attrs>
|
||||
</expr>
|
9
pkgs/test/mkOption/test.sh
Executable file
9
pkgs/test/mkOption/test.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#! /bin/sh -e
|
||||
|
||||
echo 1>&2 "Test: Merge of option bindings."
|
||||
nix-instantiate merge.nix --eval-only --strict --xml >& merge.out
|
||||
diff merge.ref merge.out
|
||||
|
||||
echo 1>&2 "Test: Filter of option declarations."
|
||||
nix-instantiate keep.nix --eval-only --strict --xml >& keep.out
|
||||
diff keep.ref keep.out
|
Loading…
Reference in a new issue