forked from lix-project/lix
Merge pull request #3587 from NixOS/bash-completion
Generic shell completion support for the 'nix' command
This commit is contained in:
commit
215f09d765
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 \
|
||||||
|
|
19
misc/bash/completion.sh
Normal file
19
misc/bash/completion.sh
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
function _complete_nix {
|
||||||
|
local -a words
|
||||||
|
local cword cur
|
||||||
|
_get_comp_words_by_ref -n ':=&' words cword cur
|
||||||
|
local have_type
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ -z $have_type ]]; then
|
||||||
|
have_type=1
|
||||||
|
if [[ $line = filenames ]]; then
|
||||||
|
compopt -o filenames
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
COMPREPLY+=("$line")
|
||||||
|
fi
|
||||||
|
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}")
|
||||||
|
__ltrim_colon_completions "$cur"
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
|
@ -155,7 +155,7 @@ void overrideRegistry(
|
||||||
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
||||||
{
|
{
|
||||||
static auto reg = [&]() {
|
static auto reg = [&]() {
|
||||||
auto path = settings.flakeRegistry;
|
auto path = settings.flakeRegistry.get();
|
||||||
|
|
||||||
if (!hasPrefix(path, "/")) {
|
if (!hasPrefix(path, "/")) {
|
||||||
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;
|
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;
|
||||||
|
|
|
@ -33,9 +33,19 @@ MixCommonArgs::MixCommonArgs(const string & programName)
|
||||||
try {
|
try {
|
||||||
globalConfig.set(name, value);
|
globalConfig.set(name, value);
|
||||||
} catch (UsageError & e) {
|
} catch (UsageError & e) {
|
||||||
warn(e.what());
|
if (!completions)
|
||||||
|
warn(e.what());
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
|
.completer = [](size_t index, std::string_view prefix) {
|
||||||
|
if (index == 0) {
|
||||||
|
std::map<std::string, Config::SettingInfo> settings;
|
||||||
|
globalConfig.getSettings(settings);
|
||||||
|
for (auto & s : settings)
|
||||||
|
if (hasPrefix(s.first, prefix))
|
||||||
|
completions->insert(s.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
|
|
||||||
|
#include <glob.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void Args::addFlag(Flag && flag_)
|
void Args::addFlag(Flag && flag_)
|
||||||
|
@ -13,6 +15,20 @@ void Args::addFlag(Flag && flag_)
|
||||||
if (flag->shortName) shortFlags[flag->shortName] = flag;
|
if (flag->shortName) shortFlags[flag->shortName] = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool pathCompletions = false;
|
||||||
|
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 +36,14 @@ 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>();
|
||||||
|
verbosity = lvlError;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
|
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {
|
||||||
|
|
||||||
auto arg = *pos;
|
auto arg = *pos;
|
||||||
|
@ -63,7 +87,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";
|
||||||
|
@ -104,6 +128,9 @@ 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 (flag.completer)
|
||||||
|
if (auto prefix = needsCompletion(*pos))
|
||||||
|
flag.completer(n, *prefix);
|
||||||
args.push_back(*pos++);
|
args.push_back(*pos++);
|
||||||
}
|
}
|
||||||
flag.handler.fun(std::move(args));
|
flag.handler.fun(std::move(args));
|
||||||
|
@ -111,6 +138,13 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,12 +180,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;
|
||||||
}
|
}
|
||||||
|
@ -164,10 +211,46 @@ Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
|
||||||
*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);
|
||||||
}}
|
}},
|
||||||
|
.completer = [](size_t index, std::string_view prefix) {
|
||||||
|
for (auto & type : hashTypes)
|
||||||
|
if (hasPrefix(type, prefix))
|
||||||
|
completions->insert(type);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void completePath(std::string_view prefix, bool onlyDirs)
|
||||||
|
{
|
||||||
|
pathCompletions = true;
|
||||||
|
glob_t globbuf;
|
||||||
|
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, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void completeDir(size_t, std::string_view prefix)
|
||||||
|
{
|
||||||
|
completePath(prefix, true);
|
||||||
|
}
|
||||||
|
|
||||||
Strings argvToStrings(int argc, char * * argv)
|
Strings argvToStrings(int argc, char * * argv)
|
||||||
{
|
{
|
||||||
Strings args;
|
Strings args;
|
||||||
|
@ -215,13 +298,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({
|
||||||
assert(!command);
|
.label = "command",
|
||||||
auto i = commands.find(ss[0]);
|
.optional = true,
|
||||||
if (i == commands.end())
|
.handler = {[=](std::string s) {
|
||||||
throw UsageError("'%s' is not a recognised command", ss[0]);
|
assert(!command);
|
||||||
command = {ss[0], i->second()};
|
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";
|
categories[Command::catDefault] = "Available commands";
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,61 +28,67 @@ protected:
|
||||||
|
|
||||||
static const size_t ArityAny = std::numeric_limits<size_t>::max();
|
static const size_t ArityAny = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
struct Handler
|
||||||
|
{
|
||||||
|
std::function<void(std::vector<std::string>)> fun;
|
||||||
|
size_t arity;
|
||||||
|
|
||||||
|
Handler() {}
|
||||||
|
|
||||||
|
Handler(std::function<void(std::vector<std::string>)> && fun)
|
||||||
|
: fun(std::move(fun))
|
||||||
|
, arity(ArityAny)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Handler(std::function<void()> && handler)
|
||||||
|
: fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); })
|
||||||
|
, arity(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Handler(std::function<void(std::string)> && handler)
|
||||||
|
: fun([handler{std::move(handler)}](std::vector<std::string> ss) {
|
||||||
|
handler(std::move(ss[0]));
|
||||||
|
})
|
||||||
|
, arity(1)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Handler(std::function<void(std::string, std::string)> && handler)
|
||||||
|
: fun([handler{std::move(handler)}](std::vector<std::string> ss) {
|
||||||
|
handler(std::move(ss[0]), std::move(ss[1]));
|
||||||
|
})
|
||||||
|
, arity(2)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Handler(std::vector<std::string> * dest)
|
||||||
|
: fun([=](std::vector<std::string> ss) { *dest = ss; })
|
||||||
|
, arity(ArityAny)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
Handler(T * dest)
|
||||||
|
: fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
|
||||||
|
, arity(1)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
Handler(T * dest, const T & val)
|
||||||
|
: fun([=](std::vector<std::string> ss) { *dest = val; })
|
||||||
|
, arity(0)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
/* Flags. */
|
/* Flags. */
|
||||||
struct Flag
|
struct Flag
|
||||||
{
|
{
|
||||||
typedef std::shared_ptr<Flag> ptr;
|
typedef std::shared_ptr<Flag> ptr;
|
||||||
|
|
||||||
struct Handler
|
|
||||||
{
|
|
||||||
std::function<void(std::vector<std::string>)> fun;
|
|
||||||
size_t arity;
|
|
||||||
|
|
||||||
Handler() {}
|
|
||||||
|
|
||||||
Handler(std::function<void(std::vector<std::string>)> && fun)
|
|
||||||
: fun(std::move(fun))
|
|
||||||
, arity(ArityAny)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
Handler(std::function<void()> && handler)
|
|
||||||
: fun([handler{std::move(handler)}](std::vector<std::string>) { handler(); })
|
|
||||||
, arity(0)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
Handler(std::function<void(std::string)> && handler)
|
|
||||||
: fun([handler{std::move(handler)}](std::vector<std::string> ss) {
|
|
||||||
handler(std::move(ss[0]));
|
|
||||||
})
|
|
||||||
, arity(1)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
Handler(std::function<void(std::string, std::string)> && handler)
|
|
||||||
: fun([handler{std::move(handler)}](std::vector<std::string> ss) {
|
|
||||||
handler(std::move(ss[0]), std::move(ss[1]));
|
|
||||||
})
|
|
||||||
, arity(2)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
Handler(T * dest)
|
|
||||||
: fun([=](std::vector<std::string> ss) { *dest = ss[0]; })
|
|
||||||
, arity(1)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
Handler(T * dest, const T & val)
|
|
||||||
: fun([=](std::vector<std::string> ss) { *dest = val; })
|
|
||||||
, arity(0)
|
|
||||||
{ }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string longName;
|
std::string longName;
|
||||||
char shortName = 0;
|
char shortName = 0;
|
||||||
std::string description;
|
std::string description;
|
||||||
std::string category;
|
std::string category;
|
||||||
Strings labels;
|
Strings labels;
|
||||||
Handler handler;
|
Handler handler;
|
||||||
|
std::function<void(size_t, std::string_view)> completer;
|
||||||
|
|
||||||
static Flag mkHashTypeFlag(std::string && longName, HashType * ht);
|
static Flag mkHashTypeFlag(std::string && longName, HashType * ht);
|
||||||
};
|
};
|
||||||
|
@ -98,9 +104,9 @@ protected:
|
||||||
struct ExpectedArg
|
struct ExpectedArg
|
||||||
{
|
{
|
||||||
std::string label;
|
std::string label;
|
||||||
size_t arity; // 0 = any
|
bool optional = false;
|
||||||
bool optional;
|
Handler handler;
|
||||||
std::function<void(std::vector<std::string>)> handler;
|
std::function<void(size_t, std::string_view)> completer;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::list<ExpectedArg> expectedArgs;
|
std::list<ExpectedArg> expectedArgs;
|
||||||
|
@ -174,20 +180,28 @@ 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}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
friend class MultiCommand;
|
friend class MultiCommand;
|
||||||
|
@ -256,4 +270,13 @@ 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;
|
||||||
|
extern bool pathCompletions;
|
||||||
|
|
||||||
|
std::optional<std::string> needsCompletion(std::string_view s);
|
||||||
|
|
||||||
|
void completePath(size_t, std::string_view prefix);
|
||||||
|
|
||||||
|
void completeDir(size_t, std::string_view prefix);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -1330,7 +1330,7 @@ bool statusOk(int status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool hasPrefix(const string & s, const string & prefix)
|
bool hasPrefix(std::string_view s, std::string_view prefix)
|
||||||
{
|
{
|
||||||
return s.compare(0, prefix.size(), prefix) == 0;
|
return s.compare(0, prefix.size(), prefix) == 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -431,7 +431,7 @@ template<class N> bool string2Float(const string & s, N & n)
|
||||||
|
|
||||||
|
|
||||||
/* Return true iff `s' starts with `prefix'. */
|
/* Return true iff `s' starts with `prefix'. */
|
||||||
bool hasPrefix(const string & s, const string & prefix);
|
bool hasPrefix(std::string_view s, std::string_view prefix);
|
||||||
|
|
||||||
|
|
||||||
/* Return true iff `s' ends in `suffix'. */
|
/* Return true iff `s' ends in `suffix'. */
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
|
||||||
.description = "path of the symlink to the build result",
|
.description = "path of the symlink to the build result",
|
||||||
.labels = {"path"},
|
.labels = {"path"},
|
||||||
.handler = {&outLink},
|
.handler = {&outLink},
|
||||||
|
.completer = completePath
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
|
|
@ -25,7 +25,11 @@ struct CmdCatStore : StoreCommand, MixCat
|
||||||
{
|
{
|
||||||
CmdCatStore()
|
CmdCatStore()
|
||||||
{
|
{
|
||||||
expectArg("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()
|
||||||
{
|
{
|
||||||
expectArg("nar", &narPath);
|
expectArgs({
|
||||||
|
.label = "nar",
|
||||||
|
.handler = {&narPath},
|
||||||
|
.completer = completePath
|
||||||
|
});
|
||||||
expectArg("path", &path);
|
expectArg("path", &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,7 @@ MixProfile::MixProfile()
|
||||||
.description = "profile to update",
|
.description = "profile to update",
|
||||||
.labels = {"path"},
|
.labels = {"path"},
|
||||||
.handler = {&profile},
|
.handler = {&profile},
|
||||||
|
.completer = completePath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs
|
||||||
ref<EvalState> getEvalState();
|
ref<EvalState> getEvalState();
|
||||||
|
|
||||||
std::shared_ptr<EvalState> evalState;
|
std::shared_ptr<EvalState> evalState;
|
||||||
|
|
||||||
|
void completeFlakeRef(std::string_view prefix);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MixFlakeOptions : virtual Args
|
struct MixFlakeOptions : virtual Args
|
||||||
|
@ -63,6 +65,8 @@ struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
|
||||||
virtual Strings getDefaultFlakeAttrPaths();
|
virtual Strings getDefaultFlakeAttrPaths();
|
||||||
|
|
||||||
virtual Strings getDefaultFlakeAttrPathPrefixes();
|
virtual Strings getDefaultFlakeAttrPathPrefixes();
|
||||||
|
|
||||||
|
void completeInstallable(std::string_view prefix);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum RealiseMode { Build, NoBuild, DryRun };
|
enum RealiseMode { Build, NoBuild, DryRun };
|
||||||
|
@ -73,10 +77,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;
|
||||||
|
|
||||||
|
@ -92,10 +93,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
|
||||||
{
|
{
|
||||||
std::shared_ptr<Installable> installable;
|
std::shared_ptr<Installable> installable;
|
||||||
|
|
||||||
InstallableCommand()
|
InstallableCommand();
|
||||||
{
|
|
||||||
expectArg("installable", &_installable, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void prepare() override;
|
void prepare() override;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,14 @@ public:
|
||||||
|
|
||||||
FlakeCommand()
|
FlakeCommand()
|
||||||
{
|
{
|
||||||
expectArg("flake-url", &flakeUrl, true);
|
expectArgs({
|
||||||
|
.label = "flake-url",
|
||||||
|
.optional = true,
|
||||||
|
.handler = {&flakeUrl},
|
||||||
|
.completer = {[&](size_t, std::string_view prefix) {
|
||||||
|
completeFlakeRef(prefix);
|
||||||
|
}}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
FlakeRef getFlakeRef()
|
FlakeRef getFlakeRef()
|
||||||
|
|
|
@ -31,7 +31,11 @@ struct CmdHash : Command
|
||||||
.labels({"modulus"})
|
.labels({"modulus"})
|
||||||
.dest(&modulus);
|
.dest(&modulus);
|
||||||
#endif
|
#endif
|
||||||
expectArgs("paths", &paths);
|
expectArgs({
|
||||||
|
.label = "paths",
|
||||||
|
.handler = {&paths},
|
||||||
|
.completer = completePath
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "flake/flake.hh"
|
#include "flake/flake.hh"
|
||||||
#include "eval-cache.hh"
|
#include "eval-cache.hh"
|
||||||
#include "url.hh"
|
#include "url.hh"
|
||||||
|
#include "registry.hh"
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
@ -77,7 +78,8 @@ SourceExprCommand::SourceExprCommand()
|
||||||
.shortName = 'f',
|
.shortName = 'f',
|
||||||
.description = "evaluate FILE rather than the default",
|
.description = "evaluate FILE rather than the default",
|
||||||
.labels = {"file"},
|
.labels = {"file"},
|
||||||
.handler = {&file}
|
.handler = {&file},
|
||||||
|
.completer = completePath
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
@ -105,6 +107,76 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SourceExprCommand::completeInstallable(std::string_view prefix)
|
||||||
|
{
|
||||||
|
if (file) return; // FIXME
|
||||||
|
|
||||||
|
/* Look for flake output attributes that match the
|
||||||
|
prefix. */
|
||||||
|
try {
|
||||||
|
auto hash = prefix.find('#');
|
||||||
|
if (hash != std::string::npos) {
|
||||||
|
auto fragment = prefix.substr(hash + 1);
|
||||||
|
auto flakeRefS = std::string(prefix.substr(0, hash));
|
||||||
|
// FIXME: do tilde expansion.
|
||||||
|
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
|
||||||
|
|
||||||
|
auto state = getEvalState();
|
||||||
|
|
||||||
|
auto evalCache = openEvalCache(*state,
|
||||||
|
std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags)),
|
||||||
|
true);
|
||||||
|
|
||||||
|
auto root = evalCache->getRoot();
|
||||||
|
|
||||||
|
/* Complete 'fragment' relative to all the
|
||||||
|
attrpath prefixes as well as the root of the
|
||||||
|
flake. */
|
||||||
|
auto attrPathPrefixes = getDefaultFlakeAttrPathPrefixes();
|
||||||
|
attrPathPrefixes.push_back("");
|
||||||
|
|
||||||
|
for (auto & attrPathPrefixS : attrPathPrefixes) {
|
||||||
|
auto attrPathPrefix = parseAttrPath(*state, attrPathPrefixS);
|
||||||
|
auto attrPathS = attrPathPrefixS + std::string(fragment);
|
||||||
|
auto attrPath = parseAttrPath(*state, attrPathS);
|
||||||
|
|
||||||
|
std::string lastAttr;
|
||||||
|
if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
|
||||||
|
lastAttr = attrPath.back();
|
||||||
|
attrPath.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attr = root->findAlongAttrPath(attrPath);
|
||||||
|
if (!attr) continue;
|
||||||
|
|
||||||
|
auto attrs = attr->getAttrs();
|
||||||
|
for (auto & attr2 : attrs) {
|
||||||
|
if (hasPrefix(attr2, lastAttr)) {
|
||||||
|
auto attrPath2 = attr->getAttrPath(attr2);
|
||||||
|
/* Strip the attrpath prefix. */
|
||||||
|
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
||||||
|
completions->insert(flakeRefS + "#" + concatStringsSep(".", attrPath2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* And add an empty completion for the default
|
||||||
|
attrpaths. */
|
||||||
|
if (fragment.empty()) {
|
||||||
|
for (auto & attrPath : getDefaultFlakeAttrPaths()) {
|
||||||
|
auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
|
||||||
|
if (!attr) continue;
|
||||||
|
completions->insert(flakeRefS + "#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Error & e) {
|
||||||
|
warn(e.msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
completeFlakeRef(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
ref<EvalState> EvalCommand::getEvalState()
|
ref<EvalState> EvalCommand::getEvalState()
|
||||||
{
|
{
|
||||||
if (!evalState)
|
if (!evalState)
|
||||||
|
@ -112,6 +184,29 @@ ref<EvalState> EvalCommand::getEvalState()
|
||||||
return ref<EvalState>(evalState);
|
return ref<EvalState>(evalState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EvalCommand::completeFlakeRef(std::string_view prefix)
|
||||||
|
{
|
||||||
|
if (prefix == "")
|
||||||
|
completions->insert(".");
|
||||||
|
|
||||||
|
completeDir(0, prefix);
|
||||||
|
|
||||||
|
/* Look for registry entries that match the prefix. */
|
||||||
|
for (auto & registry : fetchers::getRegistries(getStore())) {
|
||||||
|
for (auto & entry : registry->entries) {
|
||||||
|
auto from = entry.from->to_string();
|
||||||
|
if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
|
||||||
|
std::string from2(from, 6);
|
||||||
|
if (hasPrefix(from2, prefix))
|
||||||
|
completions->insert(from2);
|
||||||
|
} else {
|
||||||
|
if (hasPrefix(from, prefix))
|
||||||
|
completions->insert(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Buildable Installable::toBuildable()
|
Buildable Installable::toBuildable()
|
||||||
{
|
{
|
||||||
auto buildables = toBuildables();
|
auto buildables = toBuildables();
|
||||||
|
@ -551,6 +646,17 @@ StorePathSet toDerivations(ref<Store> store,
|
||||||
return drvPaths;
|
return drvPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstallablesCommand::InstallablesCommand()
|
||||||
|
{
|
||||||
|
expectArgs({
|
||||||
|
.label = "installables",
|
||||||
|
.handler = {&_installables},
|
||||||
|
.completer = {[&](size_t, std::string_view prefix) {
|
||||||
|
completeInstallable(prefix);
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void InstallablesCommand::prepare()
|
void InstallablesCommand::prepare()
|
||||||
{
|
{
|
||||||
if (_installables.empty() && useDefaultInstallables())
|
if (_installables.empty() && useDefaultInstallables())
|
||||||
|
@ -560,6 +666,18 @@ void InstallablesCommand::prepare()
|
||||||
installables = parseInstallables(getStore(), _installables);
|
installables = parseInstallables(getStore(), _installables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstallableCommand::InstallableCommand()
|
||||||
|
{
|
||||||
|
expectArgs({
|
||||||
|
.label = "installable",
|
||||||
|
.optional = true,
|
||||||
|
.handler = {&_installable},
|
||||||
|
.completer = {[&](size_t, std::string_view prefix) {
|
||||||
|
completeInstallable(prefix);
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void InstallableCommand::prepare()
|
void InstallableCommand::prepare()
|
||||||
{
|
{
|
||||||
installable = parseInstallable(getStore(), _installable);
|
installable = parseInstallable(getStore(), _installable);
|
||||||
|
|
|
@ -85,7 +85,11 @@ struct CmdLsStore : StoreCommand, MixLs
|
||||||
{
|
{
|
||||||
CmdLsStore()
|
CmdLsStore()
|
||||||
{
|
{
|
||||||
expectArg("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()
|
||||||
{
|
{
|
||||||
expectArg("nar", &narPath);
|
expectArgs({
|
||||||
|
.label = "nar",
|
||||||
|
.handler = {&narPath},
|
||||||
|
.completer = completePath
|
||||||
|
});
|
||||||
expectArg("path", &path);
|
expectArg("path", &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
|
||||||
addFlag({
|
addFlag({
|
||||||
.longName = "help",
|
.longName = "help",
|
||||||
.description = "show usage information",
|
.description = "show usage information",
|
||||||
.handler = {[&]() { showHelpAndExit(); }},
|
.handler = {[&]() { if (!completions) showHelpAndExit(); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
@ -96,7 +96,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
|
||||||
addFlag({
|
addFlag({
|
||||||
.longName = "version",
|
.longName = "version",
|
||||||
.description = "show version information",
|
.description = "show version information",
|
||||||
.handler = {[&]() { printVersion(programName); }},
|
.handler = {[&]() { if (!completions) printVersion(programName); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
@ -166,7 +166,22 @@ void mainWrapped(int argc, char * * argv)
|
||||||
|
|
||||||
NixArgs args;
|
NixArgs args;
|
||||||
|
|
||||||
args.parseCmdline(argvToStrings(argc, argv));
|
Finally printCompletions([&]()
|
||||||
|
{
|
||||||
|
if (completions) {
|
||||||
|
std::cout << (pathCompletions ? "filenames\n" : "no-filenames\n");
|
||||||
|
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");
|
||||||
|
|
||||||
|
|
|
@ -767,7 +767,11 @@ struct CmdRepl : StoreCommand, MixEvalArgs
|
||||||
|
|
||||||
CmdRepl()
|
CmdRepl()
|
||||||
{
|
{
|
||||||
expectArgs("files", &files);
|
expectArgs({
|
||||||
|
.label = "files",
|
||||||
|
.handler = {&files},
|
||||||
|
.completer = completePath
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
|
|
@ -149,7 +149,11 @@ struct CmdRun : InstallableCommand, RunCommon
|
||||||
|
|
||||||
CmdRun()
|
CmdRun()
|
||||||
{
|
{
|
||||||
expectArgs("args", &args);
|
expectArgs({
|
||||||
|
.label = "args",
|
||||||
|
.handler = {&args},
|
||||||
|
.completer = completePath
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
|
|
@ -105,7 +105,8 @@ struct CmdSignPaths : StorePathsCommand
|
||||||
.shortName = 'k',
|
.shortName = 'k',
|
||||||
.description = "file containing the secret signing key",
|
.description = "file containing the secret signing key",
|
||||||
.labels = {"file"},
|
.labels = {"file"},
|
||||||
.handler = {&secretKeyFile}
|
.handler = {&secretKeyFile},
|
||||||
|
.completer = completePath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue