diff --git a/nixos/doc/manual/man-nixos-option.xml b/nixos/doc/manual/man-nixos-option.xml index b82f31256099..b921386d0df0 100644 --- a/nixos/doc/manual/man-nixos-option.xml +++ b/nixos/doc/manual/man-nixos-option.xml @@ -14,12 +14,16 @@ <refsynopsisdiv> <cmdsynopsis> <command>nixos-option</command> + <arg> - <option>-I</option> <replaceable>path</replaceable> + <group choice='req'> + <arg choice='plain'><option>-r</option></arg> + <arg choice='plain'><option>--recursive</option></arg> + </group> </arg> <arg> - <option>--all</option> + <option>-I</option> <replaceable>path</replaceable> </arg> <arg> @@ -45,6 +49,15 @@ This command accepts the following options: </para> <variablelist> + <varlistentry> + <term><option>-r</option></term> + <term><option>--recursive</option></term> + <listitem> + <para> + Print all the values at or below the specified path recursively. + </para> + </listitem> + </varlistentry> <varlistentry> <term> <option>-I</option> <replaceable>path</replaceable> @@ -56,16 +69,6 @@ </para> </listitem> </varlistentry> - <varlistentry> - <term> - <option>--all</option> - </term> - <listitem> - <para> - Print the values of all options. - </para> - </listitem> - </varlistentry> </variablelist> </refsection> <refsection> diff --git a/nixos/doc/manual/release-notes/rl-2003.xml b/nixos/doc/manual/release-notes/rl-2003.xml index 4ee9ebd6a2b4..8883c99d4b70 100644 --- a/nixos/doc/manual/release-notes/rl-2003.xml +++ b/nixos/doc/manual/release-notes/rl-2003.xml @@ -59,7 +59,7 @@ <listitem> <para> <command>nixos-option</command> has been rewritten in C++, speeding it up, improving correctness, - and adding a <option>--all</option> option which prints all options and their values. + and adding a <option>-r</option> option which prints all options and their values recursively. </para> </listitem> <listitem> diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc index 9b92dc829cd1..1a7b07a74f8a 100644 --- a/nixos/modules/installer/tools/nixos-option/nixos-option.cc +++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc @@ -131,12 +131,12 @@ bool isOption(Context & ctx, const Value & v) if (v.type != tAttrs) { return false; } - const auto & atualType = v.attrs->find(ctx.underscoreType); - if (atualType == v.attrs->end()) { + const auto & actualType = v.attrs->find(ctx.underscoreType); + if (actualType == v.attrs->end()) { return false; } try { - Value evaluatedType = evaluateValue(ctx, *atualType->value); + Value evaluatedType = evaluateValue(ctx, *actualType->value); if (evaluatedType.type != tString) { return false; } @@ -197,9 +197,107 @@ void recurse(const std::function<bool(const std::string & path, std::variant<Val } } -// Calls f on all the option names -void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root) +bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) { + try { + const auto & typeLookup = v.attrs->find(ctx.state.sType); + if (typeLookup == v.attrs->end()) { + return false; + } + Value type = evaluateValue(ctx, *typeLookup->value); + if (type.type != tAttrs) { + return false; + } + const auto & nameLookup = type.attrs->find(ctx.state.sName); + if (nameLookup == type.attrs->end()) { + return false; + } + Value name = evaluateValue(ctx, *nameLookup->value); + if (name.type != tString) { + return false; + } + return name.string.s == soughtType; + } catch (Error &) { + return false; + } +} + +bool isAggregateOptionType(Context & ctx, Value & v) +{ + return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf"); +} + +MakeError(OptionPathError, EvalError); + +Value getSubOptions(Context & ctx, Value & option) +{ + Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option)); + if (getSubOptions.type != tLambda) { + throw OptionPathError("Option's type.getSubOptions isn't a function"); + } + Value emptyString{}; + nix::mkString(emptyString, ""); + Value v; + ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{}); + return v; +} + +// Carefully walk an option path, looking for sub-options when a path walks past +// an option value. +struct FindAlongOptionPathRet +{ + Value option; + std::string path; +}; +FindAlongOptionPathRet findAlongOptionPath(Context & ctx, const std::string & path) +{ + Strings tokens = parseAttrPath(path); + Value v = ctx.optionsRoot; + std::string processedPath; + for (auto i = tokens.begin(); i != tokens.end(); i++) { + const auto & attr = *i; + try { + bool lastAttribute = std::next(i) == tokens.end(); + v = evaluateValue(ctx, v); + if (attr.empty()) { + throw OptionPathError("empty attribute name"); + } + if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { + v = getSubOptions(ctx, v); + } + if (isOption(ctx, v) && isAggregateOptionType(ctx, v)) { + auto subOptions = getSubOptions(ctx, v); + if (lastAttribute && subOptions.attrs->empty()) { + break; + } + v = subOptions; + // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked + // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name". + } else if (v.type != tAttrs) { + throw OptionPathError("Value is %s while a set was expected", showType(v)); + } else { + const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); + if (next == v.attrs->end()) { + throw OptionPathError("Attribute not found", attr, path); + } + v = *next->value; + } + processedPath = appendPath(processedPath, attr); + } catch (OptionPathError & e) { + throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); + } + } + return {v, processedPath}; +} + +// Calls f on all the option names at or below the option described by `path`. +// Note that "the option described by `path`" is not trivial -- if path describes a value inside an aggregate +// option (such as users.users.root), the *option* described by that path is one path component shorter +// (eg: users.users), which results in f being called on sibling-paths (eg: users.users.nixbld1). If f +// doesn't want these, it must do its own filtering. +void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, const std::string & path) +{ + auto root = findAlongOptionPath(ctx, path); recurse( [f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) { bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v)); @@ -208,7 +306,7 @@ void mapOptions(const std::function<void(const std::string & path)> & f, Context } return !isOpt; }, - ctx, root, ""); + ctx, root.option, root.path); } // Calls f on all the config values inside one option. @@ -294,9 +392,11 @@ void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path) Out attrsOut(out, "{", "}", v.attrs->size()); for (const auto & a : v.attrs->lexicographicOrder()) { std::string name = a->name; - attrsOut << name << " = "; - printValue(ctx, attrsOut, *a->value, appendPath(path, name)); - attrsOut << ";" << Out::sep; + if (!forbiddenRecursionName(name)) { + attrsOut << name << " = "; + printValue(ctx, attrsOut, *a->value, appendPath(path, name)); + attrsOut << ";" << Out::sep; + } } } @@ -380,17 +480,26 @@ void printConfigValue(Context & ctx, Out & out, const std::string & path, std::v out << ";\n"; } -void printAll(Context & ctx, Out & out) +// Replace with std::starts_with when C++20 is available +bool starts_with(const std::string & s, const std::string & prefix) +{ + return s.size() >= prefix.size() && + std::equal(s.begin(), std::next(s.begin(), prefix.size()), prefix.begin(), prefix.end()); +} + +void printRecursive(Context & ctx, Out & out, const std::string & path) { mapOptions( - [&ctx, &out](const std::string & optionPath) { + [&ctx, &out, &path](const std::string & optionPath) { mapConfigValuesInOption( - [&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) { - printConfigValue(ctx, out, configPath, v); + [&ctx, &out, &path](const std::string & configPath, std::variant<Value, std::exception_ptr> v) { + if (starts_with(configPath, path)) { + printConfigValue(ctx, out, configPath, v); + } }, optionPath, ctx); }, - ctx, ctx.optionsRoot); + ctx, path); } void printAttr(Context & ctx, Out & out, const std::string & path, Value & root) @@ -450,95 +559,17 @@ void printListing(Out & out, Value & v) } } -bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType) -{ - try { - const auto & typeLookup = v.attrs->find(ctx.state.sType); - if (typeLookup == v.attrs->end()) { - return false; - } - Value type = evaluateValue(ctx, *typeLookup->value); - if (type.type != tAttrs) { - return false; - } - const auto & nameLookup = type.attrs->find(ctx.state.sName); - if (nameLookup == type.attrs->end()) { - return false; - } - Value name = evaluateValue(ctx, *nameLookup->value); - if (name.type != tString) { - return false; - } - return name.string.s == soughtType; - } catch (Error &) { - return false; - } -} - -bool isAggregateOptionType(Context & ctx, Value & v) -{ - return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf"); -} - -MakeError(OptionPathError, EvalError); - -Value getSubOptions(Context & ctx, Value & option) -{ - Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option)); - if (getSubOptions.type != tLambda) { - throw OptionPathError("Option's type.getSubOptions isn't a function"); - } - Value emptyString{}; - nix::mkString(emptyString, ""); - Value v; - ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{}); - return v; -} - -// Carefully walk an option path, looking for sub-options when a path walks past -// an option value. -Value findAlongOptionPath(Context & ctx, const std::string & path) -{ - Strings tokens = parseAttrPath(path); - Value v = ctx.optionsRoot; - for (auto i = tokens.begin(); i != tokens.end(); i++) { - const auto & attr = *i; - try { - bool lastAttribute = std::next(i) == tokens.end(); - v = evaluateValue(ctx, v); - if (attr.empty()) { - throw OptionPathError("empty attribute name"); - } - if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) { - v = getSubOptions(ctx, v); - } - if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) { - v = getSubOptions(ctx, v); - // Note that we've consumed attr, but didn't actually use it. This is the path component that's looked - // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name". - } else if (v.type != tAttrs) { - throw OptionPathError("Value is %s while a set was expected", showType(v)); - } else { - const auto & next = v.attrs->find(ctx.state.symbols.create(attr)); - if (next == v.attrs->end()) { - throw OptionPathError("Attribute not found", attr, path); - } - v = *next->value; - } - } catch (OptionPathError & e) { - throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg()); - } - } - return v; -} - void printOne(Context & ctx, Out & out, const std::string & path) { try { - Value option = findAlongOptionPath(ctx, path); + auto result = findAlongOptionPath(ctx, path); + Value & option = result.option; option = evaluateValue(ctx, option); + if (path != result.path) { + out << "Note: showing " << result.path << " instead of " << path << "\n"; + } if (isOption(ctx, option)) { - printOption(ctx, out, path, option); + printOption(ctx, out, result.path, option); } else { printListing(out, option); } @@ -552,7 +583,7 @@ void printOne(Context & ctx, Out & out, const std::string & path) int main(int argc, char ** argv) { - bool all = false; + bool recursive = false; std::string path = "."; std::string optionsExpr = "(import <nixpkgs/nixos> {}).options"; std::string configExpr = "(import <nixpkgs/nixos> {}).config"; @@ -568,8 +599,8 @@ int main(int argc, char ** argv) nix::showManPage("nixos-option"); } else if (*arg == "--version") { nix::printVersion("nixos-option"); - } else if (*arg == "--all") { - all = true; + } else if (*arg == "-r" || *arg == "--recursive") { + recursive = true; } else if (*arg == "--path") { path = nix::getArg(*arg, arg, end); } else if (*arg == "--options_expr") { @@ -598,18 +629,12 @@ int main(int argc, char ** argv) Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot}; Out out(std::cout); - if (all) { - if (!args.empty()) { - throw UsageError("--all cannot be used with arguments"); - } - printAll(ctx, out); - } else { - if (args.empty()) { - printOne(ctx, out, ""); - } - for (const auto & arg : args) { - printOne(ctx, out, arg); - } + auto print = recursive ? printRecursive : printOne; + if (args.empty()) { + print(ctx, out, ""); + } + for (const auto & arg : args) { + print(ctx, out, arg); } ctx.state.printStats();