summary refs log tree commit diff
path: root/lib/options.nix
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:21 +0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2013-10-10 13:28:21 +0200
commit5fef92c4a0c91153e3edac3a61a232581765074a (patch)
tree291d684d0ef71e200e6d8ab5c33fc1aca467cbb3 /lib/options.nix
parent2a537fb369d1479748fe233261eaadfa5c2fa930 (diff)
downloadnixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.tar
nixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.tar.gz
nixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.tar.bz2
nixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.tar.lz
nixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.tar.xz
nixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.tar.zst
nixpkgs-5fef92c4a0c91153e3edac3a61a232581765074a.zip
Move pkgs/lib/ to lib/
Diffstat (limited to 'lib/options.nix')
-rw-r--r--lib/options.nix315
1 files changed, 315 insertions, 0 deletions
diff --git a/lib/options.nix b/lib/options.nix
new file mode 100644
index 00000000000..e8e01083a77
--- /dev/null
+++ b/lib/options.nix
@@ -0,0 +1,315 @@
+# Nixpkgs/NixOS option handling.
+
+let lib = import ./default.nix; in
+
+with { inherit (builtins) head length; };
+with import ./trivial.nix;
+with import ./lists.nix;
+with import ./misc.nix;
+with import ./attrsets.nix;
+with import ./properties.nix;
+
+rec {
+
+  inherit (lib) isType;
+  
+
+  isOption = isType "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)
+    # extraConfigs (list of possible configurations)
+  };
+
+  mkEnableOption = name: mkOption {
+    default = false;
+    example = true;
+    description = "Whether to enable ${name}";
+    type = lib.types.bool;
+  };
+
+  mapSubOptions = f: opt:
+    if opt ? options then
+      opt // {
+        options = imap f (toList opt.options);
+      }
+    else
+      opt;
+
+  # 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;
+      };
+
+      functionsFromType = opt:
+        opt // (builtins.intersectAttrs { merge = 1; check = 1; } (decl.type or {})); 
+
+      addDeclaration = opt: opt // decl;
+
+      ensureMergeInputType = opt:
+        if opt ? check then
+          opt // {
+            merge = list:
+              if all opt.check list then
+                opt.merge list
+              else
+                throw "One of option ${name} values has a bad type.";
+          }
+        else opt;
+
+      checkDefault = opt:
+        if opt ? check && opt ? default then
+          opt // {
+            default =
+              if opt.check opt.default then
+                opt.default
+              else
+                throw "The default value of option ${name} has a bad type.";
+          }
+        else opt;
+
+      handleOptionSets = opt:
+        if opt ? type && opt.type.hasOptions then
+          let
+            # Evaluate sub-modules.
+            subModuleMerge = path: vals:
+              lib.fix (args:
+                let
+                  result = recurseInto path (opt.options ++ imap (index: v: args: {
+                    key = rec {
+                      #!!! Would be nice if we had the file the val was from
+                      option = path;
+                      number = index;
+                      outPath = "option ${option} config number ${toString number}";
+                    };
+                  } // (lib.applyIfFunction v args)) (toList vals)) args;
+                  name = lib.removePrefix (opt.name + ".") path;
+                  extraArgs = opt.extraArgs or {};
+                  individualExtraArgs = opt.individualExtraArgs or {};
+                in {
+                  inherit (result) config options;
+                  inherit name;
+                } //
+                  (opt.extraArgs or {}) //
+                  (if hasAttr name individualExtraArgs then getAttr name individualExtraArgs else {})
+              );
+
+            # Add _options in sub-modules to make it viewable from other
+            # modules.
+            subModuleMergeConfig = path: vals:
+              let result = subModuleMerge path vals; in
+                { _args = result; } // result.config;
+
+          in
+            opt // {
+              merge = list:
+                opt.type.iter
+                  subModuleMergeConfig
+                  opt.name
+                  (opt.merge list);
+              options =
+                let path = opt.type.docPath opt.name; in
+                  (subModuleMerge path []).options;
+            }
+        else
+          opt;
+    in
+      foldl (opt: f: f opt) init [
+        # default settings
+        functionsFromType
+
+        # user settings
+        addDeclaration
+
+        # override settings
+        ensureMergeInputType
+        checkDefault
+        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 length opts == 1 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;
+        opt1 // opt2
+        // optionalAttrs (opt1 ? options || opt2 ? options) {
+            options =
+               (toList (opt1.options or []))
+            ++ (toList (opt2.options or []));
+          }
+        // optionalAttrs (opt1 ? extraConfigs || opt2 ? extraConfigs) {
+            extraConfigs = opt1.extraConfigs or [] ++ opt2.extraConfigs or [];
+          }
+        // optionalAttrs (opt1 ? extraArgs || opt2 ? extraArgs) {
+            extraArgs = opt1.extraArgs or {} // opt2.extraArgs or {};
+          }
+        // optionalAttrs (opt1 ? individualExtraArgs || opt2 ? individualExtraArgs) {
+            individualExtraArgs = zipAttrsWith (name: values:
+              if length values == 1 then head values else (head values // (head (tail values)))
+            ) [ (opt1.individualExtraArgs or {}) (opt2.individualExtraArgs or {}) ];
+          }
+      )) {} opts;
+
+  
+  # !!! 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;
+        value = 
+          let
+            defValue = builtins.getAttr defName defs;
+            optValue = builtins.getAttr defName opts;
+          in
+          if isOption defValue
+          then
+            # `defValue' is an option.
+            if hasAttr defName opts
+            then builtins.getAttr defName opts
+            else defValue.default
+          else
+            # `defValue' is an attribute set containing options.
+            # So recurse.
+            if hasAttr defName opts && isAttrs optValue 
+            then addDefaultOptionValues defValue optValue
+            else addDefaultOptionValues defValue {};
+      }
+    ) (attrNames defs));
+
+  mergeDefaultOption = list:
+    if length list == 1 then head list
+    else if all builtins.isFunction list then x: mergeDefaultOption (map (f: f x) list)
+    else if all isList list then concatLists list
+    else if all isAttrs list then fold lib.mergeAttrs {} list
+    else if all builtins.isBool list then fold lib.or false list
+    else if all builtins.isString list then lib.concatStrings list
+    else if all builtins.isInt list && all (x: x == head list) list
+         then head list
+    else throw "Cannot merge values.";
+
+  mergeTypedOption = typeName: predicate: merge: list:
+    if all predicate list then merge list
+    else throw "Expect a ${typeName}.";
+
+  mergeEnableOption = mergeTypedOption "boolean"
+    (x: true == x || false == x) (fold lib.or false);
+
+  mergeListOption = mergeTypedOption "list" isList concatLists;
+
+  mergeStringOption = mergeTypedOption "string"
+    (x: if builtins ? isString then builtins.isString x else x + "")
+    lib.concatStrings;
+
+  mergeOneOption = list:
+    if list == [] then abort "This case should never happen."
+    else if length list != 1 then throw "Multiple definitions. Only one is allowed for this option."
+    else head list;
+
+
+  fixableMergeFun = merge: f: config:
+    merge (
+      # generate the list of option sets.
+      f config
+    );
+
+  fixableMergeModules = merge: initModules: {...}@args: config:
+    fixableMergeFun merge (config:
+      lib.moduleClosure initModules (args // { inherit config; })
+    ) config;
+
+
+  fixableDefinitionsOf = initModules: {...}@args:
+    fixableMergeModules (modules: (lib.moduleMerge "" modules).config) initModules args;
+
+  fixableDeclarationsOf = initModules: {...}@args:
+    fixableMergeModules (modules: (lib.moduleMerge "" modules).options) initModules args;
+
+  definitionsOf = initModules: {...}@args:
+    (lib.fix (module:
+      fixableMergeModules (lib.moduleMerge "") initModules args module.config
+    )).config;
+
+  declarationsOf = initModules: {...}@args:
+    (lib.fix (module:
+      fixableMergeModules (lib.moduleMerge "") initModules args module.config
+    )).options;
+
+
+  # Generate documentation template from the list of option declaration like
+  # the set generated with filterOptionSets.
+  optionAttrSetToDocList = ignore: newOptionAttrSetToDocList;
+  newOptionAttrSetToDocList = attrs:
+    let options = collect isOption attrs; in
+      fold (opt: rest:
+        let
+          docOption = {
+            inherit (opt) name;
+            description = if opt ? description then opt.description else
+              throw "Option ${opt.name}: No description.";
+
+            declarations = map (x: toString x.source) opt.declarations;
+            #definitions = map (x: toString x.source) opt.definitions;
+          }
+          // optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
+          // optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
+          // optionalAttrs (opt ? defaultText) { default = opt.defaultText; };
+
+          subOptions =
+            if opt ? options then
+              newOptionAttrSetToDocList opt.options
+            else
+              [];
+        in
+          [ docOption ] ++ subOptions ++ rest
+      ) [] options;
+
+
+  /* This function recursively removes all derivation attributes from
+     `x' except for the `name' attribute.  This is to make the
+     generation of `options.xml' much more efficient: the XML
+     representation of derivations is very large (on the order of
+     megabytes) and is not actually used by the manual generator. */
+  scrubOptionValue = x: 
+    if isDerivation x then { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
+    else if isList x then map scrubOptionValue x
+    else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
+    else x;
+
+
+  /* For use in the ‘example’ option attribute.  It causes the given
+     text to be included verbatim in documentation.  This is necessary
+     for example values that are not simple values, e.g.,
+     functions. */
+  literalExample = text: { _type = "literalExample"; inherit text; };
+
+
+}