From f70434b1fbbdb0e188718f0c55a8156a7aa08744 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 22 Nov 2018 16:03:31 +0100 Subject: Move Command and MultiCommand to libutil --- src/libutil/args.cc | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 7af2a1bf7..2837dacc9 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -200,4 +200,73 @@ void printTable(std::ostream & out, const Table2 & table) } } +void Command::printHelp(const string & programName, std::ostream & out) +{ + Args::printHelp(programName, out); + + auto exs = examples(); + if (!exs.empty()) { + out << "\n"; + out << "Examples:\n"; + for (auto & ex : exs) + out << "\n" + << " " << ex.description << "\n" // FIXME: wrap + << " $ " << ex.command << "\n"; + } +} + +MultiCommand::MultiCommand(const std::vector> & _commands) +{ + for (auto & command : _commands) + commands.emplace(command->name(), command); + + expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { + assert(!command); + auto i = commands.find(ss[0]); + if (i == commands.end()) + throw UsageError("'%s' is not a recognised command", ss[0]); + command = i->second; + }}); +} + +void MultiCommand::printHelp(const string & programName, std::ostream & out) +{ + if (command) { + command->printHelp(programName + " " + command->name(), out); + return; + } + + out << "Usage: " << programName << " ... ...\n"; + + out << "\n"; + out << "Common flags:\n"; + printFlags(out); + + out << "\n"; + out << "Available commands:\n"; + + Table2 table; + for (auto & command : commands) { + auto descr = command.second->description(); + if (!descr.empty()) + table.push_back(std::make_pair(command.second->name(), descr)); + } + printTable(out, table); +} + +bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end) +{ + if (Args::processFlag(pos, end)) return true; + if (command && command->processFlag(pos, end)) return true; + return false; +} + +bool MultiCommand::processArgs(const Strings & args, bool finish) +{ + if (command) + return command->processArgs(args, finish); + else + return Args::processArgs(args, finish); +} + } -- cgit v1.2.3 From a0de58f471c9087d8e6cc60a6078f9940a125b15 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Jun 2019 16:01:35 +0200 Subject: Make subcommand construction in MultiCommand lazy --- src/libutil/args.cc | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 2837dacc9..217495c26 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -215,17 +215,15 @@ void Command::printHelp(const string & programName, std::ostream & out) } } -MultiCommand::MultiCommand(const std::vector> & _commands) +MultiCommand::MultiCommand(const Commands & commands) + : commands(commands) { - for (auto & command : _commands) - commands.emplace(command->name(), command); - expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { assert(!command); auto i = commands.find(ss[0]); if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); - command = i->second; + command = i->second(); }}); } @@ -246,10 +244,11 @@ void MultiCommand::printHelp(const string & programName, std::ostream & out) out << "Available commands:\n"; Table2 table; - for (auto & command : commands) { - auto descr = command.second->description(); + for (auto & i : commands) { + auto command = i.second(); + auto descr = command->description(); if (!descr.empty()) - table.push_back(std::make_pair(command.second->name(), descr)); + table.push_back(std::make_pair(command->name(), descr)); } printTable(out, table); } -- cgit v1.2.3 From d0a769cb061a13ad880c76e5ea69a76150439853 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Jun 2019 23:37:40 +0200 Subject: Initialize Command::_name --- src/libutil/args.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 217495c26..ba15ea571 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -224,6 +224,7 @@ MultiCommand::MultiCommand(const Commands & commands) if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); command = i->second(); + command->_name = ss[0]; }}); } @@ -246,6 +247,7 @@ void MultiCommand::printHelp(const string & programName, std::ostream & out) Table2 table; for (auto & i : commands) { auto command = i.second(); + command->_name = i.first; auto descr = command->description(); if (!descr.empty()) table.push_back(std::make_pair(command->name(), descr)); -- cgit v1.2.3 From 91ddee6bf045b1c6144d14233abdb96127186ec3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 20:32:21 +0200 Subject: nix: Implement basic bash completion --- src/libutil/args.cc | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f829415d1..ceaabcfca 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -13,6 +13,19 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +std::shared_ptr> completions; + +std::string completionMarker = "___COMPLETE___"; + +std::optional needsCompletion(std::string_view s) +{ + if (!completions) return {}; + auto i = s.find(completionMarker); + if (i != std::string::npos) + return std::string(s.begin(), i); + return {}; +} + void Args::parseCmdline(const Strings & _cmdline) { Strings pendingArgs; @@ -20,6 +33,13 @@ void Args::parseCmdline(const Strings & _cmdline) Strings cmdline(_cmdline); + if (auto s = getEnv("NIX_GET_COMPLETIONS")) { + size_t n = std::stoi(*s); + assert(n > 0 && n <= cmdline.size()); + *std::next(cmdline.begin(), n - 1) += completionMarker; + completions = std::make_shared(); + } + for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { auto arg = *pos; @@ -99,18 +119,32 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; + bool anyNeedsCompletion = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; - throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); + if (anyNeedsCompletion) + args.push_back(""); + else + throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); + } else { + if (needsCompletion(*pos)) + anyNeedsCompletion = true; + args.push_back(*pos++); } - args.push_back(*pos++); } flag.handler.fun(std::move(args)); return true; }; if (string(*pos, 0, 2) == "--") { + if (auto prefix = needsCompletion(*pos)) { + for (auto & [name, flag] : longFlags) { + if (!hiddenCategories.count(flag->category) + && hasPrefix(name, std::string(*prefix, 2))) + completions->insert("--" + name); + } + } auto i = longFlags.find(string(*pos, 2)); if (i == longFlags.end()) return false; return process("--" + i->first, *i->second); @@ -123,6 +157,14 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) return process(std::string("-") + c, *i->second); } + if (auto prefix = needsCompletion(*pos)) { + if (prefix == "-") { + completions->insert("--"); + for (auto & [flag, _] : shortFlags) + completions->insert(std::string("-") + flag); + } + } + return false; } @@ -161,6 +203,10 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')", .labels = {"hash-algo"}, .handler = {[ht](std::string s) { + if (auto prefix = needsCompletion(s)) + for (auto & type : hashTypes) + if (hasPrefix(type, *prefix)) + completions->insert(type); *ht = parseHashType(s); if (*ht == htUnknown) throw UsageError("unknown hash type '%1%'", s); @@ -217,6 +263,11 @@ MultiCommand::MultiCommand(const Commands & commands) { expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { assert(!command); + if (auto prefix = needsCompletion(ss[0])) { + for (auto & [name, command] : commands) + if (hasPrefix(name, *prefix)) + completions->insert(name); + } auto i = commands.find(ss[0]); if (i == commands.end()) throw UsageError("'%s' is not a recognised command", ss[0]); -- cgit v1.2.3 From e0c19ee620c53b52ca7cf69c19d414d782338be1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:35:07 +0200 Subject: Add completion for paths --- src/libutil/args.cc | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ceaabcfca..320b1b4b2 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -1,6 +1,8 @@ #include "args.hh" #include "hash.hh" +#include + namespace nix { void Args::addFlag(Flag && flag_) @@ -13,6 +15,7 @@ void Args::addFlag(Flag && flag_) if (flag->shortName) shortFlags[flag->shortName] = flag; } +bool pathCompletions = false; std::shared_ptr> completions; std::string completionMarker = "___COMPLETE___"; @@ -128,8 +131,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) else throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } else { - if (needsCompletion(*pos)) + if (needsCompletion(*pos)) { + if (flag.completer) + flag.completer(n, *pos); anyNeedsCompletion = true; + } args.push_back(*pos++); } } @@ -214,6 +220,46 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } +void completePath(size_t, std::string_view s) +{ + if (auto prefix = needsCompletion(s)) { + pathCompletions = true; + glob_t globbuf; + if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) + completions->insert(globbuf.gl_pathv[i]); + 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 ss) { + completePath(0, ss[0]); + *dest = ss[0]; + }} + }); +} + +void Args::expectPathArgs(const std::string & label, std::vector * dest) +{ + expectedArgs.push_back({ + .label = label, + .arity = 0, + .optional = false, + .handler = {[=](std::vector ss) { + for (auto & s : ss) + completePath(0, s); + *dest = std::move(ss); + }} + }); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; -- cgit v1.2.3 From 0884f180f5ca8a864e6db5256eaa10646e87d671 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 10 May 2020 21:50:32 +0200 Subject: Simplify --- src/libutil/args.cc | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 320b1b4b2..257b4b6c4 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -122,22 +122,15 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; - bool anyNeedsCompletion = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; - if (anyNeedsCompletion) - args.push_back(""); - else - throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); - } else { - if (needsCompletion(*pos)) { - if (flag.completer) - flag.completer(n, *pos); - anyNeedsCompletion = true; - } - args.push_back(*pos++); + throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } + if (auto prefix = needsCompletion(*pos)) + if (flag.completer) + flag.completer(n, *prefix); + args.push_back(*pos++); } flag.handler.fun(std::move(args)); return true; @@ -209,14 +202,15 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) .description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')", .labels = {"hash-algo"}, .handler = {[ht](std::string s) { - if (auto prefix = needsCompletion(s)) - for (auto & type : hashTypes) - if (hasPrefix(type, *prefix)) - completions->insert(type); *ht = parseHashType(s); if (*ht == htUnknown) throw UsageError("unknown hash type '%1%'", s); - }} + }}, + .completer = [](size_t index, std::string_view prefix) { + for (auto & type : hashTypes) + if (hasPrefix(type, prefix)) + completions->insert(type); + } }; } -- cgit v1.2.3 From 4c3c638a05e52cdc3bd96255873b711a28630288 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 15:46:18 +0200 Subject: Cleanup --- src/libutil/args.cc | 90 +++++++++++++++++++++-------------------------------- 1 file changed, 35 insertions(+), 55 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 257b4b6c4..c389090ae 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -86,7 +86,7 @@ void Args::printHelp(const string & programName, std::ostream & out) for (auto & exp : expectedArgs) { std::cout << renderLabels({exp.label}); // FIXME: handle arity > 1 - if (exp.arity == 0) std::cout << "..."; + if (exp.handler.arity == ArityAny) std::cout << "..."; if (exp.optional) std::cout << "?"; } std::cout << "\n"; @@ -127,8 +127,8 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) if (flag.handler.arity == ArityAny) break; 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); args.push_back(*pos++); } @@ -179,12 +179,17 @@ bool Args::processArgs(const Strings & args, bool finish) bool res = false; - if ((exp.arity == 0 && finish) || - (exp.arity > 0 && args.size() == exp.arity)) + if ((exp.handler.arity == ArityAny && finish) || + (exp.handler.arity != ArityAny && args.size() == exp.handler.arity)) { std::vector ss; - for (auto & s : args) ss.push_back(s); - exp.handler(std::move(ss)); + for (const auto &[n, s] : enumerate(args)) { + ss.push_back(s); + if (exp.completer) + if (auto prefix = needsCompletion(s)) + exp.completer(n, *prefix); + } + exp.handler.fun(ss); expectedArgs.pop_front(); res = true; } @@ -214,46 +219,17 @@ 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; - glob_t globbuf; - if (glob((*prefix + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { - for (size_t i = 0; i < globbuf.gl_pathc; ++i) - completions->insert(globbuf.gl_pathv[i]); - globfree(&globbuf); - } + pathCompletions = true; + glob_t globbuf; + if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) + completions->insert(globbuf.gl_pathv[i]); + 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 ss) { - completePath(0, ss[0]); - *dest = ss[0]; - }} - }); -} - -void Args::expectPathArgs(const std::string & label, std::vector * dest) -{ - expectedArgs.push_back({ - .label = label, - .arity = 0, - .optional = false, - .handler = {[=](std::vector ss) { - for (auto & s : ss) - completePath(0, s); - *dest = std::move(ss); - }} - }); -} - Strings argvToStrings(int argc, char * * argv) { Strings args; @@ -301,18 +277,22 @@ void Command::printHelp(const string & programName, std::ostream & out) MultiCommand::MultiCommand(const Commands & commands) : commands(commands) { - expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector ss) { - assert(!command); - if (auto prefix = needsCompletion(ss[0])) { - for (auto & [name, command] : commands) - if (hasPrefix(name, *prefix)) - completions->insert(name); - } - auto i = commands.find(ss[0]); - if (i == commands.end()) - throw UsageError("'%s' is not a recognised command", ss[0]); - command = {ss[0], i->second()}; - }}); + expectArgs({ + .label = "command", + .optional = true, + .handler = {[=](std::string s) { + assert(!command); + if (auto prefix = needsCompletion(s)) { + for (auto & [name, command] : commands) + if (hasPrefix(name, *prefix)) + completions->insert(name); + } + auto i = commands.find(s); + if (i == commands.end()) + throw UsageError("'%s' is not a recognised command", s); + command = {s, i->second()}; + }} + }); categories[Command::catDefault] = "Available commands"; } -- cgit v1.2.3 From e917332d6396fe9808519e7a87916f789b71392a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 21:38:17 +0200 Subject: Shut up warnings while running completers --- src/libutil/args.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index c389090ae..ee0c99c2a 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -41,6 +41,7 @@ void Args::parseCmdline(const Strings & _cmdline) assert(n > 0 && n <= cmdline.size()); *std::next(cmdline.begin(), n - 1) += completionMarker; completions = std::make_shared(); + verbosity = lvlError; } for (auto pos = cmdline.begin(); pos != cmdline.end(); ) { -- cgit v1.2.3 From 27d34ef770356f86823ca832f278e72bb0a07982 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 11 May 2020 22:04:13 +0200 Subject: When completing flakerefs, only return directories --- src/libutil/args.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index ee0c99c2a..f6740e076 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -220,17 +220,27 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -void completePath(size_t, std::string_view prefix) +static void completePath(std::string_view prefix, int flags) { pathCompletions = true; glob_t globbuf; - if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE, nullptr, &globbuf) == 0) { + if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE | flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) completions->insert(globbuf.gl_pathv[i]); globfree(&globbuf); } } +void completePath(size_t, std::string_view prefix) +{ + completePath(prefix, 0); +} + +void completeDir(size_t, std::string_view prefix) +{ + completePath(prefix, GLOB_ONLYDIR); +} + Strings argvToStrings(int argc, char * * argv) { Strings args; -- cgit v1.2.3 From 437614b479dd30200dcdc1950f8d4fdddfef7a61 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 12 May 2020 11:08:59 +0200 Subject: Fix macOS build macOS doesn't have GLOB_ONLYDIR. --- src/libutil/args.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f6740e076..8667bd450 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -220,25 +220,35 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht) }; } -static void completePath(std::string_view prefix, int flags) +static void completePath(std::string_view prefix, bool onlyDirs) { pathCompletions = true; glob_t globbuf; - if (glob((std::string(prefix) + "*").c_str(), GLOB_NOESCAPE | GLOB_TILDE | flags, nullptr, &globbuf) == 0) { - for (size_t i = 0; i < globbuf.gl_pathc; ++i) + int flags = GLOB_NOESCAPE | GLOB_TILDE; + #ifdef GLOB_ONLYDIR + if (onlyDirs) + flags |= GLOB_ONLYDIR; + #endif + if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { + for (size_t i = 0; i < globbuf.gl_pathc; ++i) { + if (onlyDirs) { + auto st = lstat(globbuf.gl_pathv[i]); + if (!S_ISDIR(st.st_mode)) continue; + } completions->insert(globbuf.gl_pathv[i]); + } globfree(&globbuf); } } void completePath(size_t, std::string_view prefix) { - completePath(prefix, 0); + completePath(prefix, false); } void completeDir(size_t, std::string_view prefix) { - completePath(prefix, GLOB_ONLYDIR); + completePath(prefix, true); } Strings argvToStrings(int argc, char * * argv) -- cgit v1.2.3 From 6ff9aa8df7ce8266147f74c65e2cc529a1e72ce0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 1 Jul 2020 20:31:39 +0200 Subject: Don't process an option if any of its arguments need completion --- src/libutil/args.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/libutil/args.cc') diff --git a/src/libutil/args.cc b/src/libutil/args.cc index b16a2e213..986c5d1cd 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -123,17 +123,21 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) auto process = [&](const std::string & name, const Flag & flag) -> bool { ++pos; std::vector args; + bool anyCompleted = false; for (size_t n = 0 ; n < flag.handler.arity; ++n) { if (pos == end) { if (flag.handler.arity == ArityAny) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } if (flag.completer) - if (auto prefix = needsCompletion(*pos)) + if (auto prefix = needsCompletion(*pos)) { + anyCompleted = true; flag.completer(n, *prefix); + } args.push_back(*pos++); } - flag.handler.fun(std::move(args)); + if (!anyCompleted) + flag.handler.fun(std::move(args)); return true; }; -- cgit v1.2.3