This commit is contained in:
Eelco Dolstra 2020-05-11 15:46:18 +02:00
parent 0884f180f5
commit 4c3c638a05
9 changed files with 145 additions and 123 deletions

View file

@ -86,7 +86,7 @@ void Args::printHelp(const string & programName, std::ostream & out)
for (auto & exp : expectedArgs) { for (auto & exp : expectedArgs) {
std::cout << renderLabels({exp.label}); std::cout << renderLabels({exp.label});
// FIXME: handle arity > 1 // FIXME: handle arity > 1
if (exp.arity == 0) std::cout << "..."; if (exp.handler.arity == ArityAny) std::cout << "...";
if (exp.optional) std::cout << "?"; if (exp.optional) std::cout << "?";
} }
std::cout << "\n"; std::cout << "\n";
@ -127,8 +127,8 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (flag.handler.arity == ArityAny) break; if (flag.handler.arity == ArityAny) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
} }
if (auto prefix = needsCompletion(*pos))
if (flag.completer) if (flag.completer)
if (auto prefix = needsCompletion(*pos))
flag.completer(n, *prefix); flag.completer(n, *prefix);
args.push_back(*pos++); args.push_back(*pos++);
} }
@ -179,12 +179,17 @@ bool Args::processArgs(const Strings & args, bool finish)
bool res = false; bool res = false;
if ((exp.arity == 0 && finish) || if ((exp.handler.arity == ArityAny && finish) ||
(exp.arity > 0 && args.size() == exp.arity)) (exp.handler.arity != ArityAny && args.size() == exp.handler.arity))
{ {
std::vector<std::string> ss; std::vector<std::string> ss;
for (auto & s : args) ss.push_back(s); for (const auto &[n, s] : enumerate(args)) {
exp.handler(std::move(ss)); ss.push_back(s);
if (exp.completer)
if (auto prefix = needsCompletion(s))
exp.completer(n, *prefix);
}
exp.handler.fun(ss);
expectedArgs.pop_front(); expectedArgs.pop_front();
res = true; res = true;
} }
@ -214,44 +219,15 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
}; };
} }
void completePath(size_t, std::string_view s) void completePath(size_t, std::string_view prefix)
{ {
if (auto prefix = needsCompletion(s)) {
pathCompletions = true; pathCompletions = true;
glob_t globbuf; glob_t globbuf;
if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) {
for (size_t i = 0; i < globbuf.gl_pathc; ++i) for (size_t i = 0; i < globbuf.gl_pathc; ++i)
completions->insert(globbuf.gl_pathv[i]); completions->insert(globbuf.gl_pathv[i]);
globfree(&globbuf); globfree(&globbuf);
} }
}
}
void Args::expectPathArg(const std::string & label, string * dest, bool optional)
{
expectedArgs.push_back({
.label = label,
.arity = 1,
.optional = optional,
.handler = {[=](std::vector<std::string> ss) {
completePath(0, ss[0]);
*dest = ss[0];
}}
});
}
void Args::expectPathArgs(const std::string & label, std::vector<std::string> * dest)
{
expectedArgs.push_back({
.label = label,
.arity = 0,
.optional = false,
.handler = {[=](std::vector<std::string> ss) {
for (auto & s : ss)
completePath(0, s);
*dest = std::move(ss);
}}
});
} }
Strings argvToStrings(int argc, char * * argv) Strings argvToStrings(int argc, char * * argv)
@ -301,18 +277,22 @@ void Command::printHelp(const string & programName, std::ostream & out)
MultiCommand::MultiCommand(const Commands & commands) MultiCommand::MultiCommand(const Commands & commands)
: commands(commands) : commands(commands)
{ {
expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) { expectArgs({
.label = "command",
.optional = true,
.handler = {[=](std::string s) {
assert(!command); assert(!command);
if (auto prefix = needsCompletion(ss[0])) { if (auto prefix = needsCompletion(s)) {
for (auto & [name, command] : commands) for (auto & [name, command] : commands)
if (hasPrefix(name, *prefix)) if (hasPrefix(name, *prefix))
completions->insert(name); completions->insert(name);
} }
auto i = commands.find(ss[0]); auto i = commands.find(s);
if (i == commands.end()) if (i == commands.end())
throw UsageError("'%s' is not a recognised command", ss[0]); throw UsageError("'%s' is not a recognised command", s);
command = {ss[0], i->second()}; command = {s, i->second()};
}}); }}
});
categories[Command::catDefault] = "Available commands"; categories[Command::catDefault] = "Available commands";
} }

View file

