# Nixpkgs/NixOS option handling. let lib = import ./default.nix; in with { inherit (builtins) head tail; }; with import ./trivial.nix; with import ./lists.nix; with import ./misc.nix; with import ./attrsets.nix; with import ./properties.nix; rec { inherit (lib) typeOf; isOption = attrs: (typeOf attrs) == "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) }; mapSubOptions = f: opt: if opt ? options then opt // { options = map 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: if decl ? type && decl.type ? merge then opt // optionalAttrs (decl.type ? merge) { inherit (decl.type) merge; } // optionalAttrs (decl.type ? check) { inherit (decl.type) check; } else opt; 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 the definitions has a bad type."; } else opt; ensureDefaultType = opt: if opt ? check && opt ? default then opt // { default = if opt.check opt.default then opt.default else throw "The default value has a bad type."; } else opt; handleOptionSets = opt: if opt ? type && opt.type.hasOptions then let optionConfig = vals: args: map (f: lib.applyIfFunction f args) (opt.options ++ toList vals); # Evaluate sub-modules. subModuleMerge = path: vals: lib.fix (args: let result = recurseInto path (optionConfig vals args); in { inherit (result) config options; name = lib.removePrefix (opt.name + ".") path; } ); # 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 ensureDefaultType 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 tail opts == [] 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 (attrByPath ["options"] [] opt1)) ++ (toList (attrByPath ["options"] [] opt2)); } // optionalAttrs (opt1 ? extraConfigs || opt2 ? extraConfigs) { extraConfigs = (attrByPath ["extraConfigs"] [] opt1) ++ (attrByPath ["extraConfigs"] [] opt2); } )) {} 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 typeOf defValue == "option" 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 list != [] && tail list == [] 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 tail list != [] 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; }