Fix manual generation

This commit is contained in:
Eelco Dolstra 2013-10-28 14:25:58 +01:00
parent 7cf0e0bda8
commit 89bd18b3af
4 changed files with 70 additions and 62 deletions

View file

@ -6,14 +6,16 @@ rec {
/* Evaluate a set of modules. The result is a set of two /* Evaluate a set of modules. The result is a set of two
attributes: options: the nested set of all option declarations, attributes: options: the nested set of all option declarations,
and config: the nested set of all option values. */ and config: the nested set of all option values. */
evalModules = modules: args: evalModules = evalModules' [];
evalModules' = prefix: modules: args:
let let
args' = args // result; args' = args // result;
closed = closeModules modules args'; closed = closeModules modules args';
# Note: the list of modules is reversed to maintain backward # Note: the list of modules is reversed to maintain backward
# compatibility with the old module system. Not sure if this is # compatibility with the old module system. Not sure if this is
# the most sensible policy. # the most sensible policy.
options = mergeModules (reverseList closed); options = mergeModules prefix (reverseList closed);
config = yieldConfig options; config = yieldConfig options;
yieldConfig = mapAttrs (n: v: if isOption v then v.value else yieldConfig v); yieldConfig = mapAttrs (n: v: if isOption v then v.value else yieldConfig v);
result = { inherit options config; }; result = { inherit options config; };
@ -22,16 +24,15 @@ rec {
/* Close a set of modules under the imports relation. */ /* Close a set of modules under the imports relation. */
closeModules = modules: args: closeModules = modules: args:
let let
coerceToModule = n: x: toClosureList = parent: imap (n: x:
if isAttrs x || builtins.isFunction x then if isAttrs x || builtins.isFunction x then
unifyModuleSyntax "<unknown-file>" "anon-${toString n}" (applyIfFunction x args) unifyModuleSyntax parent "anon-${toString n}" (applyIfFunction x args)
else else
unifyModuleSyntax (toString x) (toString x) (applyIfFunction (import x) args); unifyModuleSyntax (toString x) (toString x) (applyIfFunction (import x) args));
toClosureList = imap (path: coerceToModule path);
in in
builtins.genericClosure { builtins.genericClosure {
startSet = toClosureList modules; startSet = toClosureList unknownModule modules;
operator = m: toClosureList m.imports; operator = m: toClosureList m.file m.imports;
}; };
/* Massage a module into canonical form, that is, a set consisting /* Massage a module into canonical form, that is, a set consisting
@ -61,18 +62,18 @@ rec {
At the same time, for each option declaration, it will merge the At the same time, for each option declaration, it will merge the
corresponding option definitions in all machines, returning them corresponding option definitions in all machines, returning them
in the value attribute of each option. */ in the value attribute of each option. */
mergeModules = modules: mergeModules = prefix: modules:
mergeModules' [] modules mergeModules' prefix modules
(concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules); (concatMap (m: map (config: { inherit (m) file; inherit config; }) (pushDownProperties m.config)) modules);
mergeModules' = loc: options: configs: mergeModules' = prefix: options: configs:
let names = concatMap (m: attrNames m.options) options; let names = concatMap (m: attrNames m.options) options;
in listToAttrs (map (name: { in listToAttrs (map (name: {
# We're descending into attribute name. # We're descending into attribute name.
inherit name; inherit name;
value = value =
let let
loc' = loc ++ [name]; loc = prefix ++ [name];
# Get all submodules that declare name. # Get all submodules that declare name.
decls = concatLists (map (m: decls = concatLists (map (m:
if hasAttr name m.options if hasAttr name m.options
@ -95,16 +96,16 @@ rec {
) configs; ) configs;
in in
if nrOptions == length decls then if nrOptions == length decls then
let opt = fixupOptionType loc' (mergeOptionDecls loc' decls); let opt = fixupOptionType loc (mergeOptionDecls loc decls);
in evalOptionValue loc' opt defns' in evalOptionValue loc opt defns'
else if nrOptions != 0 then else if nrOptions != 0 then
let let
firstOption = findFirst (m: isOption m.options) "" decls; firstOption = findFirst (m: isOption m.options) "" decls;
firstNonOption = findFirst (m: !isOption m.options) "" decls; firstNonOption = findFirst (m: !isOption m.options) "" decls;
in in
throw "The option `${showOption loc'}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'." throw "The option `${showOption loc}' in `${firstOption.file}' is a prefix of options in `${firstNonOption.file}'."
else else
mergeModules' loc' decls defns; mergeModules' loc decls defns;
}) names); }) names);
/* Merge multiple option declarations into a single declaration. In /* Merge multiple option declarations into a single declaration. In
@ -128,7 +129,7 @@ rec {
{ declarations = [opt.file] ++ res.declarations; { declarations = [opt.file] ++ res.declarations;
options = if opt.options ? options then [(toList opt.options.options ++ res.options)] else []; options = if opt.options ? options then [(toList opt.options.options ++ res.options)] else [];
} }
) { 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
config value. */ config value. */

View file

@ -87,31 +87,28 @@ rec {
# Generate documentation template from the list of option declaration like # Generate documentation template from the list of option declaration like
# the set generated with filterOptionSets. # the set generated with filterOptionSets.
optionAttrSetToDocList = attrs: optionAttrSetToDocList = optionAttrSetToDocList' [];
let options = collect isOption attrs; in
fold (opt: rest:
let
docOption = {
inherit (opt) name;
description = opt.description or (throw "Option ${opt.name}: No description.");
declarations = map (x: toString x.source) opt.declarations;
#definitions = map (x: toString x.source) opt.definitions;
internal = opt.internal or false;
visible = opt.visible or true;
}
// optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
// optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
// optionalAttrs (opt ? defaultText) { default = opt.defaultText; };
subOptions = optionAttrSetToDocList' = prefix: options:
if opt ? options then fold (opt: rest:
optionAttrSetToDocList opt.options let
else docOption = rec {
[]; name = showOption opt.loc;
in description = opt.description or (throw "Option `${name}' has no description.");
# FIXME: expensive (O(n^2) declarations = filter (x: x != unknownModule) opt.declarations;
[ docOption ] ++ subOptions ++ rest internal = opt.internal or false;
) [] options; visible = opt.visible or true;
}
// optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
// optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
// optionalAttrs (opt ? defaultText) { default = opt.defaultText; };
subOptions =
let ss = opt.type.getSubOptions opt.loc;
in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
in
# FIXME: expensive, O(n^2)
[ docOption ] ++ subOptions ++ rest) [] (collect isOption options);
/* This function recursively removes all derivation attributes from /* This function recursively removes all derivation attributes from
@ -135,5 +132,6 @@ rec {
/* Helper functions. */ /* Helper functions. */
showOption = concatStringsSep "."; showOption = concatStringsSep ".";
unknownModule = "<unknown-file>";
} }

View file

@ -22,18 +22,18 @@ rec {
# name (name of the type) # name (name of the type)
# check (check the config value) # check (check the config value)
# merge (default merge function) # merge (default merge function)
# docPath (path concatenated to the option name contained in the option set) # getSubOptions (returns sub-options for manual generation)
isOptionType = isType "option-type"; isOptionType = isType "option-type";
mkOptionType = mkOptionType =
{ name { name
, check ? (x: true) , check ? (x: true)
, merge ? mergeDefaultOption , merge ? mergeDefaultOption
, merge' ? args: merge , merge' ? args: merge
, docPath ? lib.id , getSubOptions ? prefix: {}
}: }:
{ _type = "option-type"; { _type = "option-type";
inherit name check merge merge' docPath; inherit name check merge merge' getSubOptions;
}; };
@ -99,14 +99,14 @@ rec {
name = "list of ${elemType.name}s"; name = "list of ${elemType.name}s";
check = value: isList value && all elemType.check value; check = value: isList value && all elemType.check value;
merge = defs: map (def: elemType.merge [def]) (concatLists defs); merge = defs: map (def: elemType.merge [def]) (concatLists defs);
docPath = path: elemType.docPath (path + ".*"); getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
}; };
attrsOf = elemType: mkOptionType { attrsOf = elemType: mkOptionType {
name = "attribute set of ${elemType.name}s"; name = "attribute set of ${elemType.name}s";
check = x: isAttrs x && all elemType.check (lib.attrValues x); check = x: isAttrs x && all elemType.check (lib.attrValues x);
merge = lib.zipAttrsWith (name: elemType.merge' { inherit name; }); merge = lib.zipAttrsWith (name: elemType.merge' { inherit name; });
docPath = path: elemType.docPath (path + ".<name>"); getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
}; };
# List or attribute set of ... # List or attribute set of ...
@ -129,26 +129,27 @@ rec {
else if isAttrs x then attrOnly.check x else if isAttrs x then attrOnly.check x
else false; else false;
merge = defs: attrOnly.merge (imap convertIfList defs); merge = defs: attrOnly.merge (imap convertIfList defs);
docPath = path: elemType.docPath (path + ".<name?>"); getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
}; };
uniq = elemType: mkOptionType { uniq = elemType: mkOptionType {
inherit (elemType) name check docPath; inherit (elemType) name check;
merge = list: merge = list:
if length list == 1 then if length list == 1 then
head list head list
else else
throw "Multiple definitions of ${elemType.name}. Only one is allowed for this option."; throw "Multiple definitions of ${elemType.name}. Only one is allowed for this option.";
getSubOptions = elemType.getSubOptions;
}; };
none = elemType: mkOptionType { none = elemType: mkOptionType {
inherit (elemType) name check docPath; inherit (elemType) name check;
merge = list: merge = list:
throw "No definitions are allowed for this option."; throw "No definitions are allowed for this option.";
getSubOptions = elemType.getSubOptions;
}; };
nullOr = elemType: mkOptionType { nullOr = elemType: mkOptionType {
inherit (elemType) docPath;
name = "null or ${elemType.name}"; name = "null or ${elemType.name}";
check = x: builtins.isNull x || elemType.check x; check = x: builtins.isNull x || elemType.check x;
merge = defs: merge = defs:
@ -156,6 +157,7 @@ rec {
else if any isNull defs then else if any isNull defs then
throw "Some but not all values are null." throw "Some but not all values are null."
else elemType.merge defs; else elemType.merge defs;
getSubOptions = elemType.getSubOptions;
}; };
functionTo = elemType: mkOptionType { functionTo = elemType: mkOptionType {
@ -163,19 +165,26 @@ rec {
check = builtins.isFunction; check = builtins.isFunction;
merge = fns: merge = fns:
args: elemType.merge (map (fn: fn args) fns); args: elemType.merge (map (fn: fn args) fns);
getSubOptions = elemType.getSubOptions;
}; };
submodule = opts: mkOptionType rec { submodule = opts:
name = "submodule"; let opts' = toList opts; in
check = x: isAttrs x || builtins.isFunction x; mkOptionType rec {
# FIXME: make error messages include the parent attrpath. name = "submodule";
merge = merge' {}; check = x: isAttrs x || builtins.isFunction x;
merge' = args: defs: # FIXME: make error messages include the parent attrpath.
let merge = merge' {};
coerce = def: if builtins.isFunction def then def else { config = def; }; merge' = args: defs:
modules = (toList opts) ++ map coerce defs; let
in (evalModules modules args).config; coerce = def: if builtins.isFunction def then def else { config = def; };
}; modules = opts' ++ map coerce defs;
in (evalModules modules args).config;
getSubOptions = prefix: (evalModules' prefix opts'
# FIXME: hack to get shit to evaluate.
{ name = ""; }
).options;
};
# 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

View file

@ -19,7 +19,7 @@ let
manual = import ../../../doc/manual { manual = import ../../../doc/manual {
inherit pkgs; inherit pkgs;
revision = config.system.nixosRevision; revision = config.system.nixosRevision;
options = (fixMergeModules ([ versionModule ] ++ baseModules) options = (evalModules ([ versionModule ] ++ baseModules)
(removeAttrs extraArgs ["config" "options"]) // { (removeAttrs extraArgs ["config" "options"]) // {
modules = [ ]; modules = [ ];
}).options; }).options;