mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-10 03:23:29 +03:00
Add option types.
svn path=/nixpkgs/trunk/; revision=15749
This commit is contained in:
parent
8c2a5ccdcb
commit
49b4942f0e
1 changed files with 227 additions and 12 deletions
|
@ -11,13 +11,227 @@ with import ./attrsets.nix;
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
|
|
||||||
mkOption = attrs: attrs // {_type = "option";};
|
|
||||||
|
|
||||||
hasType = x: isAttrs x && x ? _type;
|
hasType = x: isAttrs x && x ? _type;
|
||||||
typeOf = x: if hasType x then x._type else "";
|
typeOf = x: if hasType x then x._type else "";
|
||||||
|
|
||||||
isOption = attrs: (typeOf attrs) == "option";
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
mergeFromType = opt:
|
||||||
|
if decl ? type && decl.type ? merge then
|
||||||
|
opt // { merge = decl.type.merge; }
|
||||||
|
else
|
||||||
|
opt;
|
||||||
|
|
||||||
|
addDeclaration = opt: opt // decl;
|
||||||
|
|
||||||
|
ensureMergeInputType = opt:
|
||||||
|
if decl ? type then
|
||||||
|
opt // {
|
||||||
|
merge = list:
|
||||||
|
if all decl.type.check list then
|
||||||
|
opt.merge list
|
||||||
|
else
|
||||||
|
throw "One of the definitions has a bad type.";
|
||||||
|
}
|
||||||
|
else opt;
|
||||||
|
|
||||||
|
ensureDefaultType = opt:
|
||||||
|
if decl ? type && decl ? default then
|
||||||
|
opt // {
|
||||||
|
default =
|
||||||
|
if decl.type.check decl.default then
|
||||||
|
decl.default
|
||||||
|
else
|
||||||
|
throw "The default value has a bad type.";
|
||||||
|
}
|
||||||
|
else opt;
|
||||||
|
|
||||||
|
handleOptionSets = opt:
|
||||||
|
if decl ? type && decl.type.hasOptions then
|
||||||
|
opt // {
|
||||||
|
merge = list:
|
||||||
|
decl.type.iter
|
||||||
|
(path: opts: recurseInto path (decl.options ++ [opts]))
|
||||||
|
opt.name
|
||||||
|
(opt.merge list);
|
||||||
|
options = recurseInto (decl.type.docPath opt.name) decl.options;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
opt;
|
||||||
|
in
|
||||||
|
foldl (opt: f: f opt) init [
|
||||||
|
# default settings
|
||||||
|
mergeFromType
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
if opt1 ? options || opt2 ? options then
|
||||||
|
opt1 // opt2 // {
|
||||||
|
options =
|
||||||
|
(toList (attrByPath ["options"] [] opt1))
|
||||||
|
++ (toList (attrByPath ["options"] [] opt2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
opt1 // opt2
|
||||||
|
)) {} opts;
|
||||||
|
|
||||||
|
|
||||||
|
# name (name of the type)
|
||||||
|
# check (boolean function)
|
||||||
|
# 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)
|
||||||
|
# path (path contatenated to the option name contained contained in the option set)
|
||||||
|
isOptionType = attrs: (typeOf attrs) == "option-type";
|
||||||
|
mkOptionType = attrs@{
|
||||||
|
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
|
||||||
|
}: { _type = "option-type";
|
||||||
|
inherit name check merge iter fold docPath hasOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
type = {
|
||||||
|
|
||||||
|
infere = mkOptionType {
|
||||||
|
name = "infered type";
|
||||||
|
};
|
||||||
|
|
||||||
|
enable = mkOptionType {
|
||||||
|
name = "boolean";
|
||||||
|
check = builtins.isBool;
|
||||||
|
merge = fold lib.or false;
|
||||||
|
};
|
||||||
|
|
||||||
|
int = mkOptionType {
|
||||||
|
name = "integer";
|
||||||
|
check = builtins.isInt;
|
||||||
|
};
|
||||||
|
|
||||||
|
string = mkOptionType {
|
||||||
|
name = "string";
|
||||||
|
check = x: builtins ? isString -> builtins.isString x;
|
||||||
|
merge = lib.concatStrings;
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs = mkOptionType {
|
||||||
|
name = "attribute set";
|
||||||
|
check = builtins.isAttrs;
|
||||||
|
merge = fold lib.mergeAttrs {};
|
||||||
|
};
|
||||||
|
|
||||||
|
# derivation is a reserved keyword.
|
||||||
|
package = mkOptionType {
|
||||||
|
name = "derivation";
|
||||||
|
check = x: builtins.isAttrs x && x ? outPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
list = elemType: mkOptionType {
|
||||||
|
name = "list of ${elemType.name}s";
|
||||||
|
check = value: 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 (path + ".*");
|
||||||
|
inherit (elemType) hasOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
attrsOf = elemType: mkOptionType {
|
||||||
|
name = "attribute set of ${elemType}s";
|
||||||
|
check = x: builtins.isAttrs x
|
||||||
|
&& fold (e: v: v && elemType.check e) true (lib.attrValues x);
|
||||||
|
merge = fold lib.mergeAttrs {};
|
||||||
|
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 (path + ".<name>");
|
||||||
|
inherit (elemType) hasOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniq = elemType: mkOptionType {
|
||||||
|
inherit (elemType) name check iter fold docPath hasOptions;
|
||||||
|
merge = list:
|
||||||
|
if tail list == [] then
|
||||||
|
head list
|
||||||
|
else
|
||||||
|
throw "Multiple definitions. Only one is 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
optionSet = mkOptionType {
|
||||||
|
name = "option set";
|
||||||
|
check = x: builtins.isAttrs x;
|
||||||
|
hasOptions = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# !!! This function will be removed because this can be done with the
|
||||||
|
# multiple option declarations.
|
||||||
addDefaultOptionValues = defs: opts: opts //
|
addDefaultOptionValues = defs: opts: opts //
|
||||||
builtins.listToAttrs (map (defName:
|
builtins.listToAttrs (map (defName:
|
||||||
{ name = defName;
|
{ name = defName;
|
||||||
|
@ -82,6 +296,9 @@ rec {
|
||||||
if all isAttrs opts then
|
if all isAttrs opts then
|
||||||
lib.zip (attr: opts:
|
lib.zip (attr: opts:
|
||||||
let
|
let
|
||||||
|
recurseInto = name: attrs:
|
||||||
|
handleOptionSets optionHandler name attrs;
|
||||||
|
|
||||||
# Compute the path to reach the attribute.
|
# Compute the path to reach the attribute.
|
||||||
name = if path == "" then attr else path + "." + attr;
|
name = if path == "" then attr else path + "." + attr;
|
||||||
|
|
||||||
|
@ -90,13 +307,12 @@ rec {
|
||||||
test = partition isOption opts;
|
test = partition isOption opts;
|
||||||
decls = test.right; defs = test.wrong;
|
decls = test.right; defs = test.wrong;
|
||||||
|
|
||||||
# Return the option declaration and add missing default
|
# Make the option declaration more user-friendly by adding default
|
||||||
# attributes.
|
# settings and some verifications based on the declaration content
|
||||||
opt = {
|
# (like type correctness).
|
||||||
inherit name;
|
opt = addOptionMakeUp
|
||||||
merge = mergeDefaultOption;
|
{ inherit name recurseInto; }
|
||||||
apply = lib.id;
|
(mergeOptionDecls decls);
|
||||||
} // (head decls);
|
|
||||||
|
|
||||||
# Return the list of option sets.
|
# Return the list of option sets.
|
||||||
optAttrs = map delayProperties defs;
|
optAttrs = map delayProperties defs;
|
||||||
|
@ -105,10 +321,9 @@ rec {
|
||||||
# Remove undefined values that are coming from evalIf.
|
# Remove undefined values that are coming from evalIf.
|
||||||
optValues = evalProperties defs;
|
optValues = evalProperties defs;
|
||||||
in
|
in
|
||||||
if decls == [] then handleOptionSets optionHandler name optAttrs
|
if decls == [] then recurseInto name optAttrs
|
||||||
else lib.addErrorContext "while evaluating the option ${name}:" (
|
else lib.addErrorContext "while evaluating the option ${name}:" (
|
||||||
if tail decls != [] then throw "Multiple options."
|
export opt optValues
|
||||||
else export opt optValues
|
|
||||||
)
|
)
|
||||||
) opts
|
) opts
|
||||||
else lib.addErrorContext "while evaluating ${path}:" (notHandle opts);
|
else lib.addErrorContext "while evaluating ${path}:" (notHandle opts);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue