diff options
author | Nicolas Pierron <nicolas.b.pierron@gmail.com> | 2009-05-27 20:25:17 +0000 |
---|---|---|
committer | Nicolas Pierron <nicolas.b.pierron@gmail.com> | 2009-05-27 20:25:17 +0000 |
commit | 49b4942f0e8455d41ad02ffdfac0a6b01c25215c (patch) | |
tree | 2f1373750593ea7e879e538dbbb1df0974601a8b /pkgs/lib/options.nix | |
parent | 8c2a5ccdcb17607aacc9e46c8e9b280d68440b54 (diff) | |
download | nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.tar nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.tar.gz nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.tar.bz2 nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.tar.lz nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.tar.xz nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.tar.zst nixpkgs-49b4942f0e8455d41ad02ffdfac0a6b01c25215c.zip |
Add option types.
svn path=/nixpkgs/trunk/; revision=15749
Diffstat (limited to 'pkgs/lib/options.nix')
-rw-r--r-- | pkgs/lib/options.nix | 239 |
1 files changed, 227 insertions, 12 deletions
diff --git a/pkgs/lib/options.nix b/pkgs/lib/options.nix index 683b2d05dfc..4204d2d56df 100644 --- a/pkgs/lib/options.nix +++ b/pkgs/lib/options.nix @@ -11,13 +11,227 @@ with import ./attrsets.nix; rec { - mkOption = attrs: attrs // {_type = "option";}; - hasType = x: isAttrs x && x ? _type; typeOf = x: if hasType x then x._type else ""; isOption = attrs: (typeOf attrs) == "option"; + mkOption = attrs: attrs // { + _type = "option"; + # name (this is the name of the attributem it is automatically generated by the traversal) + # default (value used when no definition exists) + # example (documentation) + # description (documentation) + # type (option type, provide a default merge function and ensure type correctness) + # merge (function used to merge definitions into one definition: [ /type/ ] -> /type/) + # apply (convert the option value to ease the manipulation of the option result) + # options (set of sub-options declarations & definitions) + }; + + # Make the option declaration more user-friendly by adding default + # settings and some verifications based on the declaration content (like + # type correctness). + addOptionMakeUp = {name, recurseInto}: decl: + let + init = { + inherit name; + merge = mergeDefaultOption; + apply = lib.id; + }; + + mergeFromType = opt: + if decl ? type && decl.type ? merge then + opt // { merge = decl.type.merge; } + else + opt; + + addDeclaration = opt: opt // decl; + + ensureMergeInputType = opt: + if decl ? type then + opt // { + merge = list: + if all decl.type.check list then + opt.merge list + else + throw "One of the definitions has a bad type."; + } + else opt; + + ensureDefaultType = opt: + if decl ? type && decl ? default then + opt // { + default = + if decl.type.check decl.default then + decl.default + else + throw "The default value has a bad type."; + } + else opt; + + handleOptionSets = opt: + if decl ? type && decl.type.hasOptions then + opt // { + merge = list: + decl.type.iter + (path: opts: recurseInto path (decl.options ++ [opts])) + opt.name + (opt.merge list); + options = recurseInto (decl.type.docPath opt.name) decl.options; + } + else + opt; + in + foldl (opt: f: f opt) init [ + # default settings + mergeFromType + + # user settings + addDeclaration + + # override settings + ensureMergeInputType + ensureDefaultType + handleOptionSets + ]; + + # Merge a list of options containning different field. This is useful to + # separate the merge & apply fields from the interface. + mergeOptionDecls = opts: + if opts == [] then {} + else if tail opts == [] then + let opt = head opts; in + if opt ? options then + opt // { options = toList opt.options; } + else + opt + else + fold (opt1: opt2: + lib.addErrorContext "opt1 = ${lib.showVal opt1}\nopt2 = ${lib.showVal opt2}" ( + # You cannot merge if two options have the same field. + assert opt1 ? default -> ! opt2 ? default; + assert opt1 ? example -> ! opt2 ? example; + assert opt1 ? description -> ! opt2 ? description; + assert opt1 ? merge -> ! opt2 ? merge; + assert opt1 ? apply -> ! opt2 ? apply; + assert opt1 ? type -> ! opt2 ? type; + if opt1 ? options || opt2 ? options then + opt1 // opt2 // { + options = + (toList (attrByPath ["options"] [] opt1)) + ++ (toList (attrByPath ["options"] [] opt2)); + } + else + opt1 // opt2 + )) {} opts; + + + # name (name of the type) + # check (boolean function) + # merge (default merge function) + # iter (iterate on all elements contained in this type) + # fold (fold all elements contained in this type) + # hasOptions (boolean: whatever this option contains an option set) + # path (path contatenated to the option name contained contained in the option set) + isOptionType = attrs: (typeOf attrs) == "option-type"; + mkOptionType = attrs@{ + name + , check ? (x: true) + , merge ? mergeDefaultOption + # Handle complex structure types. + , iter ? (f: path: v: f path v) + , fold ? (op: nul: v: op v nul) + , docPath ? lib.id + # If the type can contains option sets. + , hasOptions ? false + }: { _type = "option-type"; + inherit name check merge iter fold docPath hasOptions; + }; + + type = { + + infere = mkOptionType { + name = "infered type"; + }; + + enable = mkOptionType { + name = "boolean"; + check = builtins.isBool; + merge = fold lib.or false; + }; + + int = mkOptionType { + name = "integer"; + check = builtins.isInt; + }; + + string = mkOptionType { + name = "string"; + check = x: builtins ? isString -> builtins.isString x; + merge = lib.concatStrings; + }; + + attrs = mkOptionType { + name = "attribute set"; + check = builtins.isAttrs; + merge = fold lib.mergeAttrs {}; + }; + + # derivation is a reserved keyword. + package = mkOptionType { + name = "derivation"; + check = x: builtins.isAttrs x && x ? outPath; + }; + + + list = elemType: mkOptionType { + name = "list of ${elemType.name}s"; + check = value: isList value && all elemType.check value; + merge = concatLists; + iter = f: path: list: map (elemType.iter f (path + ".*")) list; + fold = op: nul: list: lib.fold (e: l: elemType.fold op l e) nul list; + docPath = path: elemType (path + ".*"); + inherit (elemType) hasOptions; + }; + + attrsOf = elemType: mkOptionType { + name = "attribute set of ${elemType}s"; + check = x: builtins.isAttrs x + && fold (e: v: v && elemType.check e) true (lib.attrValues x); + merge = fold lib.mergeAttrs {}; + iter = f: path: set: lib.mapAttrs (name: elemType.iter f (path + "." + name)) set; + fold = op: nul: set: fold (e: l: elemType.fold op l e) nul (lib.attrValues set); + docPath = path: elemType (path + ".<name>"); + inherit (elemType) hasOptions; + }; + + uniq = elemType: mkOptionType { + inherit (elemType) name check iter fold docPath hasOptions; + merge = list: + if tail list == [] then + head list + else + throw "Multiple definitions. Only one is allowed for this option."; + }; + + nullOr = elemType: mkOptionType { + inherit (elemType) name merge docPath hasOptions; + check = x: builtins.isNull x || elemType.check x; + iter = f: path: v: if v == null then v else elemType.iter f path v; + fold = op: nul: v: if v == null then nul else elemType.fold op nul v; + }; + + optionSet = mkOptionType { + name = "option set"; + check = x: builtins.isAttrs x; + hasOptions = true; + }; + + }; + + + # !!! This function will be removed because this can be done with the + # multiple option declarations. addDefaultOptionValues = defs: opts: opts // builtins.listToAttrs (map (defName: { name = defName; @@ -82,6 +296,9 @@ rec { if all isAttrs opts then lib.zip (attr: opts: let + recurseInto = name: attrs: + handleOptionSets optionHandler name attrs; + # Compute the path to reach the attribute. name = if path == "" then attr else path + "." + attr; @@ -90,13 +307,12 @@ rec { test = partition isOption opts; decls = test.right; defs = test.wrong; - # Return the option declaration and add missing default - # attributes. - opt = { - inherit name; - merge = mergeDefaultOption; - apply = lib.id; - } // (head decls); + # Make the option declaration more user-friendly by adding default + # settings and some verifications based on the declaration content + # (like type correctness). + opt = addOptionMakeUp + { inherit name recurseInto; } + (mergeOptionDecls decls); # Return the list of option sets. optAttrs = map delayProperties defs; @@ -105,10 +321,9 @@ rec { # Remove undefined values that are coming from evalIf. optValues = evalProperties defs; in - if decls == [] then handleOptionSets optionHandler name optAttrs + if decls == [] then recurseInto name optAttrs else lib.addErrorContext "while evaluating the option ${name}:" ( - if tail decls != [] then throw "Multiple options." - else export opt optValues + export opt optValues ) ) opts else lib.addErrorContext "while evaluating ${path}:" (notHandle opts); |