diff options
author | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2013-10-10 13:28:21 +0200 |
---|---|---|
committer | Eelco Dolstra <eelco.dolstra@logicblox.com> | 2013-10-10 13:28:21 +0200 |
commit | 5fef92c4a0c91153e3edac3a61a232581765074a (patch) | |
tree | 291d684d0ef71e200e6d8ab5c33fc1aca467cbb3 /lib/options.nix | |
parent | 2a537fb369d1479748fe233261eaadfa5c2fa930 (diff) | |
download | nixpkgs-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.nix | 315 |
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; }; + + +} |