aboutsummaryrefslogtreecommitdiff
path: root/src/libutil/args.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil/args.cc')
-rw-r--r--src/libutil/args.cc124
1 files changed, 81 insertions, 43 deletions
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 02d559540..3e39b4d7c 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -1,4 +1,5 @@
#include "args.hh"
+#include "args/root.hh"
#include "hash.hh"
#include "json-utils.hh"
@@ -26,6 +27,11 @@ void Args::removeFlag(const std::string & longName)
longFlags.erase(flag);
}
+void Completions::setType(AddCompletions::Type t)
+{
+ type = t;
+}
+
void Completions::add(std::string completion, std::string description)
{
description = trim(description);
@@ -37,7 +43,7 @@ void Completions::add(std::string completion, std::string description)
if (needs_ellipsis)
description.append(" [...]");
}
- insert(Completion {
+ completions.insert(Completion {
.completion = completion,
.description = description
});
@@ -46,12 +52,20 @@ void Completions::add(std::string completion, std::string description)
bool Completion::operator<(const Completion & other) const
{ return completion < other.completion || (completion == other.completion && description < other.description); }
-CompletionType completionType = ctNormal;
-std::shared_ptr<Completions> completions;
-
std::string completionMarker = "___COMPLETE___";
-static std::optional<std::string> needsCompletion(std::string_view s)
+RootArgs & Args::getRoot()
+{
+ Args * p = this;
+ while (p->parent)
+ p = p->parent;
+
+ auto * res = dynamic_cast<RootArgs *>(p);
+ assert(res);
+ return *res;
+}
+
+std::optional<std::string> RootArgs::needsCompletion(std::string_view s)
{
if (!completions) return {};
auto i = s.find(completionMarker);
@@ -60,7 +74,7 @@ static std::optional<std::string> needsCompletion(std::string_view s)
return {};
}
-void Args::parseCmdline(const Strings & _cmdline)
+void RootArgs::parseCmdline(const Strings & _cmdline)
{
Strings pendingArgs;
bool dashDash = false;
@@ -71,7 +85,7 @@ void Args::parseCmdline(const Strings & _cmdline)
size_t n = std::stoi(*s);
assert(n > 0 && n <= cmdline.size());
*std::next(cmdline.begin(), n - 1) += completionMarker;
- completions = std::make_shared<decltype(completions)::element_type>();
+ completions = std::make_shared<Completions>();
verbosity = lvlError;
}
@@ -125,17 +139,23 @@ void Args::parseCmdline(const Strings & _cmdline)
for (auto & f : flagExperimentalFeatures)
experimentalFeatureSettings.require(f);
+ /* Now that all the other args are processed, run the deferred completions.
+ */
+ for (auto d : deferredCompletions)
+ d.completer(*completions, d.n, d.prefix);
}
bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
{
assert(pos != end);
+ auto & rootArgs = getRoot();
+
auto process = [&](const std::string & name, const Flag & flag) -> bool {
++pos;
if (auto & f = flag.experimentalFeature)
- flagExperimentalFeatures.insert(*f);
+ rootArgs.flagExperimentalFeatures.insert(*f);
std::vector<std::string> args;
bool anyCompleted = false;
@@ -146,10 +166,15 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
"flag '%s' requires %d argument(s), but only %d were given",
name, flag.handler.arity, n);
}
- if (auto prefix = needsCompletion(*pos)) {
+ if (auto prefix = rootArgs.needsCompletion(*pos)) {
anyCompleted = true;
- if (flag.completer)
- flag.completer(n, *prefix);
+ if (flag.completer) {
+ rootArgs.deferredCompletions.push_back({
+ .completer = flag.completer,
+ .n = n,
+ .prefix = *prefix,
+ });
+ }
}
args.push_back(*pos++);
}
@@ -159,14 +184,14 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
};
if (std::string(*pos, 0, 2) == "--") {
- if (auto prefix = needsCompletion(*pos)) {
+ if (auto prefix = rootArgs.needsCompletion(*pos)) {
for (auto & [name, flag] : longFlags) {
if (!hiddenCategories.count(flag->category)
&& name.starts_with(std::string(*prefix, 2)))
{
if (auto & f = flag->experimentalFeature)
- flagExperimentalFeatures.insert(*f);
- completions->add("--" + name, flag->description);
+ rootArgs.flagExperimentalFeatures.insert(*f);
+ rootArgs.completions->add("--" + name, flag->description);
}
}
return false;
@@ -183,12 +208,12 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
return process(std::string("-") + c, *i->second);
}
- if (auto prefix = needsCompletion(*pos)) {
+ if (auto prefix = rootArgs.needsCompletion(*pos)) {
if (prefix == "-") {
- completions->add("--");
+ rootArgs.completions->add("--");
for (auto & [flagName, flag] : shortFlags)
if (experimentalFeatureSettings.isEnabled(flag->experimentalFeature))
- completions->add(std::string("-") + flagName, flag->description);
+ rootArgs.completions->add(std::string("-") + flagName, flag->description);
}
}
@@ -203,6 +228,8 @@ bool Args::processArgs(const Strings & args, bool finish)
return true;
}
+ auto & rootArgs = getRoot();
+
auto & exp = expectedArgs.front();
bool res = false;
@@ -211,16 +238,35 @@ bool Args::processArgs(const Strings & args, bool finish)
(exp.handler.arity != ArityAny && args.size() == exp.handler.arity))
{
std::vector<std::string> ss;
+ bool anyCompleted = false;
for (const auto &[n, s] : enumerate(args)) {
- if (auto prefix = needsCompletion(s)) {
+ if (auto prefix = rootArgs.needsCompletion(s)) {
+ anyCompleted = true;
ss.push_back(*prefix);
- if (exp.completer)
- exp.completer(n, *prefix);
+ if (exp.completer) {
+ rootArgs.deferredCompletions.push_back({
+ .completer = exp.completer,
+ .n = n,
+ .prefix = *prefix,
+ });
+ }
} else
ss.push_back(s);
}
- exp.handler.fun(ss);
- expectedArgs.pop_front();
+ if (!anyCompleted)
+ exp.handler.fun(ss);
+
+ /* Move the list element to the processedArgs. This is almost the same as
+ `processedArgs.push_back(expectedArgs.front()); expectedArgs.pop_front()`,
+ except that it will only adjust the next and prev pointers of the list
+ elements, meaning the actual contents don't move in memory. This is
+ critical to prevent invalidating internal pointers! */
+ processedArgs.splice(
+ processedArgs.end(),
+ expectedArgs,
+ expectedArgs.begin(),
+ ++expectedArgs.begin());
+
res = true;
}
@@ -271,11 +317,11 @@ nlohmann::json Args::toJSON()
return res;
}
-static void hashTypeCompleter(size_t index, std::string_view prefix)
+static void hashTypeCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & type : hashTypes)
if (type.starts_with(prefix))
- completions->add(type);
+ completions.add(type);
}
Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
@@ -287,7 +333,7 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
.handler = {[ht](std::string s) {
*ht = parseHashType(s);
}},
- .completer = hashTypeCompleter
+ .completer = hashTypeCompleter,
};
}
@@ -300,13 +346,13 @@ Args::Flag Args::Flag::mkHashTypeOptFlag(std::string && longName, std::optional<
.handler = {[oht](std::string s) {
*oht = std::optional<HashType> { parseHashType(s) };
}},
- .completer = hashTypeCompleter
+ .completer = hashTypeCompleter,
};
}
-static void _completePath(std::string_view prefix, bool onlyDirs)
+static void _completePath(AddCompletions & completions, std::string_view prefix, bool onlyDirs)
{
- completionType = ctFilenames;
+ completions.setType(Completions::Type::Filenames);
glob_t globbuf;
int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR
@@ -320,20 +366,20 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
auto st = stat(globbuf.gl_pathv[i]);
if (!S_ISDIR(st.st_mode)) continue;
}
- completions->add(globbuf.gl_pathv[i]);
+ completions.add(globbuf.gl_pathv[i]);
}
}
globfree(&globbuf);
}
-void completePath(size_t, std::string_view prefix)
+void Args::completePath(AddCompletions & completions, size_t, std::string_view prefix)
{
- _completePath(prefix, false);
+ _completePath(completions, prefix, false);
}
-void completeDir(size_t, std::string_view prefix)
+void Args::completeDir(AddCompletions & completions, size_t, std::string_view prefix)
{
- _completePath(prefix, true);
+ _completePath(completions, prefix, true);
}
Strings argvToStrings(int argc, char * * argv)
@@ -368,10 +414,10 @@ MultiCommand::MultiCommand(const Commands & commands_)
command = {s, i->second()};
command->second->parent = this;
}},
- .completer = {[&](size_t, std::string_view prefix) {
+ .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
for (auto & [name, command] : commands)
if (name.starts_with(prefix))
- completions->add(name);
+ completions.add(name);
}}
});
@@ -393,14 +439,6 @@ bool MultiCommand::processArgs(const Strings & args, bool finish)
return Args::processArgs(args, finish);
}
-void MultiCommand::completionHook()
-{
- if (command)
- return command->second->completionHook();
- else
- return Args::completionHook();
-}
-
nlohmann::json MultiCommand::toJSON()
{
auto cmds = nlohmann::json::object();