From 0e25bb67cf0e264034616b7173f9a12af9536a5c Mon Sep 17 00:00:00 2001 From: Nicolas Pierron Date: Tue, 5 Aug 2008 17:16:35 +0000 Subject: [PATCH] Add a new way to handle option sets. svn path=/nixpkgs/trunk/; revision=12505 --- pkgs/lib/default.nix | 107 ++++++++++++++++++++++++++++++++- pkgs/test/mkOption/declare.nix | 53 ++++++++++++++++ pkgs/test/mkOption/keep.nix | 11 ++++ pkgs/test/mkOption/keep.ref | 57 ++++++++++++++++++ pkgs/test/mkOption/merge.nix | 15 +++++ pkgs/test/mkOption/merge.ref | 20 ++++++ pkgs/test/mkOption/test.sh | 9 +++ 7 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 pkgs/test/mkOption/declare.nix create mode 100644 pkgs/test/mkOption/keep.nix create mode 100644 pkgs/test/mkOption/keep.ref create mode 100644 pkgs/test/mkOption/merge.nix create mode 100644 pkgs/test/mkOption/merge.ref create mode 100755 pkgs/test/mkOption/test.sh diff --git a/pkgs/lib/default.nix b/pkgs/lib/default.nix index 2c8e29cc04b1..d94f5b485657 100644 --- a/pkgs/lib/default.nix +++ b/pkgs/lib/default.nix @@ -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 [({ diff --git a/pkgs/test/mkOption/declare.nix b/pkgs/test/mkOption/declare.nix new file mode 100644 index 000000000000..9e89a1c096da --- /dev/null +++ b/pkgs/test/mkOption/declare.nix @@ -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; + }; +} diff --git a/pkgs/test/mkOption/keep.nix b/pkgs/test/mkOption/keep.nix new file mode 100644 index 000000000000..c26064d89f7e --- /dev/null +++ b/pkgs/test/mkOption/keep.nix @@ -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 ] diff --git a/pkgs/test/mkOption/keep.ref b/pkgs/test/mkOption/keep.ref new file mode 100644 index 000000000000..a3a051eb48c4 --- /dev/null +++ b/pkgs/test/mkOption/keep.ref @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkgs/test/mkOption/merge.nix b/pkgs/test/mkOption/merge.nix new file mode 100644 index 000000000000..0d4b3c1acd18 --- /dev/null +++ b/pkgs/test/mkOption/merge.nix @@ -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 ] diff --git a/pkgs/test/mkOption/merge.ref b/pkgs/test/mkOption/merge.ref new file mode 100644 index 000000000000..6956f65dbbcc --- /dev/null +++ b/pkgs/test/mkOption/merge.ref @@ -0,0 +1,20 @@ +trace: Str("Attribute named 'optionD' does not match any option declaration.",[]) + + + + + + + + + + + + + + + + + + + diff --git a/pkgs/test/mkOption/test.sh b/pkgs/test/mkOption/test.sh new file mode 100755 index 000000000000..5478846d563f --- /dev/null +++ b/pkgs/test/mkOption/test.sh @@ -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