Add a new way to handle option sets.

svn path=/nixpkgs/trunk/; revision=12505
This commit is contained in:
Nicolas Pierron 2008-08-05 17:16:35 +00:00
parent 7abbe5889f
commit 0e25bb67cf
7 changed files with 270 additions and 2 deletions

View file

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

View 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;
};
}

View 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 ]

View 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>

View 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 ]

View 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
View 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