mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-01-22 14:45:27 +00:00
module system: extensible option types
This commit is contained in:
parent
d10356b825
commit
e14de56613
|
@ -231,12 +231,20 @@ rec {
|
||||||
correspond to the definition of 'loc' in 'opt.file'. */
|
correspond to the definition of 'loc' in 'opt.file'. */
|
||||||
mergeOptionDecls = loc: opts:
|
mergeOptionDecls = loc: opts:
|
||||||
foldl' (res: opt:
|
foldl' (res: opt:
|
||||||
if opt.options ? default && res ? default ||
|
let t = res.type;
|
||||||
opt.options ? example && res ? example ||
|
t' = opt.options.type;
|
||||||
opt.options ? description && res ? description ||
|
mergedType = t.typeMerge t'.functor;
|
||||||
opt.options ? apply && res ? apply ||
|
typesMergeable = mergedType != null;
|
||||||
# Accept to merge options which have identical types.
|
typeSet = if (bothHave "type") && typesMergeable
|
||||||
opt.options ? type && res ? type && opt.options.type.name != res.type.name
|
then { type = mergedType; }
|
||||||
|
else {};
|
||||||
|
bothHave = k: opt.options ? ${k} && res ? ${k};
|
||||||
|
in
|
||||||
|
if bothHave "default" ||
|
||||||
|
bothHave "example" ||
|
||||||
|
bothHave "description" ||
|
||||||
|
bothHave "apply" ||
|
||||||
|
(bothHave "type" && (! typesMergeable))
|
||||||
then
|
then
|
||||||
throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
|
throw "The option `${showOption loc}' in `${opt.file}' is already declared in ${showFiles res.declarations}."
|
||||||
else
|
else
|
||||||
|
@ -258,7 +266,7 @@ rec {
|
||||||
in opt.options // res //
|
in opt.options // res //
|
||||||
{ declarations = res.declarations ++ [opt.file];
|
{ declarations = res.declarations ++ [opt.file];
|
||||||
options = submodules;
|
options = submodules;
|
||||||
}
|
} // typeSet
|
||||||
) { inherit loc; declarations = []; options = []; } opts;
|
) { inherit loc; declarations = []; options = []; } opts;
|
||||||
|
|
||||||
/* Merge all the definitions of an option to produce the final
|
/* Merge all the definitions of an option to produce the final
|
||||||
|
@ -422,12 +430,14 @@ rec {
|
||||||
options = opt.options or
|
options = opt.options or
|
||||||
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
|
(throw "Option `${showOption loc'}' has type optionSet but has no option attribute, in ${showFiles opt.declarations}.");
|
||||||
f = tp:
|
f = tp:
|
||||||
|
let optionSetIn = type: (tp.name == type) && (tp.functor.wrapped.name == "optionSet");
|
||||||
|
in
|
||||||
if tp.name == "option set" || tp.name == "submodule" then
|
if tp.name == "option set" || tp.name == "submodule" then
|
||||||
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
|
throw "The option ${showOption loc} uses submodules without a wrapping type, in ${showFiles opt.declarations}."
|
||||||
else if tp.name == "attribute set of option sets" then types.attrsOf (types.submodule options)
|
else if optionSetIn "attrsOf" then types.attrsOf (types.submodule options)
|
||||||
else if tp.name == "list or attribute set of option sets" then types.loaOf (types.submodule options)
|
else if optionSetIn "loaOf" then types.loaOf (types.submodule options)
|
||||||
else if tp.name == "list of option sets" then types.listOf (types.submodule options)
|
else if optionSetIn "listOf" then types.listOf (types.submodule options)
|
||||||
else if tp.name == "null or option set" then types.nullOr (types.submodule options)
|
else if optionSetIn "nullOr" then types.nullOr (types.submodule options)
|
||||||
else tp;
|
else tp;
|
||||||
in
|
in
|
||||||
if opt.type.getSubModules or null == null
|
if opt.type.getSubModules or null == null
|
||||||
|
|
|
@ -92,7 +92,7 @@ rec {
|
||||||
internal = opt.internal or false;
|
internal = opt.internal or false;
|
||||||
visible = opt.visible or true;
|
visible = opt.visible or true;
|
||||||
readOnly = opt.readOnly or false;
|
readOnly = opt.readOnly or false;
|
||||||
type = opt.type.name or null;
|
type = opt.type.description or null;
|
||||||
}
|
}
|
||||||
// (if opt ? example then { example = scrubOptionValue opt.example; } else {})
|
// (if opt ? example then { example = scrubOptionValue opt.example; } else {})
|
||||||
// (if opt ? default then { default = scrubOptionValue opt.default; } else {})
|
// (if opt ? default then { default = scrubOptionValue opt.default; } else {})
|
||||||
|
|
142
lib/types.nix
142
lib/types.nix
|
@ -17,10 +17,43 @@ rec {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# Default type merging function
|
||||||
|
# takes two type functors and return the merged type
|
||||||
|
defaultTypeMerge = f: f':
|
||||||
|
let wrapped = f.wrapped.typeMerge f'.wrapped.functor;
|
||||||
|
payload = f.binOp f.payload f'.payload;
|
||||||
|
in
|
||||||
|
# cannot merge different types
|
||||||
|
if f.name != f'.name
|
||||||
|
then null
|
||||||
|
# simple types
|
||||||
|
else if (f.wrapped == null && f'.wrapped == null)
|
||||||
|
&& (f.payload == null && f'.payload == null)
|
||||||
|
then f.type
|
||||||
|
# composed types
|
||||||
|
else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
|
||||||
|
then f.type wrapped
|
||||||
|
# value types
|
||||||
|
else if (f.payload != null && f'.payload != null) && (payload != null)
|
||||||
|
then f.type payload
|
||||||
|
else null;
|
||||||
|
|
||||||
|
# Default type functor
|
||||||
|
defaultFunctor = name: {
|
||||||
|
inherit name;
|
||||||
|
type = types."${name}" or null;
|
||||||
|
wrapped = null;
|
||||||
|
payload = null;
|
||||||
|
binOp = a: b: null;
|
||||||
|
};
|
||||||
|
|
||||||
isOptionType = isType "option-type";
|
isOptionType = isType "option-type";
|
||||||
mkOptionType =
|
mkOptionType =
|
||||||
{ # Human-readable representation of the type.
|
{ # Human-readable representation of the type, should be equivalent to
|
||||||
|
# the type function name.
|
||||||
name
|
name
|
||||||
|
, # Description of the type, defined recursively by embedding the the wrapped type if any.
|
||||||
|
description ? null
|
||||||
, # Function applied to each definition that should return true if
|
, # Function applied to each definition that should return true if
|
||||||
# its type-correct, false otherwise.
|
# its type-correct, false otherwise.
|
||||||
check ? (x: true)
|
check ? (x: true)
|
||||||
|
@ -36,12 +69,26 @@ rec {
|
||||||
getSubOptions ? prefix: {}
|
getSubOptions ? prefix: {}
|
||||||
, # List of modules if any, or null if none.
|
, # List of modules if any, or null if none.
|
||||||
getSubModules ? null
|
getSubModules ? null
|
||||||
, # Function for building the same option type with a different list of
|
, # Function for building the same option type with a different list of
|
||||||
# modules.
|
# modules.
|
||||||
substSubModules ? m: null
|
substSubModules ? m: null
|
||||||
|
, # Function that merge type declarations.
|
||||||
|
# internal, takes a functor as argument and returns the merged type.
|
||||||
|
# returning null means the type is not mergeable
|
||||||
|
typeMerge ? defaultTypeMerge functor
|
||||||
|
, # The type functor.
|
||||||
|
# internal, representation of the type as an attribute set.
|
||||||
|
# name: name of the type
|
||||||
|
# type: type function.
|
||||||
|
# wrapped: the type wrapped in case of compound types.
|
||||||
|
# payload: values of the type, two payloads of the same type must be
|
||||||
|
# combinable with the binOp binary operation.
|
||||||
|
# binOp: binary operation that merge two payloads of the same type.
|
||||||
|
functor ? defaultFunctor name
|
||||||
}:
|
}:
|
||||||
{ _type = "option-type";
|
{ _type = "option-type";
|
||||||
inherit name check merge getSubOptions getSubModules substSubModules;
|
inherit name check merge getSubOptions getSubModules substSubModules typeMerge functor;
|
||||||
|
description = if description == null then name else description;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,29 +99,39 @@ rec {
|
||||||
};
|
};
|
||||||
|
|
||||||
bool = mkOptionType {
|
bool = mkOptionType {
|
||||||
name = "boolean";
|
name = "bool";
|
||||||
|
description = "boolean";
|
||||||
check = isBool;
|
check = isBool;
|
||||||
merge = mergeEqualOption;
|
merge = mergeEqualOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
int = mkOptionType {
|
int = mkOptionType rec {
|
||||||
name = "integer";
|
name = "int";
|
||||||
|
description = "integer";
|
||||||
check = isInt;
|
check = isInt;
|
||||||
merge = mergeOneOption;
|
merge = mergeOneOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
str = mkOptionType {
|
str = mkOptionType {
|
||||||
name = "string";
|
name = "str";
|
||||||
|
description = "string";
|
||||||
check = isString;
|
check = isString;
|
||||||
merge = mergeOneOption;
|
merge = mergeOneOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Merge multiple definitions by concatenating them (with the given
|
# Merge multiple definitions by concatenating them (with the given
|
||||||
# separator between the values).
|
# separator between the values).
|
||||||
separatedString = sep: mkOptionType {
|
separatedString = sep: mkOptionType rec {
|
||||||
name = "string";
|
name = "separatedString";
|
||||||
|
description = "string";
|
||||||
check = isString;
|
check = isString;
|
||||||
merge = loc: defs: concatStringsSep sep (getValues defs);
|
merge = loc: defs: concatStringsSep sep (getValues defs);
|
||||||
|
functor = (defaultFunctor name) // {
|
||||||
|
payload = sep;
|
||||||
|
binOp = sepLhs: sepRhs:
|
||||||
|
if sepLhs == sepRhs then sepLhs
|
||||||
|
else null;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
lines = separatedString "\n";
|
lines = separatedString "\n";
|
||||||
|
@ -86,7 +143,8 @@ rec {
|
||||||
string = separatedString "";
|
string = separatedString "";
|
||||||
|
|
||||||
attrs = mkOptionType {
|
attrs = mkOptionType {
|
||||||
name = "attribute set";
|
name = "attrs";
|
||||||
|
description = "attribute set";
|
||||||
check = isAttrs;
|
check = isAttrs;
|
||||||
merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
|
merge = loc: foldl' (res: def: mergeAttrs res def.value) {};
|
||||||
};
|
};
|
||||||
|
@ -114,8 +172,9 @@ rec {
|
||||||
# drop this in the future:
|
# drop this in the future:
|
||||||
list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
|
list = builtins.trace "`types.list' is deprecated; use `types.listOf' instead" types.listOf;
|
||||||
|
|
||||||
listOf = elemType: mkOptionType {
|
listOf = elemType: mkOptionType rec {
|
||||||
name = "list of ${elemType.name}s";
|
name = "listOf";
|
||||||
|
description = "list of ${elemType.description}s";
|
||||||
check = isList;
|
check = isList;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def:
|
map (x: x.value) (filter (x: x ? value) (concatLists (imap (n: def:
|
||||||
|
@ -132,10 +191,12 @@ rec {
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: listOf (elemType.substSubModules m);
|
substSubModules = m: listOf (elemType.substSubModules m);
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
attrsOf = elemType: mkOptionType {
|
attrsOf = elemType: mkOptionType rec {
|
||||||
name = "attribute set of ${elemType.name}s";
|
name = "attrsOf";
|
||||||
|
description = "attribute set of ${elemType.description}s";
|
||||||
check = isAttrs;
|
check = isAttrs;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
|
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
|
||||||
|
@ -147,6 +208,7 @@ rec {
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: attrsOf (elemType.substSubModules m);
|
substSubModules = m: attrsOf (elemType.substSubModules m);
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
# List or attribute set of ...
|
# List or attribute set of ...
|
||||||
|
@ -165,18 +227,21 @@ rec {
|
||||||
def;
|
def;
|
||||||
listOnly = listOf elemType;
|
listOnly = listOf elemType;
|
||||||
attrOnly = attrsOf elemType;
|
attrOnly = attrsOf elemType;
|
||||||
in mkOptionType {
|
in mkOptionType rec {
|
||||||
name = "list or attribute set of ${elemType.name}s";
|
name = "loaOf";
|
||||||
|
description = "list or attribute set of ${elemType.description}s";
|
||||||
check = x: isList x || isAttrs x;
|
check = x: isList x || isAttrs x;
|
||||||
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
merge = loc: defs: attrOnly.merge loc (imap convertIfList defs);
|
||||||
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: loaOf (elemType.substSubModules m);
|
substSubModules = m: loaOf (elemType.substSubModules m);
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
# List or element of ...
|
# List or element of ...
|
||||||
loeOf = elemType: mkOptionType {
|
loeOf = elemType: mkOptionType rec {
|
||||||
name = "element or list of ${elemType.name}s";
|
name = "loeOf";
|
||||||
|
description = "element or list of ${elemType.description}s";
|
||||||
check = x: isList x || elemType.check x;
|
check = x: isList x || elemType.check x;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
let
|
let
|
||||||
|
@ -189,18 +254,22 @@ rec {
|
||||||
else if !isString res then
|
else if !isString res then
|
||||||
throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
|
throw "The option `${showOption loc}' does not have a string value, in ${showFiles (getFiles defs)}."
|
||||||
else res;
|
else res;
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
uniq = elemType: mkOptionType {
|
uniq = elemType: mkOptionType rec {
|
||||||
inherit (elemType) name check;
|
name = "uniq";
|
||||||
|
inherit (elemType) description check;
|
||||||
merge = mergeOneOption;
|
merge = mergeOneOption;
|
||||||
getSubOptions = elemType.getSubOptions;
|
getSubOptions = elemType.getSubOptions;
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: uniq (elemType.substSubModules m);
|
substSubModules = m: uniq (elemType.substSubModules m);
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
nullOr = elemType: mkOptionType {
|
nullOr = elemType: mkOptionType rec {
|
||||||
name = "null or ${elemType.name}";
|
name = "nullOr";
|
||||||
|
description = "null or ${elemType.description}";
|
||||||
check = x: x == null || elemType.check x;
|
check = x: x == null || elemType.check x;
|
||||||
merge = loc: defs:
|
merge = loc: defs:
|
||||||
let nrNulls = count (def: def.value == null) defs; in
|
let nrNulls = count (def: def.value == null) defs; in
|
||||||
|
@ -211,6 +280,7 @@ rec {
|
||||||
getSubOptions = elemType.getSubOptions;
|
getSubOptions = elemType.getSubOptions;
|
||||||
getSubModules = elemType.getSubModules;
|
getSubModules = elemType.getSubModules;
|
||||||
substSubModules = m: nullOr (elemType.substSubModules m);
|
substSubModules = m: nullOr (elemType.substSubModules m);
|
||||||
|
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||||
};
|
};
|
||||||
|
|
||||||
submodule = opts:
|
submodule = opts:
|
||||||
|
@ -236,6 +306,12 @@ rec {
|
||||||
args = { name = ""; }; }).options;
|
args = { name = ""; }; }).options;
|
||||||
getSubModules = opts';
|
getSubModules = opts';
|
||||||
substSubModules = m: submodule m;
|
substSubModules = m: submodule m;
|
||||||
|
functor = (defaultFunctor name) // {
|
||||||
|
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
|
||||||
|
# each submodule with its location.
|
||||||
|
payload = [];
|
||||||
|
binOp = lhs: rhs: [];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
enum = values:
|
enum = values:
|
||||||
|
@ -245,23 +321,35 @@ rec {
|
||||||
else if builtins.isInt v then builtins.toString v
|
else if builtins.isInt v then builtins.toString v
|
||||||
else ''<${builtins.typeOf v}>'';
|
else ''<${builtins.typeOf v}>'';
|
||||||
in
|
in
|
||||||
mkOptionType {
|
mkOptionType rec {
|
||||||
name = "one of ${concatMapStringsSep ", " show values}";
|
name = "enum";
|
||||||
|
description = "one of ${concatMapStringsSep ", " show values}";
|
||||||
check = flip elem values;
|
check = flip elem values;
|
||||||
merge = mergeOneOption;
|
merge = mergeOneOption;
|
||||||
|
functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
|
||||||
};
|
};
|
||||||
|
|
||||||
either = t1: t2: mkOptionType {
|
either = t1: t2: mkOptionType rec {
|
||||||
name = "${t1.name} or ${t2.name}";
|
name = "either";
|
||||||
|
description = "${t1.description} or ${t2.description}";
|
||||||
check = x: t1.check x || t2.check x;
|
check = x: t1.check x || t2.check x;
|
||||||
merge = mergeOneOption;
|
merge = mergeOneOption;
|
||||||
|
typeMerge = f':
|
||||||
|
let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
|
||||||
|
mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
|
||||||
|
in
|
||||||
|
if (name == f'.name) && (mt1 != null) && (mt2 != null)
|
||||||
|
then functor.type mt1 mt2
|
||||||
|
else null;
|
||||||
|
functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
|
||||||
};
|
};
|
||||||
|
|
||||||
# Obsolete alternative to configOf. It takes its option
|
# Obsolete alternative to configOf. It takes its option
|
||||||
# declarations from the ‘options’ attribute of containing option
|
# declarations from the ‘options’ attribute of containing option
|
||||||
# declaration.
|
# declaration.
|
||||||
optionSet = mkOptionType {
|
optionSet = mkOptionType {
|
||||||
name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "option set";
|
name = builtins.trace "types.optionSet is deprecated; use types.submodule instead" "optionSet";
|
||||||
|
description = "option set";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Augment the given type with an additional type check function.
|
# Augment the given type with an additional type check function.
|
||||||
|
|
|
@ -65,4 +65,92 @@ options = {
|
||||||
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<section xml:id="sec-option-declarations-eot"><title>Extensible Option
|
||||||
|
Types</title>
|
||||||
|
|
||||||
|
<para>Extensible option types is a feature that allow to extend certain types
|
||||||
|
declaration through multiple module files.
|
||||||
|
This feature only work with a restricted set of types, namely
|
||||||
|
<literal>enum</literal> and <literal>submodules</literal> and any composed
|
||||||
|
forms of them.</para>
|
||||||
|
|
||||||
|
<para>Extensible option types can be used for <literal>enum</literal> options
|
||||||
|
that affects multiple modules, or as an alternative to related
|
||||||
|
<literal>enable</literal> options.</para>
|
||||||
|
|
||||||
|
<para>As an example, we will take the case of display managers. There is a
|
||||||
|
central display manager module for generic display manager options and a
|
||||||
|
module file per display manager backend (slim, kdm, gdm ...).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>There are two approach to this module structure:
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem><para>Managing the display managers independently by adding an
|
||||||
|
enable option to every display manager module backend. (NixOS)</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem><para>Managing the display managers in the central module by
|
||||||
|
adding an option to select which display manager backend to use.</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>Both approachs have problems.</para>
|
||||||
|
|
||||||
|
<para>Making backends independent can quickly become hard to manage. For
|
||||||
|
display managers, there can be only one enabled at a time, but the type
|
||||||
|
system can not enforce this restriction as there is no relation between
|
||||||
|
each backend <literal>enable</literal> option. As a result, this restriction
|
||||||
|
has to be done explicitely by adding assertions in each display manager
|
||||||
|
backend module.</para>
|
||||||
|
|
||||||
|
<para>On the other hand, managing the display managers backends in the
|
||||||
|
central module will require to change the central module option every time
|
||||||
|
a new backend is added or removed.</para>
|
||||||
|
|
||||||
|
<para>By using extensible option types, it is possible to create a placeholder
|
||||||
|
option in the central module (<xref linkend='ex-option-declaration-eot-service'
|
||||||
|
/>), and to extend it in each backend module (<xref
|
||||||
|
linkend='ex-option-declaration-eot-backend-slim' />, <xref
|
||||||
|
linkend='ex-option-declaration-eot-backend-kdm' />).</para>
|
||||||
|
|
||||||
|
<para>As a result, <literal>displayManager.enable</literal> option values can
|
||||||
|
be added without changing the main service module file and the type system
|
||||||
|
automatically enforce that there can only be a single display manager
|
||||||
|
enabled.</para>
|
||||||
|
|
||||||
|
<example xml:id='ex-option-declaration-eot-service'><title>Extensible type
|
||||||
|
placeholder in the service module</title>
|
||||||
|
<screen>
|
||||||
|
services.xserver.displayManager.enable = mkOption {
|
||||||
|
description = "Display manager to use";
|
||||||
|
type = with types; nullOr (enum [ ]);
|
||||||
|
};</screen></example>
|
||||||
|
|
||||||
|
<example xml:id='ex-option-declaration-eot-backend-slim'><title>Extending
|
||||||
|
<literal>services.xserver.displayManager.enable</literal> in the
|
||||||
|
<literal>slim</literal> module</title>
|
||||||
|
<screen>
|
||||||
|
services.xserver.displayManager.enable = mkOption {
|
||||||
|
type = with types; nullOr (enum [ "slim" ]);
|
||||||
|
};</screen></example>
|
||||||
|
|
||||||
|
<example xml:id='ex-option-declaration-eot-backend-kdm'><title>Extending
|
||||||
|
<literal>services.foo.backend</literal> in the <literal>kdm</literal>
|
||||||
|
module</title>
|
||||||
|
<screen>
|
||||||
|
services.xserver.displayManager.enable = mkOption {
|
||||||
|
type = with types; nullOr (enum [ "kdm" ]);
|
||||||
|
};</screen></example>
|
||||||
|
|
||||||
|
<para>The placeholder declaration is a standard <literal>mkOption</literal>
|
||||||
|
declaration, but it is important that extensible option declarations only use
|
||||||
|
the <literal>type</literal> argument.</para>
|
||||||
|
|
||||||
|
<para>Extensible option types work with any of the composed variants of
|
||||||
|
<literal>enum</literal> such as
|
||||||
|
<literal>with types; nullOr (enum [ "foo" "bar" ])</literal>
|
||||||
|
or <literal>with types; listOf (enum [ "foo" "bar" ])</literal>.</para>
|
||||||
|
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -62,23 +62,45 @@
|
||||||
<listitem><para>A string. Multiple definitions are concatenated with a
|
<listitem><para>A string. Multiple definitions are concatenated with a
|
||||||
collon <literal>":"</literal>.</para></listitem>
|
collon <literal>":"</literal>.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
|
||||||
<term><varname>types.separatedString</varname>
|
|
||||||
<replaceable>sep</replaceable></term>
|
|
||||||
<listitem><para>A string with a custom separator
|
|
||||||
<replaceable>sep</replaceable>, e.g. <literal>types.separatedString
|
|
||||||
"|"</literal>.</para></listitem>
|
|
||||||
</varlistentry>
|
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section><title>Value Types</title>
|
||||||
|
|
||||||
|
<para>Value types are type that take a value parameter. The only value type
|
||||||
|
in the library is <literal>enum</literal>.</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>types.enum</varname> <replaceable>l</replaceable></term>
|
||||||
|
<listitem><para>One element of the list <replaceable>l</replaceable>, e.g.
|
||||||
|
<literal>types.enum [ "left" "right" ]</literal>. Multiple definitions
|
||||||
|
cannot be merged.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>types.separatedString</varname>
|
||||||
|
<replaceable>sep</replaceable></term>
|
||||||
|
<listitem><para>A string with a custom separator
|
||||||
|
<replaceable>sep</replaceable>, e.g. <literal>types.separatedString
|
||||||
|
"|"</literal>.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>types.submodule</varname> <replaceable>o</replaceable></term>
|
||||||
|
<listitem><para>A set of sub options <replaceable>o</replaceable>.
|
||||||
|
<replaceable>o</replaceable> can be an attribute set or a function
|
||||||
|
returning an attribute set. Submodules are used in composed types to
|
||||||
|
create modular options. Submodule are detailed in <xref
|
||||||
|
linkend='section-option-types-submodule' />.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section><title>Composed Types</title>
|
<section><title>Composed Types</title>
|
||||||
|
|
||||||
<para>Composed types allow to create complex types by taking another type(s)
|
<para>Composed types are types that take a type as parameter. <literal>listOf
|
||||||
or value(s) as parameter(s).
|
int</literal> and <literal>either int str</literal> are examples of
|
||||||
It is possible to compose types multiple times, e.g. <literal>with types;
|
composed types.</para>
|
||||||
nullOr (enum [ "left" "right" ])</literal>.</para>
|
|
||||||
|
|
||||||
<variablelist>
|
<variablelist>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
|
@ -111,12 +133,6 @@
|
||||||
merged. It is used to ensure option definitions are declared only
|
merged. It is used to ensure option definitions are declared only
|
||||||
once.</para></listitem>
|
once.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
|
||||||
<term><varname>types.enum</varname> <replaceable>l</replaceable></term>
|
|
||||||
<listitem><para>One element of the list <replaceable>l</replaceable>, e.g.
|
|
||||||
<literal>types.enum [ "left" "right" ]</literal>. Multiple definitions
|
|
||||||
cannot be merged</para></listitem>
|
|
||||||
</varlistentry>
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>types.either</varname> <replaceable>t1</replaceable>
|
<term><varname>types.either</varname> <replaceable>t1</replaceable>
|
||||||
<replaceable>t2</replaceable></term>
|
<replaceable>t2</replaceable></term>
|
||||||
|
@ -125,14 +141,6 @@
|
||||||
str</literal>. Multiple definitions cannot be
|
str</literal>. Multiple definitions cannot be
|
||||||
merged.</para></listitem>
|
merged.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
|
||||||
<term><varname>types.submodule</varname> <replaceable>o</replaceable></term>
|
|
||||||
<listitem><para>A set of sub options <replaceable>o</replaceable>.
|
|
||||||
<replaceable>o</replaceable> can be an attribute set or a function
|
|
||||||
returning an attribute set. Submodules are used in composed types to
|
|
||||||
create modular options. Submodule are detailed in <xref
|
|
||||||
linkend='section-option-types-submodule' />.</para></listitem>
|
|
||||||
</varlistentry>
|
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
@ -191,7 +199,6 @@ options.mod = mkOption {
|
||||||
type = with types; listOf (submodule modOptions);
|
type = with types; listOf (submodule modOptions);
|
||||||
};</screen></example>
|
};</screen></example>
|
||||||
|
|
||||||
|
|
||||||
<section><title>Composed with <literal>listOf</literal></title>
|
<section><title>Composed with <literal>listOf</literal></title>
|
||||||
|
|
||||||
<para>When composed with <literal>listOf</literal>, submodule allows multiple
|
<para>When composed with <literal>listOf</literal>, submodule allows multiple
|
||||||
|
@ -317,9 +324,13 @@ code before creating a new type.</para>
|
||||||
<variablelist>
|
<variablelist>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>name</varname></term>
|
<term><varname>name</varname></term>
|
||||||
<listitem><para>A string representation of the type function name, name
|
<listitem><para>A string representation of the type function
|
||||||
usually changes accordingly parameters passed to
|
name.</para></listitem>
|
||||||
types.</para></listitem>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>definition</varname></term>
|
||||||
|
<listitem><para>Description of the type used in documentation. Give
|
||||||
|
information of the type and any of its arguments.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>check</varname></term>
|
<term><varname>check</varname></term>
|
||||||
|
@ -382,6 +393,53 @@ code before creating a new type.</para>
|
||||||
type parameter, this function should be defined as <literal>m:
|
type parameter, this function should be defined as <literal>m:
|
||||||
composedType (elemType.substSubModules m)</literal>.</para></listitem>
|
composedType (elemType.substSubModules m)</literal>.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>typeMerge</varname></term>
|
||||||
|
<listitem><para>A function to merge multiple type declarations. Takes the
|
||||||
|
type to merge <literal>functor</literal> as parameter. A
|
||||||
|
<literal>null</literal> return value means that type cannot be
|
||||||
|
merged.</para>
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable>f</replaceable></term>
|
||||||
|
<listitem><para>The type to merge
|
||||||
|
<literal>functor</literal>.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
<para>Note: There is a generic <literal>defaultTypeMerge</literal> that
|
||||||
|
work with most of value and composed types.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>functor</varname></term>
|
||||||
|
<listitem><para>An attribute set representing the type. It is used for type
|
||||||
|
operations and has the following keys:</para>
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>type</varname></term>
|
||||||
|
<listitem><para>The type function.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>wrapped</varname></term>
|
||||||
|
<listitem><para>Holds the type parameter for composed types.</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>payload</varname></term>
|
||||||
|
<listitem><para>Holds the value parameter for value types.
|
||||||
|
The types that have a <literal>payload</literal> are the
|
||||||
|
<literal>enum</literal>, <literal>separatedString</literal> and
|
||||||
|
<literal>submodule</literal> types.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>binOp</varname></term>
|
||||||
|
<listitem><para>A binary operation that can merge the payloads of two
|
||||||
|
same types. Defined as a function that take two payloads as
|
||||||
|
parameters and return the payloads merged.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -75,7 +75,10 @@ following incompatible changes:</para>
|
||||||
|
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para></para>
|
<para>Module type system have a new extensible option types feature that
|
||||||
|
allow to extend certain types, such as enum, through multiple option
|
||||||
|
declarations of the same option across multiple modules.
|
||||||
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
|
|
||||||
|
|
|
@ -256,7 +256,7 @@ if isOption opt then
|
||||||
// optionalAttrs (opt ? default) { inherit (opt) default; }
|
// optionalAttrs (opt ? default) { inherit (opt) default; }
|
||||||
// optionalAttrs (opt ? example) { inherit (opt) example; }
|
// optionalAttrs (opt ? example) { inherit (opt) example; }
|
||||||
// optionalAttrs (opt ? description) { inherit (opt) description; }
|
// optionalAttrs (opt ? description) { inherit (opt) description; }
|
||||||
// optionalAttrs (opt ? type) { typename = opt.type.name; }
|
// optionalAttrs (opt ? type) { typename = opt.type.description; }
|
||||||
// optionalAttrs (opt ? options) { inherit (opt) options; }
|
// optionalAttrs (opt ? options) { inherit (opt) options; }
|
||||||
// {
|
// {
|
||||||
# to disambiguate the xml output.
|
# to disambiguate the xml output.
|
||||||
|
|
Loading…
Reference in a new issue