Permalink
Browse files

Add a new way to handle option sets.

svn path=/nixpkgs/trunk/; revision=12505
  • Loading branch information...
1 parent 7abbe58 commit 0e25bb67cf0e264034616b7173f9a12af9536a5c @nbp nbp committed Aug 5, 2008
View
@@ -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
[({
@@ -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;
+ };
+}
@@ -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 ]
@@ -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>
@@ -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 ]
@@ -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>
@@ -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

0 comments on commit 0e25bb6

Please sign in to comment.