lib/types: Add types.submoduleWith for more flexibility than types.submodule

This commit is contained in:
Silvan Mosberger 2019-12-04 23:41:39 +01:00
parent 3cc77ce756
commit 5002e6afbc
No known key found for this signature in database
GPG key ID: E8F1E9EAD284E17D

View file

@ -358,25 +358,41 @@ rec {
}; };
# A submodule (like typed attribute set). See NixOS manual. # A submodule (like typed attribute set). See NixOS manual.
submodule = opts: submodule = modules: submoduleWith {
shorthandOnlyDefinesConfig = true;
modules = toList modules;
};
submoduleWith =
{ modules
, specialArgs ? {}
, shorthandOnlyDefinesConfig ? false
}@attrs:
let let
opts' = toList opts;
inherit (lib.modules) evalModules; inherit (lib.modules) evalModules;
coerce = unify: value: if isFunction value
then setFunctionArgs (args: unify (value args)) (functionArgs value)
else unify (if shorthandOnlyDefinesConfig then { config = value; } else value);
allModules = defs: modules ++ imap1 (n: { value, file }:
# Annotate the value with the location of its definition for better error messages
coerce (lib.modules.unifyModuleSyntax file "${toString file}-${toString n}") value
) defs;
in in
mkOptionType rec { mkOptionType rec {
name = "submodule"; name = "submodule";
check = x: isAttrs x || isFunction x; check = x: isAttrs x || isFunction x;
merge = loc: defs: merge = loc: defs:
let (evalModules {
coerce = def: if isFunction def then def else { config = def; }; modules = allModules defs;
modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs; inherit specialArgs;
in (evalModules {
inherit modules;
args.name = last loc; args.name = last loc;
prefix = loc; prefix = loc;
}).config; }).config;
getSubOptions = prefix: (evalModules getSubOptions = prefix: (evalModules
{ modules = opts'; inherit prefix; { inherit modules prefix specialArgs;
# This is a work-around due to the fact that some sub-modules, # This is a work-around due to the fact that some sub-modules,
# such as the one included in an attribute set, expects a "args" # such as the one included in an attribute set, expects a "args"
# attribute to be given to the sub-module. As the option # attribute to be given to the sub-module. As the option
@ -394,13 +410,29 @@ rec {
# It shouldn't cause an issue since this is cosmetic for the manual. # It shouldn't cause an issue since this is cosmetic for the manual.
args.name = "name"; args.name = "name";
}).options; }).options;
getSubModules = opts'; getSubModules = modules;
substSubModules = m: submodule m; substSubModules = m: submoduleWith (attrs // {
functor = (defaultFunctor name) // { modules = m;
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate });
# each submodule with its location. functor = defaultFunctor name // {
payload = []; type = types.submoduleWith;
binOp = lhs: rhs: []; payload = {
modules = modules;
specialArgs = specialArgs;
shorthandOnlyDefinesConfig = shorthandOnlyDefinesConfig;
};
binOp = lhs: rhs: {
modules = lhs.modules ++ rhs.modules;
specialArgs =
let intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
in if intersecting == {}
then lhs.specialArgs // rhs.specialArgs
else throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
shorthandOnlyDefinesConfig =
if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig
then lhs.shorthandOnlyDefinesConfig
else throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
};
}; };
}; };