summary refs log tree commit diff
path: root/pkgs/lib/options.nix
diff options
context:
space:
mode:
authorNicolas Pierron <nicolas.b.pierron@gmail.com>2009-05-27 20:25:17 +0000
committerNicolas Pierron <nicolas.b.pierron@gmail.com>2009-05-27 20:25:17 +0000
commit49b4942f0e8455d41ad02ffdfac0a6b01c25215c (patch)
tree2f1373750593ea7e879e538dbbb1df0974601a8b /pkgs/lib/options.nix
parent8c2a5ccdcb17607aacc9e46c8e9b280d68440b54 (diff)
downloadnixpkgs-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.nix239
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);