summary refs log tree commit diff
path: root/lib/types.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/types.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/types.nix')
-rw-r--r--lib/types.nix226
1 files changed, 226 insertions, 0 deletions
diff --git a/lib/types.nix b/lib/types.nix
new file mode 100644
index 00000000000..156d72ac5e7
--- /dev/null
+++ b/lib/types.nix
@@ -0,0 +1,226 @@
+# Definitions related to run-time type checking.  Used in particular
+# to type-check NixOS configurations.
+
+let lib = import ./default.nix; in
+
+with import ./lists.nix;
+with import ./attrsets.nix;
+with import ./options.nix;
+with import ./trivial.nix;
+
+rec {
+
+  isType = type: x: (x._type or "") == type;
+  hasType = x: isAttrs x && x ? _type;
+  typeOf = x: x._type or "";
+
+  setType = typeName: value: value // {
+    _type = typeName;
+  };
+
+
+  # name (name of the type)
+  # check (check the config value. Before returning false it should trace the bad value eg using traceValIfNot)
+  # 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)
+  # delayOnGlobalEval (boolean: should properties go through the evaluation of this option)
+  # docPath (path concatenated to the option name contained in the option set)
+  isOptionType = isType "option-type";
+  mkOptionType =
+    { 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
+    , delayOnGlobalEval ? false
+    }:
+
+    { _type = "option-type";
+      inherit name check merge iter fold docPath hasOptions delayOnGlobalEval;
+    };
+
+
+  types = rec {
+
+    bool = mkOptionType {
+      name = "boolean";
+      check = lib.traceValIfNot builtins.isBool;
+      merge = fold lib.or false;
+    };
+
+    int = mkOptionType {
+      name = "integer";
+      check = lib.traceValIfNot builtins.isInt;
+    };
+
+    string = mkOptionType {
+      name = "string";
+      check = lib.traceValIfNot builtins.isString;
+      merge = lib.concatStrings;
+    };
+
+    # Like ‘string’, but add newlines between every value.  Useful for
+    # configuration file contents.
+    lines = mkOptionType {
+      name = "string";
+      check = lib.traceValIfNot builtins.isString;
+      merge = lib.concatStringsSep "\n";
+    };
+
+    envVar = mkOptionType {
+      name = "environment variable";
+      inherit (string) check;
+      merge = lib.concatStringsSep ":";
+    };
+
+    attrs = mkOptionType {
+      name = "attribute set";
+      check = lib.traceValIfNot isAttrs;
+      merge = fold lib.mergeAttrs {};
+    };
+
+    # derivation is a reserved keyword.
+    package = mkOptionType {
+      name = "derivation";
+      check = lib.traceValIfNot isDerivation;
+    };
+
+    path = mkOptionType {
+      name = "path";
+      # Hacky: there is no ‘isPath’ primop.
+      check = lib.traceValIfNot (x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/");
+    };
+
+    # drop this in the future:
+    list = builtins.trace "types.list is deprecated, use types.listOf instead" types.listOf;
+
+    listOf = elemType: mkOptionType { 
+      name = "list of ${elemType.name}s";
+      check = value: lib.traceValIfNot 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.docPath (path + ".*");
+      inherit (elemType) hasOptions;
+
+      # You cannot define multiple configurations of one entity, therefore
+      # no reason justify to delay properties inside list elements.
+      delayOnGlobalEval = false;
+    };
+
+    attrsOf = elemType: mkOptionType {
+      name = "attribute set of ${elemType.name}s";
+      check = x: lib.traceValIfNot isAttrs x
+        && all elemType.check (lib.attrValues x); 
+      merge = lib.zipAttrsWith (name: elemType.merge);
+      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.docPath (path + ".<name>");
+      inherit (elemType) hasOptions delayOnGlobalEval;
+    };
+
+    # List or attribute set of ...
+    loaOf = elemType:
+      let
+        convertIfList = defIdx: def:
+          if isList def then
+            listToAttrs (
+              flip imap def (elemIdx: elem:
+                nameValuePair "unnamed-${toString defIdx}.${toString elemIdx}" elem))
+          else
+            def;
+        listOnly = listOf elemType;
+        attrOnly = attrsOf elemType;
+
+      in mkOptionType {
+        name = "list or attribute set of ${elemType.name}s";
+        check = x:
+          if isList x       then listOnly.check x
+          else if isAttrs x then attrOnly.check x
+          else lib.traceValIfNot (x: false) x;
+        ## The merge function returns an attribute set
+        merge = defs:
+          attrOnly.merge (imap convertIfList defs);
+        iter = f: path: def:
+          if isList def       then listOnly.iter f path def
+          else if isAttrs def then attrOnly.iter f path def
+          else throw "Unexpected value";
+        fold = op: nul: def:
+          if isList def       then listOnly.fold op nul def
+          else if isAttrs def then attrOnly.fold op nul def
+          else throw "Unexpected value";
+
+        docPath = path: elemType.docPath (path + ".<name?>");
+        inherit (elemType) hasOptions delayOnGlobalEval;
+      }
+    ;
+
+    uniq = elemType: mkOptionType {
+      inherit (elemType) name check iter fold docPath hasOptions;
+      merge = list:
+        if length list == 1 then
+          head list
+        else
+          throw "Multiple definitions of ${elemType.name}. Only one is allowed for this option.";
+    };
+
+    none = elemType: mkOptionType {
+      inherit (elemType) name check iter fold docPath hasOptions;
+      merge = list:
+        throw "No definitions are 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;
+    };
+
+    functionTo = elemType: mkOptionType {
+      name = "function that evaluates to a(n) ${elemType.name}";
+      check = lib.traceValIfNot builtins.isFunction;
+      merge = fns:
+        args: elemType.merge (map (fn: fn args) fns);
+      # These are guesses, I don't fully understand iter, fold, delayOnGlobalEval
+      iter = f: path: v:
+        args: elemType.iter f path (v args);
+      fold = op: nul: v:
+        args: elemType.fold op nul (v args);
+      inherit (elemType) delayOnGlobalEval;
+      hasOptions = false;
+    };
+
+    # usually used with listOf, attrsOf, loaOf like this:
+    # users = mkOption {
+    #   type = loaOf optionSet;
+    #
+    #   # you can omit the list if there is one element only
+    #   options = [ {
+    #     name = mkOption {
+    #       description = "name of the user"
+    #       ...
+    #     };
+    #     # more options here
+    #   } { more options } ];
+    # }
+    # TODO: !!! document passing options as an argument to optionSet,
+    # deprecate the current approach.
+    optionSet = mkOptionType {
+      name = "option set";
+      # merge is done in "options.nix > addOptionMakeUp > handleOptionSets"
+      merge = lib.id;
+      check = x: isAttrs x || builtins.isFunction x;
+      hasOptions = true;
+      delayOnGlobalEval = true;
+    };
+
+  };
+
+}