From 5fef92c4a0c91153e3edac3a61a232581765074a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Oct 2013 13:28:21 +0200 Subject: Move pkgs/lib/ to lib/ --- lib/types.nix | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 lib/types.nix (limited to 'lib/types.nix') 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 + "."); + 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 + "."); + 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; + }; + + }; + +} -- cgit v1.2.3