307 lines
9.6 KiB
Nix
307 lines
9.6 KiB
Nix
{ core, context, extendPath, ... }:
|
|
options:
|
|
let
|
|
inherit(core) debug list set string type;
|
|
|
|
addOption
|
|
= { options, ... } @ state:
|
|
name:
|
|
option:
|
|
state
|
|
// {
|
|
options
|
|
= options
|
|
// {
|
|
${name} = option;
|
|
};
|
|
};
|
|
|
|
collect
|
|
= {
|
|
__type__,
|
|
apply,
|
|
default,
|
|
documentation,
|
|
internal,
|
|
optionType,
|
|
path,
|
|
readOnly,
|
|
}:
|
|
definitions:
|
|
if type.isList definitions
|
|
then
|
|
let
|
|
value
|
|
= if definitions == []
|
|
then
|
|
if optionType.check default
|
|
then
|
|
default
|
|
else
|
|
debug.panic
|
|
"collect"
|
|
"The default value of `${path}` does not match its type `${optionType.name}`!"
|
|
else if list.length definitions == 1
|
|
then
|
|
let
|
|
first = (list.head definitions).value;
|
|
in
|
|
if optionType.check first
|
|
then
|
|
first
|
|
else
|
|
debug.panic
|
|
"collect"
|
|
"The value of `${path}` does not match its type `${optionType.name}`!"
|
|
else if readOnly
|
|
then
|
|
debug.panic
|
|
"collect"
|
|
"The option `${path}` is read-only, but was set multiple times!"
|
|
else if list.any ({ value, ... }: optionType.check value) definitions
|
|
then
|
|
optionType.merge [ path ] definitions
|
|
else
|
|
debug.panic
|
|
"collect"
|
|
"A value of `${path}` does not match its type `${optionType.name}`!";
|
|
in
|
|
debug.warn
|
|
"collect"
|
|
{
|
|
when = optionType.deprecationMessage != null;
|
|
text
|
|
= ''
|
|
The type `${optionType.name}' of option `${path}' defined is deprecated.
|
|
${optionType.deprecationMessage}
|
|
'';
|
|
}
|
|
value
|
|
else
|
|
debug.panic
|
|
"collect"
|
|
"A list of definitions expected, got ${type.getPrimitive definitions}";
|
|
|
|
collects
|
|
= { config, options, ... }:
|
|
if type.isSet config
|
|
then
|
|
set.map
|
|
(
|
|
name:
|
|
{
|
|
__type__ ? null,
|
|
...
|
|
} @ option:
|
|
if __type__ == "Option"
|
|
then
|
|
collect
|
|
option
|
|
(config.${name} or [])
|
|
else
|
|
collects
|
|
{
|
|
config = config.${name} or {};
|
|
options = option;
|
|
}
|
|
)
|
|
options
|
|
else
|
|
debug.panic
|
|
"collects"
|
|
"An attribute set of options expected, got ${type.getPrimitive config}";
|
|
|
|
combineLegacy
|
|
= { ... } @ state:
|
|
options:
|
|
debug.info "combineLegacy"
|
|
{
|
|
text = "mew";
|
|
data = options;
|
|
}
|
|
(
|
|
set.fold
|
|
(
|
|
{ options, path, ... } @ state:
|
|
name:
|
|
option:
|
|
let
|
|
addOption' = addOption state name;
|
|
path' = extendPath path name;
|
|
in
|
|
debug.debug "combineLegacy"
|
|
{
|
|
text = "${path'}";
|
|
data = (set.names option);
|
|
}
|
|
(
|
|
if set.hasAttribute name options
|
|
then
|
|
if options.${name} ? __type__
|
|
then
|
|
debug.panic
|
|
"combineLegacy"
|
|
"Option `${path'}` already defined"
|
|
else if option.__type__ or null == null
|
|
then
|
|
addOption'
|
|
(
|
|
combineLegacy
|
|
{
|
|
options = options.${name};
|
|
path = path';
|
|
}
|
|
option
|
|
).options
|
|
else
|
|
debug.panic
|
|
"combineLegacy"
|
|
"Option `${path'}` is already defined as an attribute set, so only attribute sets of options can be added"
|
|
else if option._type or null == "option"
|
|
then
|
|
addOption' (convertLegacy path' option)
|
|
else
|
|
addOption'
|
|
(
|
|
combineLegacy
|
|
{
|
|
options = {};
|
|
path = path';
|
|
}
|
|
option
|
|
).options
|
|
)
|
|
)
|
|
state
|
|
options
|
|
);
|
|
|
|
combineModules
|
|
= state:
|
|
{
|
|
config ? null,
|
|
evaluate ? null,
|
|
imports ? [],
|
|
legacy,
|
|
options,
|
|
...
|
|
}:
|
|
if legacy
|
|
then
|
|
combineLegacy state options
|
|
else
|
|
combine state options;
|
|
|
|
combine
|
|
= state:
|
|
options:
|
|
set.fold
|
|
(
|
|
{ options, path, ... } @ state:
|
|
name:
|
|
option:
|
|
let
|
|
addOption' = addOption state name;
|
|
path' = extendPath path name;
|
|
in
|
|
if set.hasAttribute name options
|
|
then
|
|
if options.${name} ? __type__
|
|
then
|
|
debug.panic
|
|
"combine"
|
|
"Option `${path'}` already defined"
|
|
else if option.__type__ or null == null
|
|
then
|
|
addOption'
|
|
(
|
|
combine
|
|
{
|
|
options = options.${name};
|
|
path = path';
|
|
}
|
|
option
|
|
).options
|
|
else
|
|
debug.panic
|
|
"combine"
|
|
"Option `${path'}` is already defined as an attribute set, so only attribute sets of options can be added"
|
|
else
|
|
addOption' option
|
|
)
|
|
state
|
|
options;
|
|
|
|
convertLegacy
|
|
= path:
|
|
{
|
|
default ? null,
|
|
defaultText ? null,
|
|
example ? null,
|
|
description ? null,
|
|
relatedPackages ? null,
|
|
type ? null,
|
|
apply ? (x: x),
|
|
internal ? false,
|
|
value ? null,
|
|
visible ? true,
|
|
readOnly ? false,
|
|
...
|
|
}:
|
|
debug.info "convertLegacy"
|
|
{
|
|
text = "value?";
|
|
when = value != null;
|
|
data = value;
|
|
}
|
|
{
|
|
__type__ = "Option";
|
|
documentation
|
|
= if visible
|
|
then
|
|
{
|
|
default
|
|
= if defaultText != null
|
|
then
|
|
defaultText
|
|
else
|
|
string default;
|
|
inherit description example relatedPackages;
|
|
type = { inherit(type) description descriptionClass name; };
|
|
}
|
|
else
|
|
null;
|
|
inherit apply default internal path readOnly;
|
|
optionType = convertLegacyType type;
|
|
};
|
|
|
|
convertLegacyType
|
|
= {
|
|
check, # T -> bool
|
|
deprecationMessage, # string?
|
|
description, # string
|
|
descriptionClass, # irrelevant?
|
|
emptyValue, # {} | { value: T; }
|
|
functor, # string -> type -> wrapped -> payload: P -> (binop: P -> P -> P) ->
|
|
getSubOptions, # T -> { string -> U }
|
|
getSubModules, # [ LegacyModule ]?
|
|
merge, # [ string ] -> [ { file: path; value: T; } ] -> { string -> T }
|
|
name, # string
|
|
nestedTypes, # irrelevant?
|
|
substSubModules, # T -> U
|
|
typeMerge, # T -> T -> T
|
|
...
|
|
}:
|
|
{
|
|
inherit check deprecationMessage merge name;
|
|
};
|
|
|
|
options'
|
|
= combineLegacy
|
|
{
|
|
options = {};
|
|
path = null;
|
|
}
|
|
(set.remove options [ "_module" ]);
|
|
in
|