2019-09-06 01:29:01 +01:00
|
|
|
#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
|
|
|
|
|
2019-09-06 19:37:48 +01:00
|
|
|
#include <exception> // for exception_ptr, current_exception
|
2019-09-06 01:29:01 +01:00
|
|
|
#include <functional> // for function
|
|
|
|
#include <iostream> // for operator<<, basic_ostream, ostrin...
|
|
|
|
#include <iterator> // for next
|
|
|
|
#include <list> // for _List_iterator
|
|
|
|
#include <memory> // for allocator, unique_ptr, make_unique
|
2019-09-06 19:37:48 +01:00
|
|
|
#include <new> // for operator new
|
2019-09-06 01:29:01 +01:00
|
|
|
#include <nix/args.hh> // for argvToStrings, UsageError
|
|
|
|
#include <nix/attr-path.hh> // for findAlongAttrPath
|
|
|
|
#include <nix/attr-set.hh> // for Attr, Bindings, Bindings::iterator
|
|
|
|
#include <nix/common-eval-args.hh> // for MixEvalArgs
|
|
|
|
#include <nix/eval-inline.hh> // for EvalState::forceValue
|
|
|
|
#include <nix/eval.hh> // for EvalState, initGC, operator<<
|
|
|
|
#include <nix/globals.hh> // for initPlugins, Settings, settings
|
|
|
|
#include <nix/nixexpr.hh> // for Pos
|
|
|
|
#include <nix/shared.hh> // for getArg, LegacyArgs, printVersion
|
|
|
|
#include <nix/store-api.hh> // for openStore
|
|
|
|
#include <nix/symbol-table.hh> // for Symbol, SymbolTable
|
|
|
|
#include <nix/types.hh> // for Error, Path, Strings, PathSet
|
|
|
|
#include <nix/util.hh> // for absPath, baseNameOf
|
|
|
|
#include <nix/value.hh> // for Value, Value::(anonymous), Value:...
|
|
|
|
#include <string> // for string, operator+, operator==
|
|
|
|
#include <utility> // for move
|
|
|
|
#include <variant> // for get, holds_alternative, variant
|
|
|
|
#include <vector> // for vector<>::iterator, vector
|
|
|
|
|
|
|
|
#include "libnix-copy-paste.hh"
|
|
|
|
|
|
|
|
using nix::absPath;
|
|
|
|
using nix::Bindings;
|
|
|
|
using nix::Error;
|
|
|
|
using nix::EvalState;
|
|
|
|
using nix::Path;
|
|
|
|
using nix::PathSet;
|
|
|
|
using nix::Strings;
|
|
|
|
using nix::Symbol;
|
|
|
|
using nix::tAttrs;
|
2019-09-06 20:27:34 +01:00
|
|
|
using nix::ThrownError;
|
2019-09-06 01:29:01 +01:00
|
|
|
using nix::tLambda;
|
|
|
|
using nix::tString;
|
|
|
|
using nix::UsageError;
|
|
|
|
using nix::Value;
|
|
|
|
|
|
|
|
// An ostream wrapper to handle nested indentation
|
2019-09-06 17:45:05 +01:00
|
|
|
class Out
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
class Separator
|
|
|
|
{};
|
|
|
|
const static Separator sep;
|
|
|
|
enum LinePolicy
|
|
|
|
{
|
|
|
|
ONE_LINE,
|
|
|
|
MULTI_LINE
|
|
|
|
};
|
|
|
|
explicit Out(std::ostream & ostream) : ostream(ostream), policy(ONE_LINE), write_since_sep(true) {}
|
|
|
|
Out(Out & o, std::string const & start, std::string const & end, LinePolicy policy);
|
|
|
|
Out(Out & o, std::string const & start, std::string const & end, int count)
|
|
|
|
: Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE)
|
|
|
|
{}
|
|
|
|
Out(Out const &) = delete;
|
|
|
|
Out(Out &&) = default;
|
|
|
|
Out & operator=(Out const &) = delete;
|
|
|
|
Out & operator=(Out &&) = delete;
|
|
|
|
~Out() { ostream << end; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::ostream & ostream;
|
|
|
|
std::string indentation;
|
|
|
|
std::string end;
|
|
|
|
LinePolicy policy;
|
|
|
|
bool write_since_sep;
|
|
|
|
template <typename T> friend Out & operator<<(Out & o, T thing);
|
2019-09-06 01:29:01 +01:00
|
|
|
};
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
template <typename T> Out & operator<<(Out & o, T thing)
|
|
|
|
{
|
|
|
|
if (!o.write_since_sep && o.policy == Out::MULTI_LINE) {
|
|
|
|
o.ostream << o.indentation;
|
|
|
|
}
|
|
|
|
o.write_since_sep = true;
|
|
|
|
o.ostream << thing;
|
|
|
|
return o;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */)
|
|
|
|
{
|
|
|
|
o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
|
|
|
|
o.write_since_sep = false;
|
|
|
|
return o;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
Out::Out(Out & o, std::string const & start, std::string const & end, LinePolicy policy)
|
|
|
|
: ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + " "),
|
|
|
|
end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), write_since_sep(true)
|
|
|
|
{
|
|
|
|
o << start;
|
|
|
|
*this << Out::sep;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stuff needed for evaluation
|
2019-09-06 17:45:05 +01:00
|
|
|
struct Context
|
|
|
|
{
|
|
|
|
Context(EvalState * state, Bindings * autoArgs, Value options_root, Value config_root)
|
|
|
|
: state(state), autoArgs(autoArgs), options_root(options_root), config_root(config_root),
|
|
|
|
underscore_type(state->symbols.create("_type"))
|
|
|
|
{}
|
|
|
|
EvalState * state;
|
|
|
|
Bindings * autoArgs;
|
|
|
|
Value options_root;
|
|
|
|
Value config_root;
|
|
|
|
Symbol underscore_type;
|
2019-09-06 01:29:01 +01:00
|
|
|
};
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
Value evaluateValue(Context * ctx, Value * v)
|
|
|
|
{
|
|
|
|
ctx->state->forceValue(*v);
|
|
|
|
if (ctx->autoArgs->empty()) {
|
|
|
|
return *v;
|
|
|
|
}
|
|
|
|
Value called{};
|
|
|
|
ctx->state->autoCallFunction(*ctx->autoArgs, *v, called);
|
|
|
|
return called;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
bool isOption(Context * ctx, Value const & v)
|
|
|
|
{
|
|
|
|
if (v.type != tAttrs) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto const & actual_type = v.attrs->find(ctx->underscore_type);
|
|
|
|
if (actual_type == v.attrs->end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
Value evaluated_type = evaluateValue(ctx, actual_type->value);
|
|
|
|
if (evaluated_type.type != tString) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return evaluated_type.string.s == static_cast<std::string>("option");
|
|
|
|
} catch (Error &) {
|
|
|
|
return false;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add quotes to a component of a path.
|
|
|
|
// These are needed for paths like:
|
|
|
|
// fileSystems."/".fsType
|
|
|
|
// systemd.units."dbus.service".text
|
2019-09-06 17:45:05 +01:00
|
|
|
std::string quoteAttribute(std::string const & attribute)
|
|
|
|
{
|
|
|
|
if (isVarName(attribute)) {
|
|
|
|
return attribute;
|
|
|
|
}
|
|
|
|
std::ostringstream buf;
|
|
|
|
printStringValue(buf, attribute.c_str());
|
|
|
|
return buf.str();
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
std::string const appendPath(std::string const & prefix, std::string const & suffix)
|
|
|
|
{
|
|
|
|
if (prefix.empty()) {
|
|
|
|
return quoteAttribute(suffix);
|
|
|
|
}
|
|
|
|
return prefix + "." + quoteAttribute(suffix);
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; }
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 22:30:27 +01:00
|
|
|
void recurse(const std::function<bool(std::string const & path, std::variant<Value, std::exception_ptr>)> & f,
|
|
|
|
Context * ctx, Value v, std::string const & path)
|
2019-09-06 17:45:05 +01:00
|
|
|
{
|
2019-09-06 19:37:48 +01:00
|
|
|
std::variant<Value, std::exception_ptr> evaluated;
|
2019-09-06 17:45:05 +01:00
|
|
|
try {
|
|
|
|
evaluated = evaluateValue(ctx, &v);
|
2019-09-06 19:37:48 +01:00
|
|
|
} catch (Error &) {
|
|
|
|
evaluated = std::current_exception();
|
2019-09-06 17:45:05 +01:00
|
|
|
}
|
|
|
|
if (!f(path, evaluated)) {
|
|
|
|
return;
|
|
|
|
}
|
2019-09-06 19:37:48 +01:00
|
|
|
if (std::holds_alternative<std::exception_ptr>(evaluated)) {
|
2019-09-06 17:45:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Value const & evaluated_value = std::get<Value>(evaluated);
|
|
|
|
if (evaluated_value.type != tAttrs) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (auto const & child : evaluated_value.attrs->lexicographicOrder()) {
|
|
|
|
if (forbiddenRecursionName(child->name)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
recurse(f, ctx, *child->value, appendPath(path, child->name));
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calls f on all the option names
|
2019-09-06 17:45:05 +01:00
|
|
|
void mapOptions(const std::function<void(std::string const & path)> & f, Context * ctx, Value root)
|
|
|
|
{
|
|
|
|
recurse(
|
2019-09-06 19:37:48 +01:00
|
|
|
[f, ctx](std::string const & path, std::variant<Value, std::exception_ptr> v) {
|
|
|
|
bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v));
|
2019-09-06 17:45:05 +01:00
|
|
|
if (isOpt) {
|
|
|
|
f(path);
|
|
|
|
}
|
|
|
|
return !isOpt;
|
|
|
|
},
|
|
|
|
ctx, root, "");
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calls f on all the config values inside one option.
|
|
|
|
// Simple options have one config value inside, like sound.enable = true.
|
|
|
|
// Compound options have multiple config values. For example, the option
|
|
|
|
// "users.users" has about 1000 config values inside it:
|
|
|
|
// users.users.avahi.createHome = false;
|
|
|
|
// users.users.avahi.cryptHomeLuks = null;
|
|
|
|
// users.users.avahi.description = "`avahi-daemon' privilege separation user";
|
|
|
|
// ...
|
|
|
|
// users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
|
|
|
|
// users.users.avahi.openssh.authorizedKeys.keys = [ ];
|
|
|
|
// ...
|
|
|
|
// users.users.avahi.uid = 10;
|
|
|
|
// users.users.avahi.useDefaultShell = false;
|
|
|
|
// users.users.cups.createHome = false;
|
|
|
|
// ...
|
|
|
|
// users.users.cups.useDefaultShell = false;
|
|
|
|
// users.users.gdm = ... ... ...
|
|
|
|
// users.users.messagebus = ... .. ...
|
|
|
|
// users.users.nixbld1 = ... .. ...
|
|
|
|
// ...
|
|
|
|
// users.users.systemd-timesync = ... .. ...
|
2019-09-06 22:30:27 +01:00
|
|
|
void mapConfigValuesInOption(
|
|
|
|
const std::function<void(std::string const & path, std::variant<Value, std::exception_ptr> v)> & f,
|
|
|
|
std::string const & path, Context * ctx)
|
2019-09-06 17:45:05 +01:00
|
|
|
{
|
|
|
|
Value * option;
|
|
|
|
try {
|
|
|
|
option = findAlongAttrPath(*ctx->state, path, *ctx->autoArgs, ctx->config_root);
|
2019-09-06 19:37:48 +01:00
|
|
|
} catch (Error &) {
|
|
|
|
f(path, std::current_exception());
|
2019-09-06 17:45:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
recurse(
|
2019-09-06 19:37:48 +01:00
|
|
|
[f, ctx](std::string const & path, std::variant<Value, std::exception_ptr> v) {
|
|
|
|
bool leaf = std::holds_alternative<std::exception_ptr>(v) || std::get<Value>(v).type != tAttrs ||
|
2019-09-06 17:45:05 +01:00
|
|
|
ctx->state->isDerivation(std::get<Value>(v));
|
|
|
|
if (!leaf) {
|
|
|
|
return true; // Keep digging
|
|
|
|
}
|
|
|
|
f(path, v);
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
ctx, *option, path);
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
std::string describeError(Error const & e) { return "«error: " + e.msg() + "»"; }
|
|
|
|
|
|
|
|
void describeDerivation(Context * ctx, Out & out, Value v)
|
|
|
|
{
|
|
|
|
// Copy-pasted from nix/src/nix/repl.cc :(
|
|
|
|
Bindings::iterator i = v.attrs->find(ctx->state->sDrvPath);
|
|
|
|
PathSet pathset;
|
|
|
|
try {
|
|
|
|
Path drvPath = i != v.attrs->end() ? ctx->state->coerceToPath(*i->pos, *i->value, pathset) : "???";
|
|
|
|
out << "«derivation " << drvPath << "»";
|
|
|
|
} catch (Error & e) {
|
|
|
|
out << describeError(e);
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
Value parseAndEval(EvalState * state, std::string const & expression, std::string const & path)
|
|
|
|
{
|
|
|
|
Value v{};
|
|
|
|
state->eval(state->parseExprFromString(expression, absPath(path)), v);
|
|
|
|
return v;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 22:30:27 +01:00
|
|
|
void printValue(Context * ctx, Out & out, std::variant<Value, std::exception_ptr> maybe_value,
|
|
|
|
std::string const & path);
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:58:34 +01:00
|
|
|
void printList(Context * ctx, Out & out, Value & v)
|
2019-09-06 17:45:05 +01:00
|
|
|
{
|
|
|
|
Out list_out(out, "[", "]", v.listSize());
|
|
|
|
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
|
|
|
printValue(ctx, list_out, *v.listElems()[n], "");
|
|
|
|
list_out << Out::sep;
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printAttrs(Context * ctx, Out & out, Value & v, std::string const & path)
|
|
|
|
{
|
|
|
|
Out attrs_out(out, "{", "}", v.attrs->size());
|
|
|
|
for (const auto & a : v.attrs->lexicographicOrder()) {
|
|
|
|
std::string name = a->name;
|
|
|
|
attrs_out << name << " = ";
|
|
|
|
printValue(ctx, attrs_out, *a->value, appendPath(path, name));
|
|
|
|
attrs_out << ";" << Out::sep;
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void multiLineStringEscape(Out & out, std::string const & s)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 1; i < s.size(); i++) {
|
|
|
|
if (s[i - 1] == '$' && s[i] == '{') {
|
|
|
|
out << "''${";
|
|
|
|
i++;
|
|
|
|
} else if (s[i - 1] == '\'' && s[i] == '\'') {
|
|
|
|
out << "'''";
|
|
|
|
i++;
|
|
|
|
} else {
|
|
|
|
out << s[i - 1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == s.size()) {
|
|
|
|
out << s[i - 1];
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printMultiLineString(Out & out, Value const & v)
|
|
|
|
{
|
|
|
|
std::string s = v.string.s;
|
|
|
|
Out str_out(out, "''", "''", Out::MULTI_LINE);
|
|
|
|
std::string::size_type begin = 0;
|
|
|
|
while (begin < s.size()) {
|
|
|
|
std::string::size_type end = s.find('\n', begin);
|
|
|
|
if (end == std::string::npos) {
|
|
|
|
multiLineStringEscape(str_out, s.substr(begin, s.size() - begin));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
multiLineStringEscape(str_out, s.substr(begin, end - begin));
|
|
|
|
str_out << Out::sep;
|
|
|
|
begin = end + 1;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 19:37:48 +01:00
|
|
|
void printValue(Context * ctx, Out & out, std::variant<Value, std::exception_ptr> maybe_value, std::string const & path)
|
2019-09-06 17:45:05 +01:00
|
|
|
{
|
|
|
|
try {
|
2019-09-06 19:37:48 +01:00
|
|
|
if (std::holds_alternative<std::exception_ptr>(maybe_value)) {
|
2019-09-06 22:30:27 +01:00
|
|
|
std::rethrow_exception(std::get<std::exception_ptr>(maybe_value));
|
2019-09-06 17:45:05 +01:00
|
|
|
}
|
|
|
|
Value v = evaluateValue(ctx, &std::get<Value>(maybe_value));
|
|
|
|
if (ctx->state->isDerivation(v)) {
|
|
|
|
describeDerivation(ctx, out, v);
|
|
|
|
} else if (v.isList()) {
|
|
|
|
printList(ctx, out, v);
|
|
|
|
} else if (v.type == tAttrs) {
|
|
|
|
printAttrs(ctx, out, v, path);
|
|
|
|
} else if (v.type == tString && std::string(v.string.s).find('\n') != std::string::npos) {
|
|
|
|
printMultiLineString(out, v);
|
|
|
|
} else {
|
|
|
|
ctx->state->forceValueDeep(v);
|
|
|
|
out << v;
|
|
|
|
}
|
2019-09-06 19:38:29 +01:00
|
|
|
} catch (ThrownError & e) {
|
2019-09-06 17:45:05 +01:00
|
|
|
if (e.msg() == "The option `" + path + "' is used but not defined.") {
|
|
|
|
// 93% of errors are this, and just letting this message through would be
|
|
|
|
// misleading. These values may or may not actually be "used" in the
|
|
|
|
// config. The thing throwing the error message assumes that if anything
|
|
|
|
// ever looks at this value, it is a "use" of this value. But here in
|
|
|
|
// nixos-option, we are looking at this value only to print it.
|
|
|
|
// In order to avoid implying that this undefined value is actually
|
|
|
|
// referenced, eat the underlying error message and emit "«not defined»".
|
|
|
|
out << "«not defined»";
|
|
|
|
} else {
|
|
|
|
out << describeError(e);
|
|
|
|
}
|
2019-09-06 19:38:29 +01:00
|
|
|
} catch (Error & e) {
|
|
|
|
out << describeError(e);
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 19:37:48 +01:00
|
|
|
void printConfigValue(Context * ctx, Out & out, std::string const & path, std::variant<Value, std::exception_ptr> v)
|
2019-09-06 17:45:05 +01:00
|
|
|
{
|
|
|
|
out << path << " = ";
|
|
|
|
printValue(ctx, out, std::move(v), path);
|
|
|
|
out << ";\n";
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printAll(Context * ctx, Out & out)
|
|
|
|
{
|
|
|
|
mapOptions(
|
|
|
|
[ctx, &out](std::string const & option_path) {
|
|
|
|
mapConfigValuesInOption(
|
2019-09-06 19:37:48 +01:00
|
|
|
[ctx, &out](std::string const & config_path, std::variant<Value, std::exception_ptr> v) {
|
2019-09-06 17:45:05 +01:00
|
|
|
printConfigValue(ctx, out, config_path, v);
|
|
|
|
},
|
|
|
|
option_path, ctx);
|
|
|
|
},
|
|
|
|
ctx, ctx->options_root);
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printAttr(Context * ctx, Out & out, std::string const & path, Value * root)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
printValue(ctx, out, *findAlongAttrPath(*ctx->state, path, *ctx->autoArgs, *root), path);
|
|
|
|
} catch (Error & e) {
|
|
|
|
out << describeError(e);
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printOption(Context * ctx, Out & out, std::string const & path, Value * option)
|
|
|
|
{
|
|
|
|
out << "Value:\n";
|
|
|
|
printAttr(ctx, out, path, &ctx->config_root);
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
out << "\n\nDefault:\n";
|
|
|
|
printAttr(ctx, out, "default", option);
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
out << "\n\nType:\n";
|
|
|
|
printAttr(ctx, out, "type.description", option);
|
2019-09-06 09:25:00 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
out << "\n\nExample:\n";
|
|
|
|
printAttr(ctx, out, "example", option);
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
out << "\n\nDescription:\n";
|
|
|
|
printAttr(ctx, out, "description", option);
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
out << "\n\nDeclared by:\n";
|
|
|
|
printAttr(ctx, out, "declarations", option);
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
out << "\n\nDefined by:\n";
|
|
|
|
printAttr(ctx, out, "files", option);
|
|
|
|
out << "\n";
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printListing(Out & out, Value * v)
|
|
|
|
{
|
|
|
|
// Print this header on stderr rather than stdout because the old shell script
|
|
|
|
// implementation did. I don't know why.
|
|
|
|
std::cerr << "This attribute set contains:\n";
|
|
|
|
for (const auto & a : v->attrs->lexicographicOrder()) {
|
|
|
|
std::string name = a->name;
|
|
|
|
if (!name.empty() && name[0] != '_') {
|
|
|
|
out << name << "\n";
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 22:32:22 +01:00
|
|
|
bool optionTypeIs(Context * ctx, Value & v, std::string const & sought_type)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
auto const & type_lookup = v.attrs->find(ctx->state->sType);
|
|
|
|
if (type_lookup == v.attrs->end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Value type = evaluateValue(ctx, type_lookup->value);
|
|
|
|
if (type.type != tAttrs) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto const & name_lookup = type.attrs->find(ctx->state->sName);
|
|
|
|
if (name_lookup == type.attrs->end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Value name = evaluateValue(ctx, name_lookup->value);
|
|
|
|
if (name.type != tString) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return name.string.s == sought_type;
|
|
|
|
} catch (Error &) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Value getSubOptions(Context * ctx, Value & option)
|
|
|
|
{
|
|
|
|
Value getSubOptions =
|
|
|
|
evaluateValue(ctx, findAlongAttrPath(*ctx->state, "type.getSubOptions", *ctx->autoArgs, option));
|
|
|
|
if (getSubOptions.type != tLambda) {
|
|
|
|
throw Error("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;
|
|
|
|
}
|
|
|
|
|
2019-09-06 01:29:01 +01:00
|
|
|
// Carefully walk an option path, looking for sub-options when a path walks past
|
|
|
|
// an option value.
|
2019-09-06 17:45:05 +01:00
|
|
|
Value findAlongOptionPath(Context * ctx, std::string const & path)
|
|
|
|
{
|
|
|
|
Strings tokens = parseAttrPath(path);
|
|
|
|
Value v = ctx->options_root;
|
|
|
|
for (auto i = tokens.begin(); i != tokens.end(); i++) {
|
|
|
|
bool last_attribute = std::next(i) == tokens.end();
|
|
|
|
auto const & attr = *i;
|
|
|
|
v = evaluateValue(ctx, &v);
|
|
|
|
if (attr.empty()) {
|
2019-09-06 17:51:08 +01:00
|
|
|
throw Error("empty attribute name in selection path '%s'", path);
|
2019-09-06 17:45:05 +01:00
|
|
|
}
|
2019-09-06 22:32:22 +01:00
|
|
|
if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
|
|
|
|
v = getSubOptions(ctx, v);
|
|
|
|
}
|
|
|
|
if (isOption(ctx, v) && optionTypeIs(ctx, v, "loaOf") && !last_attribute) {
|
|
|
|
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".
|
2019-09-06 17:45:05 +01:00
|
|
|
} else if (v.type != tAttrs) {
|
2019-09-06 17:51:08 +01:00
|
|
|
throw Error("attribute '%s' in path '%s' attempts to index a value that should be a set but is %s", attr,
|
|
|
|
path, showType(v));
|
2019-09-06 17:45:05 +01:00
|
|
|
} else {
|
|
|
|
auto const & next = v.attrs->find(ctx->state->symbols.create(attr));
|
|
|
|
if (next == v.attrs->end()) {
|
2019-09-06 17:51:08 +01:00
|
|
|
throw Error("attribute '%s' in path '%s' not found", attr, path);
|
2019-09-06 17:45:05 +01:00
|
|
|
}
|
|
|
|
v = *next->value;
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
2019-09-06 17:45:05 +01:00
|
|
|
return v;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
void printOne(Context * ctx, Out & out, std::string const & path)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
Value option = findAlongOptionPath(ctx, path);
|
|
|
|
option = evaluateValue(ctx, &option);
|
|
|
|
if (isOption(ctx, option)) {
|
|
|
|
printOption(ctx, out, path, &option);
|
|
|
|
} else {
|
|
|
|
printListing(out, &option);
|
|
|
|
}
|
|
|
|
} catch (Error & e) {
|
|
|
|
std::cerr << "error: " << e.msg()
|
|
|
|
<< "\nAn error occurred while looking for attribute names. Are "
|
|
|
|
"you sure that '"
|
|
|
|
<< path << "' exists?\n";
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
int main(int argc, char ** argv)
|
|
|
|
{
|
|
|
|
bool all = false;
|
|
|
|
std::string path = ".";
|
|
|
|
std::string options_expr = "(import <nixpkgs/nixos> {}).options";
|
|
|
|
std::string config_expr = "(import <nixpkgs/nixos> {}).config";
|
|
|
|
std::vector<std::string> args;
|
|
|
|
|
|
|
|
struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs
|
|
|
|
{
|
|
|
|
using nix::LegacyArgs::LegacyArgs;
|
|
|
|
};
|
|
|
|
|
|
|
|
MyArgs myArgs(nix::baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
|
|
|
|
if (*arg == "--help") {
|
|
|
|
nix::showManPage("nixos-option");
|
|
|
|
} else if (*arg == "--version") {
|
|
|
|
nix::printVersion("nixos-option");
|
|
|
|
} else if (*arg == "--all") {
|
|
|
|
all = true;
|
|
|
|
} else if (*arg == "--path") {
|
|
|
|
path = nix::getArg(*arg, arg, end);
|
|
|
|
} else if (*arg == "--options_expr") {
|
|
|
|
options_expr = nix::getArg(*arg, arg, end);
|
|
|
|
} else if (*arg == "--config_expr") {
|
|
|
|
config_expr = nix::getArg(*arg, arg, end);
|
|
|
|
} else if (!arg->empty() && arg->at(0) == '-') {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
args.push_back(*arg);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
myArgs.parseCmdline(nix::argvToStrings(argc, argv));
|
|
|
|
|
|
|
|
nix::initPlugins();
|
|
|
|
nix::initGC();
|
|
|
|
nix::settings.readOnlyMode = true;
|
|
|
|
auto store = nix::openStore();
|
|
|
|
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
|
|
|
|
|
|
|
|
Value options_root = parseAndEval(state.get(), options_expr, path);
|
|
|
|
Value config_root = parseAndEval(state.get(), config_expr, path);
|
|
|
|
|
|
|
|
Context ctx{state.get(), myArgs.getAutoArgs(*state), options_root, config_root};
|
|
|
|
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 (auto const & arg : args) {
|
|
|
|
printOne(&ctx, out, arg);
|
|
|
|
}
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
ctx.state->printStats();
|
2019-09-06 01:29:01 +01:00
|
|
|
|
2019-09-06 17:45:05 +01:00
|
|
|
return 0;
|
2019-09-06 01:29:01 +01:00
|
|
|
}
|