forked from lix-project/lix
nix: Implement basic bash completion
This commit is contained in:
parent
14a3a62bfc
commit
91ddee6bf0
1
Makefile
1
Makefile
|
@ -11,6 +11,7 @@ makefiles = \
|
||||||
src/resolve-system-dependencies/local.mk \
|
src/resolve-system-dependencies/local.mk \
|
||||||
scripts/local.mk \
|
scripts/local.mk \
|
||||||
corepkgs/local.mk \
|
corepkgs/local.mk \
|
||||||
|
misc/bash/local.mk \
|
||||||
misc/systemd/local.mk \
|
misc/systemd/local.mk \
|
||||||
misc/launchd/local.mk \
|
misc/launchd/local.mk \
|
||||||
misc/upstart/local.mk \
|
misc/upstart/local.mk \
|
||||||
|
|
7
misc/bash/completion.sh
Normal file
7
misc/bash/completion.sh
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
function _complete_nix {
|
||||||
|
while IFS= read -r line; do
|
||||||
|
COMPREPLY+=("$line")
|
||||||
|
done < <(NIX_GET_COMPLETIONS=$COMP_CWORD "${COMP_WORDS[@]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _complete_nix nix
|
1
misc/bash/local.mk
Normal file
1
misc/bash/local.mk
Normal file
|
@ -0,0 +1 @@
|
||||||
|
$(eval $(call install-file-as, $(d)/completion.sh, $(datarootdir)/bash-completion/completions/_nix3, 0644))
|
|
@ -31,9 +31,17 @@ MixCommonArgs::MixCommonArgs(const string & programName)
|
||||||
.labels = {"name", "value"},
|
.labels = {"name", "value"},
|
||||||
.handler = {[](std::string name, std::string value) {
|
.handler = {[](std::string name, std::string value) {
|
||||||
try {
|
try {
|
||||||
|
if (auto prefix = needsCompletion(name)) {
|
||||||
|
std::map<std::string, Config::SettingInfo> settings;
|
||||||
|
globalConfig.getSettings(settings);
|
||||||
|
for (auto & s : settings)
|
||||||
|
if (hasPrefix(s.first, *prefix))
|
||||||
|
completions->insert(s.first);
|
||||||
|
}
|
||||||
globalConfig.set(name, value);
|
globalConfig.set(name, value);
|
||||||
} catch (UsageError & e) {
|
} catch (UsageError & e) {
|
||||||
warn(e.what());
|
if (!completions)
|
||||||
|
warn(e.what());
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,19 @@ void Args::addFlag(Flag && flag_)
|
||||||
if (flag->shortName) shortFlags[flag->shortName] = flag;
|
if (flag->shortName) shortFlags[flag->shortName] = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<std::set<std::string>> completions;
|
||||||
|
|
||||||
|
std::string completionMarker = "___COMPLETE___";
|
||||||
|
|
||||||
|
std::optional<std::string> 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)
|
void Args::parseCmdline(const Strings & _cmdline)
|
||||||
{
|
{
|
||||||
Strings pendingArgs;
|
Strings pendingArgs;
|
||||||
|
@ -20,6 +33,13 @@ void Args::parseCmdline(const Strings & _cmdline)
|
||||||
|
|
||||||
Strings cmdline(_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<decltype(completions)::element_type>();
|
||||||
|
}
|
||||||
|
|
||||||
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
|
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
|
||||||
|
|
||||||
auto arg = *pos;
|
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 {
|
auto process = [&](const std::string & name, const Flag & flag) -> bool {
|
||||||
++pos;
|
++pos;
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
|
bool anyNeedsCompletion = false;
|
||||||
for (size_t n = 0 ; n < flag.handler.arity; ++n) {
|
for (size_t n = 0 ; n < flag.handler.arity; ++n) {
|
||||||
if (pos == end) {
|
if (pos == 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);
|
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));
|
flag.handler.fun(std::move(args));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (string(*pos, 0, 2) == "--") {
|
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));
|
auto i = longFlags.find(string(*pos, 2));
|
||||||
if (i == longFlags.end()) return false;
|
if (i == longFlags.end()) return false;
|
||||||
return process("--" + i->first, *i->second);
|
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);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +203,10 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
|
||||||
.description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')",
|
.description = "hash algorithm ('md5', 'sha1', 'sha256', or 'sha512')",
|
||||||
.labels = {"hash-algo"},
|
.labels = {"hash-algo"},
|
||||||
.handler = {[ht](std::string s) {
|
.handler = {[ht](std::string s) {
|
||||||
|
if (auto prefix = needsCompletion(s))
|
||||||
|
for (auto & type : hashTypes)
|
||||||
|
if (hasPrefix(type, *prefix))
|
||||||
|
completions->insert(type);
|
||||||
*ht = parseHashType(s);
|
*ht = parseHashType(s);
|
||||||
if (*ht == htUnknown)
|
if (*ht == htUnknown)
|
||||||
throw UsageError("unknown hash type '%1%'", s);
|
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<std::string> ss) {
|
expectedArgs.push_back(ExpectedArg{"command", 1, true, [=](std::vector<std::string> ss) {
|
||||||
assert(!command);
|
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]);
|
auto i = commands.find(ss[0]);
|
||||||
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", ss[0]);
|
||||||
|
|
|
@ -256,4 +256,8 @@ typedef std::vector<std::pair<std::string, std::string>> Table2;
|
||||||
|
|
||||||
void printTable(std::ostream & out, const Table2 & table);
|
void printTable(std::ostream & out, const Table2 & table);
|
||||||
|
|
||||||
|
extern std::shared_ptr<std::set<std::string>> completions;
|
||||||
|
|
||||||
|
std::optional<std::string> needsCompletion(std::string_view s);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
std::set<std::string> hashTypes = { "md5", "sha1", "sha256", "sha512" };
|
||||||
|
|
||||||
|
|
||||||
void Hash::init()
|
void Hash::init()
|
||||||
{
|
{
|
||||||
if (type == htMD5) hashSize = md5HashSize;
|
if (type == htMD5) hashSize = md5HashSize;
|
||||||
|
|
|
@ -18,6 +18,8 @@ const int sha1HashSize = 20;
|
||||||
const int sha256HashSize = 32;
|
const int sha256HashSize = 32;
|
||||||
const int sha512HashSize = 64;
|
const int sha512HashSize = 64;
|
||||||
|
|
||||||
|
extern std::set<std::string> hashTypes;
|
||||||
|
|
||||||
extern const string base32Chars;
|
extern const string base32Chars;
|
||||||
|
|
||||||
enum Base : int { Base64, Base32, Base16, SRI };
|
enum Base : int { Base64, Base32, Base16, SRI };
|
||||||
|
|
|
@ -166,7 +166,21 @@ void mainWrapped(int argc, char * * argv)
|
||||||
|
|
||||||
NixArgs args;
|
NixArgs args;
|
||||||
|
|
||||||
args.parseCmdline(argvToStrings(argc, argv));
|
Finally printCompletions([&]()
|
||||||
|
{
|
||||||
|
if (completions) {
|
||||||
|
for (auto & s : *completions)
|
||||||
|
std::cout << s << "\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
args.parseCmdline(argvToStrings(argc, argv));
|
||||||
|
} catch (UsageError &) {
|
||||||
|
if (!completions) throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completions) return;
|
||||||
|
|
||||||
settings.requireExperimentalFeature("nix-command");
|
settings.requireExperimentalFeature("nix-command");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue