|
|
|
@ -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(); |
|
|
|
|