Improve auto-completion for arguments

fixes #7375
This commit is contained in:
Gunnar Beutner 2014-10-14 16:45:00 +02:00
parent d9f5409e8b
commit 2a60ce8625
17 changed files with 164 additions and 25 deletions

View File

@ -4,6 +4,7 @@ _icinga2()
opts="${COMP_WORDS[*]}"
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=($(icinga2 --autocomplete $COMP_CWORD ${COMP_WORDS[*]} < /dev/null))
[[ $COMPREPLY = */ ]] && compopt -o nospace
return 0
}

View File

@ -5,3 +5,5 @@
const RunAsUser = "@ICINGA2_USER@"
const RunAsGroup = "@ICINGA2_GROUP@"
library "cli"

View File

@ -48,6 +48,33 @@ SERVICE_STATUS l_SvcStatus;
SERVICE_STATUS_HANDLE l_SvcStatusHandle;
#endif /* _WIN32 */
static std::vector<String> LogLevelCompletion(const String& arg)
{
std::vector<String> result;
String debugLevel = "debug";
if (debugLevel.Find(arg) == 0)
result.push_back(debugLevel);
String noticeLevel = "notice";
if (noticeLevel.Find(arg) == 0)
result.push_back(noticeLevel);
String informationLevel = "information";
if (informationLevel.Find(arg) == 0)
result.push_back(informationLevel);
String warningLevel = "warning";
if (warningLevel.Find(arg) == 0)
result.push_back(warningLevel);
String criticalLevel = "critical";
if (criticalLevel.Find(arg) == 0)
result.push_back(criticalLevel);
return result;
}
int Main(void)
{
int argc = Application::GetArgC();
@ -143,12 +170,16 @@ int Main(void)
po::positional_options_description positionalDesc;
positionalDesc.add("arg", -1);
ArgumentCompletionDescription argDesc;
argDesc["include"] = BashArgumentCompletion("directory");
argDesc["log-level"] = LogLevelCompletion;
String cmdname;
CLICommand::Ptr command;
po::variables_map vm;
try {
CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc, vm, cmdname, command, autocomplete);
CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc, argDesc, vm, cmdname, command, autocomplete);
} catch (const std::exception& ex) {
std::ostringstream msgbuf;
msgbuf << "Error while parsing command-line options: " << ex.what();
@ -349,7 +380,7 @@ int Main(void)
int rc = 1;
if (autocomplete) {
CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc, true, autoindex);
CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc, &argDesc, true, autoindex);
rc = 0;
} else if (command) {
std::vector<std::string> args;

View File

@ -21,6 +21,7 @@
#include "base/logger_fwd.hpp"
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/foreach.hpp>
#include <boost/program_options.hpp>
@ -32,6 +33,46 @@ namespace po = boost::program_options;
boost::mutex l_RegistryMutex;
std::map<std::vector<String>, CLICommand::Ptr> l_Registry;
static std::vector<String> BashArgumentCompletionHelper(const String& type, const String& arg)
{
std::vector<String> result;
#ifndef _WIN32
String bashArg = "compgen -A " + Utility::EscapeShellArg(type) + " " + Utility::EscapeShellArg(arg);
String cmd = "bash -c " + Utility::EscapeShellArg(bashArg);
FILE *fp = popen(cmd.CStr(), "r");
char line[4096];
while (fgets(line, sizeof(line), fp)) {
String wline = line;
boost::algorithm::trim_right_if(wline, boost::is_any_of("\r\n"));
result.push_back(wline);
}
fclose(fp);
/* Append a slash if there's only one suggestion and it's a directory */
if ((type == "file" || type == "directory") && result.size() == 1) {
String path = result[0];
struct stat statbuf;
if (lstat(path.CStr(), &statbuf) >= 0) {
if (S_ISDIR(statbuf.st_mode)) {
result.clear(),
result.push_back(path + "/");
}
}
}
#endif /* _WIN32 */
return result;
}
ArgumentCompletionCallback icinga::BashArgumentCompletion(const String& type)
{
return boost::bind(BashArgumentCompletionHelper, type, _1);
}
CLICommand::Ptr CLICommand::GetByName(const std::vector<String>& name)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
@ -64,8 +105,10 @@ RegisterCLICommandHelper::RegisterCLICommandHelper(const String& name, const CLI
}
bool CLICommand::ParseCommand(int argc, char **argv, po::options_description& visibleDesc,
po::options_description& hiddenDesc, po::positional_options_description& positionalDesc, po::variables_map& vm,
String& cmdname, CLICommand::Ptr& command, bool autocomplete)
po::options_description& hiddenDesc,
po::positional_options_description& positionalDesc,
ArgumentCompletionDescription& argCompletionDesc,
po::variables_map& vm, String& cmdname, CLICommand::Ptr& command, bool autocomplete)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
@ -104,7 +147,7 @@ found_command:
po::options_description vdesc("Command options");
if (command)
command->InitParameters(vdesc, hiddenDesc);
command->InitParameters(vdesc, hiddenDesc, argCompletionDesc);
visibleDesc.add(vdesc);
@ -122,7 +165,8 @@ found_command:
}
void CLICommand::ShowCommands(int argc, char **argv, po::options_description *visibleDesc,
po::options_description *hiddenDesc, bool autocomplete, int autoindex)
po::options_description *hiddenDesc, ArgumentCompletionDescription *argCompletionDesc,
bool autocomplete, int autoindex)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
@ -198,13 +242,47 @@ void CLICommand::ShowCommands(int argc, char **argv, po::options_description *vi
}
if (command && autocomplete) {
po::options_description vdesc("Command options");
String aname, prefix, pword;
ArgumentCompletionDescription::const_iterator it;
const po::option_description *odesc;
if (autoindex - 2 >= 0 && strcmp(argv[autoindex - 1], "=") == 0 && strstr(argv[autoindex - 2], "--") == argv[autoindex - 2]) {
aname = argv[autoindex - 2] + 2;
pword = aword;
} else if (autoindex - 1 >= 0 && argv[autoindex - 1][0] == '-' && argv[autoindex - 1][1] == '-') {
aname = argv[autoindex - 1] + 2;
pword = aword;
if (pword == "=")
pword = "";
} else if (aword.GetLength() > 1 && aword[0] == '-' && aword[1] != '-') {
aname = aword.SubStr(1, 1);
prefix = aword.SubStr(0, 2);
pword = aword.SubStr(2);
} else {
goto complete_option;
}
if (command)
command->InitParameters(vdesc, *hiddenDesc);
odesc = visibleDesc->find_nothrow(aname, false);
visibleDesc->add(vdesc);
if (!odesc)
return;
if (odesc->semantic()->min_tokens() == 0)
goto complete_option;
it = argCompletionDesc->find(odesc->long_name());
if (it == argCompletionDesc->end())
return;
BOOST_FOREACH(const String& suggestion, it->second(pword)) {
std::cout << prefix << suggestion << "\n";
}
return;
complete_option:
BOOST_FOREACH(const shared_ptr<po::option_description>& odesc, visibleDesc->options()) {
String cname = "--" + odesc->long_name();

View File

@ -29,6 +29,11 @@
namespace icinga
{
typedef boost::function<std::vector<String> (const String&)> ArgumentCompletionCallback;
typedef std::map<String, ArgumentCompletionCallback> ArgumentCompletionDescription;
I2_BASE_API ArgumentCompletionCallback BashArgumentCompletion(const String& type);
/**
* A CLI command.
*
@ -41,7 +46,9 @@ public:
virtual String GetDescription(void) const = 0;
virtual String GetShortDescription(void) const = 0;
virtual void InitParameters(boost::program_options::options_description& visibleDesc, boost::program_options::options_description& hiddenDesc) const = 0;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const = 0;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const = 0;
static CLICommand::Ptr GetByName(const std::vector<String>& name);
@ -51,10 +58,15 @@ public:
static bool ParseCommand(int argc, char **argv, boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
boost::program_options::positional_options_description& positionalDesc,
ArgumentCompletionDescription& argCompletionDesc,
boost::program_options::variables_map& vm, String& cmdname,
CLICommand::Ptr& command, bool autocomplete);
static void ShowCommands(int argc, char **argv, boost::program_options::options_description *visibleDesc = NULL,
boost::program_options::options_description *hiddenDesc = NULL, bool autocomplete = false, int autoindex = -1);
static void ShowCommands(int argc, char **argv,
boost::program_options::options_description *visibleDesc = NULL,
boost::program_options::options_description *hiddenDesc = NULL,
ArgumentCompletionDescription *argCompletionDesc = NULL,
bool autocomplete = false, int autoindex = -1);
};
/**

View File

@ -276,7 +276,8 @@ String DaemonCommand::GetShortDescription(void) const
}
void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
visibleDesc.add_options()
("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
@ -292,6 +293,9 @@ void DaemonCommand::InitParameters(boost::program_options::options_description&
hiddenDesc.add_options()
("reload-internal", po::value<int>(), "used internally to implement config reload: do not call manually, send SIGHUP instead");
#endif /* _WIN32 */
argCompletionDesc["config"] = BashArgumentCompletion("file");
argCompletionDesc["errorlog"] = BashArgumentCompletion("file");
}
/**

View File

@ -40,7 +40,8 @@ public:
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const;
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -45,7 +45,8 @@ String FeatureDisableCommand::GetShortDescription(void) const
}
void FeatureDisableCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
/* Command doesn't support any parameters. */
}

View File

@ -39,7 +39,8 @@ public:
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const;
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -45,7 +45,8 @@ String FeatureEnableCommand::GetShortDescription(void) const
}
void FeatureEnableCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
/* Command doesn't support any parameters. */
}

View File

@ -39,7 +39,8 @@ public:
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const;
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -45,7 +45,8 @@ String FeatureListCommand::GetShortDescription(void) const
}
void FeatureListCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
/* Command doesn't support any parameters. */
}

View File

@ -39,7 +39,8 @@ public:
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const;
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
private:

View File

@ -39,7 +39,8 @@ String PKINewCACommand::GetShortDescription(void) const
}
void PKINewCACommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
/* Command doesn't support any parameters. */
}

View File

@ -39,7 +39,8 @@ public:
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const;
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -38,7 +38,8 @@ String PKINewCertCommand::GetShortDescription(void) const
}
void PKINewCertCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
visibleDesc.add_options()
("cn", po::value<std::string>(), "Common Name")

View File

@ -39,7 +39,8 @@ public:
virtual String GetDescription(void) const;
virtual String GetShortDescription(void) const;
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const;
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};