summaryrefslogtreecommitdiffstats
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)
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 000000000000..156d72ac5e73
--- /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;
+ };
+
+ };
+
+}