@ -28,11 +28,6 @@ protected:
static const size_t ArityAny = std::numeric_limits<size_t>::max(); static const size_t ArityAny = std::numeric_limits<size_t>::max();
/* Flags. */
struct Flag
{
typedef std::shared_ptr<Flag> ptr;
struct Handler struct Handler
{ {
std::function<void(std::vector<std::string>)> fun; std::function<void(std::vector<std::string>)> fun;
@ -64,6 +59,11 @@ protected:
, arity(2) , arity(2)
{ } { }
Handler(std::vector<std::string> * dest)
: fun([=](std::vector<std::string> ss) { *dest = ss; })
, arity(ArityAny)
{ }
template<class T> template<class T>
Handler(T * dest) Handler(T * dest)
: fun([=](std::vector<std::string> ss) { *dest = ss[0]; }) : fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
@ -77,6 +77,11 @@ protected:
{ } { }
}; };
/* Flags. */
struct Flag
{
typedef std::shared_ptr<Flag> ptr;
std::string longName; std::string longName;
char shortName = 0; char shortName = 0;
std::string description; std::string description;
@ -99,9 +104,9 @@ protected:
struct ExpectedArg struct ExpectedArg
{ {
std::string label; std::string label;
size_t arity = 0; // 0 = any
bool optional = false; bool optional = false;
std::function<void(std::vector<std::string>)> handler; Handler handler;
std::function<void(size_t, std::string_view)> completer;
}; };
std::list<ExpectedArg> expectedArgs; std::list<ExpectedArg> expectedArgs;
@ -175,26 +180,30 @@ public:
}); });
} }
void expectArgs(ExpectedArg && arg)
{
expectedArgs.emplace_back(std::move(arg));
}
/* Expect a string argument. */ /* Expect a string argument. */
void expectArg(const std::string & label, string * dest, bool optional = false) void expectArg(const std::string & label, string * dest, bool optional = false)
{ {
expectedArgs.push_back(ExpectedArg{label, 1, optional, [=](std::vector<std::string> ss) { expectArgs({
*dest = ss[0]; .label = label,
}}); .optional = true,
.handler = {dest}
});
} }
void expectPathArg(const std::string & label, string * dest, bool optional = false);
/* Expect 0 or more arguments. */ /* Expect 0 or more arguments. */
void expectArgs(const std::string & label, std::vector<std::string> * dest) void expectArgs(const std::string & label, std::vector<std::string> * dest)
{ {
expectedArgs.push_back(ExpectedArg{label, 0, false, [=](std::vector<std::string> ss) { expectArgs({
*dest = std::move(ss); .label = label,
}}); .handler = {dest}
});
} }
void expectPathArgs(const std::string & label, std::vector<std::string> * dest);
friend class MultiCommand; friend class MultiCommand;
}; };
@ -266,6 +275,6 @@ extern bool pathCompletions;
std::optional<std::string> needsCompletion(std::string_view s); std::optional<std::string> needsCompletion(std::string_view s);
void completePath(size_t, std::string_view s); void completePath(size_t, std::string_view prefix);
} }

View file

@ -25,7 +25,11 @@ struct CmdCatStore : StoreCommand, MixCat
{ {
CmdCatStore() CmdCatStore()
{ {
expectPathArg("path", &path); expectArgs({
.label = "path",
.handler = {&path},
.completer = completePath
});
} }
std::string description() override std::string description() override
@ -47,7 +51,11 @@ struct CmdCatNar : StoreCommand, MixCat
CmdCatNar() CmdCatNar()
{ {
expectPathArg("nar", &narPath); expectArgs({
.label = "nar",
.handler = {&narPath},
.completer = completePath
});
expectArg("path", &path); expectArg("path", &path);
} }

View file

@ -73,10 +73,7 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
{ {
std::vector<std::shared_ptr<Installable>> installables; std::vector<std::shared_ptr<Installable>> installables;
InstallablesCommand() InstallablesCommand();
{
expectArgs("installables", &_installables);
}
void prepare() override; void prepare() override;

View file

@ -31,7 +31,11 @@ struct CmdHash : Command
.labels({"modulus"}) .labels({"modulus"})
.dest(&modulus); .dest(&modulus);
#endif #endif
expectPathArgs("paths", &paths); expectArgs({
.label = "paths",
.handler = {&paths},
.completer = completePath
});
} }
std::string description() override std::string description() override

View file

@ -571,6 +571,14 @@ StorePathSet toDerivations(ref<Store> store,
return drvPaths; return drvPaths;
} }
InstallablesCommand::InstallablesCommand()
{
expectArgs({
.label = "installables",
.handler = {&_installables},
});
}
void InstallablesCommand::prepare() void InstallablesCommand::prepare()
{ {
if (_installables.empty() && useDefaultInstallables()) if (_installables.empty() && useDefaultInstallables())

View file

@ -85,7 +85,11 @@ struct CmdLsStore : StoreCommand, MixLs
{ {
CmdLsStore() CmdLsStore()
{ {
expectPathArg("path", &path); expectArgs({
.label = "path",
.handler = {&path},
.completer = completePath
});
} }
Examples examples() override Examples examples() override
@ -117,7 +121,11 @@ struct CmdLsNar : Command, MixLs
CmdLsNar() CmdLsNar()
{ {
expectPathArg("nar", &narPath); expectArgs({
.label = "nar",
.handler = {&narPath},
.completer = completePath
});
expectArg("path", &path); expectArg("path", &path);
} }

View file

@ -767,7 +767,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs
CmdRepl() CmdRepl()
{ {
expectPathArgs("files", &files); expectArgs({
.label = "files",
.handler = {&files},
.completer = completePath
});
} }
std::string description() override std::string description() override

View file

@ -149,7 +149,11 @@ struct CmdRun : InstallableCommand, RunCommon
CmdRun() CmdRun()
{ {
expectPathArgs("args", &args); expectArgs({
.label = "args",
.handler = {&args},
.completer = completePath
});
} }
std::string description() override std::string description() override