Implement auto-completion for positional arguments

refs #7255
This commit is contained in:
Gunnar Beutner 2014-10-17 15:54:46 +02:00
parent ac47649722
commit 7db8588980
26 changed files with 133 additions and 114 deletions

View File

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

View File

@ -48,7 +48,7 @@ SERVICE_STATUS l_SvcStatus;
SERVICE_STATUS_HANDLE l_SvcStatusHandle;
#endif /* _WIN32 */
static std::vector<String> LogLevelCompletion(const String& arg)
static std::vector<String> GetLogLevelCompletionSuggestions(const String& arg)
{
std::vector<String> result;
@ -75,6 +75,16 @@ static std::vector<String> LogLevelCompletion(const String& arg)
return result;
}
static std::vector<String> GlobalArgumentCompletion(const String& argument, const String& word)
{
if (argument == "include")
return GetBashCompletionSuggestions("directory", word);
else if (argument == "log-level")
return GetLogLevelCompletionSuggestions(word);
else
return std::vector<String>();
}
int Main(void)
{
int argc = Application::GetArgC();
@ -170,16 +180,13 @@ 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, argDesc, vm, cmdname, command, autocomplete);
CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc,
vm, cmdname, command, autocomplete);
} catch (const std::exception& ex) {
std::ostringstream msgbuf;
msgbuf << "Error while parsing command-line options: " << ex.what();
@ -307,7 +314,8 @@ int Main(void)
int rc = 1;
if (autocomplete) {
CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc, &argDesc, true, autoindex);
CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc,
&GlobalArgumentCompletion, true, autoindex);
rc = 0;
} else if (command) {
#ifndef _WIN32

View File

@ -34,12 +34,12 @@ 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> icinga::GetBashCompletionSuggestions(const String& type, const String& word)
{
std::vector<String> result;
#ifndef _WIN32
String bashArg = "compgen -A " + Utility::EscapeShellArg(type) + " " + Utility::EscapeShellArg(arg);
String bashArg = "compgen -A " + Utility::EscapeShellArg(type) + " " + Utility::EscapeShellArg(word);
String cmd = "bash -c " + Utility::EscapeShellArg(bashArg);
FILE *fp = popen(cmd.CStr(), "r");
@ -69,25 +69,28 @@ static std::vector<String> BashArgumentCompletionHelper(const String& type, cons
return result;
}
void icinga::AddTypeFields(const Type *type, boost::program_options::options_description& desc)
std::vector<String> icinga::GetFieldCompletionSuggestions(const Type *type, const String& word)
{
std::vector<String> result;
for (int i = 0; i < type->GetFieldCount(); i++) {
Field field = type->GetFieldInfo(i);
if (strcmp(field.Name, "__name") == 0)
continue;
if (!(field.Attributes & FAConfig))
continue;
desc.add_options()
(field.Name, po::value<std::string>(), field.Name);
}
}
String fname = field.Name;
ArgumentCompletionCallback icinga::BashArgumentCompletion(const String& type)
{
return boost::bind(BashArgumentCompletionHelper, type, _1);
if (fname == "__name" || fname == "templates")
continue;
String suggestion = fname + "=";
if (suggestion.Find(word) == 0)
result.push_back(suggestion);
}
return result;
}
CLICommand::Ptr CLICommand::GetByName(const std::vector<String>& name)
@ -121,10 +124,23 @@ RegisterCLICommandHelper::RegisterCLICommandHelper(const String& name, const CLI
CLICommand::Register(vname, command);
}
std::vector<String> CLICommand::GetArgumentSuggestions(const String& argument, const String& word) const
{
return std::vector<String>();
}
std::vector<String> CLICommand::GetPositionalSuggestions(const String& word) const
{
return std::vector<String>();
}
void CLICommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
{ }
bool CLICommand::ParseCommand(int argc, char **argv, po::options_description& visibleDesc,
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);
@ -164,7 +180,7 @@ found_command:
po::options_description vdesc("Command options");
if (command)
command->InitParameters(vdesc, hiddenDesc, argCompletionDesc);
command->InitParameters(vdesc, hiddenDesc);
visibleDesc.add(vdesc);
@ -182,7 +198,8 @@ found_command:
}
void CLICommand::ShowCommands(int argc, char **argv, po::options_description *visibleDesc,
po::options_description *hiddenDesc, ArgumentCompletionDescription *argCompletionDesc,
po::options_description *hiddenDesc,
ArgumentCompletionCallback globalArgCompletionCallback,
bool autocomplete, int autoindex)
{
boost::mutex::scoped_lock lock(l_RegistryMutex);
@ -263,7 +280,6 @@ void CLICommand::ShowCommands(int argc, char **argv, po::options_description *vi
if (command && autocomplete) {
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]) {
@ -290,13 +306,12 @@ void CLICommand::ShowCommands(int argc, char **argv, po::options_description *vi
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)) {
BOOST_FOREACH(const String& suggestion, globalArgCompletionCallback(aname, pword)) {
std::cout << prefix << suggestion << "\n";
}
BOOST_FOREACH(const String& suggestion, command->GetArgumentSuggestions(aname, pword)) {
std::cout << prefix << suggestion << "\n";
}
@ -309,6 +324,10 @@ complete_option:
if (cname.Find(aword) == 0)
std::cout << cname << "\n";
}
BOOST_FOREACH(const String& suggestion, command->GetPositionalSuggestions(aword)) {
std::cout << suggestion << "\n";
}
}
return;

View File

@ -29,11 +29,8 @@
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);
I2_BASE_API void AddTypeFields(const Type *type, boost::program_options::options_description& desc);
I2_BASE_API std::vector<String> GetBashCompletionSuggestions(const String& type, const String& word);
I2_BASE_API std::vector<String> GetFieldCompletionSuggestions(const Type *type, const String& word);
/**
* A CLI command.
@ -45,12 +42,15 @@ class I2_BASE_API CLICommand : public Object
public:
DECLARE_PTR_TYPEDEFS(CLICommand);
typedef std::vector<String>(*ArgumentCompletionCallback)(const String&, const String&);
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,
ArgumentCompletionDescription& argCompletionDesc) const = 0;
boost::program_options::options_description& hiddenDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const = 0;
virtual std::vector<String> GetArgumentSuggestions(const String& argument, const String& word) const;
virtual std::vector<String> GetPositionalSuggestions(const String& word) const;
static CLICommand::Ptr GetByName(const std::vector<String>& name);
static void Register(const std::vector<String>& name, const CLICommand::Ptr& command);
@ -59,14 +59,13 @@ 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,
ArgumentCompletionDescription *argCompletionDesc = NULL,
ArgumentCompletionCallback globalArgCompletionCallback = NULL,
bool autocomplete = false, int autoindex = -1);
};

View File

@ -276,8 +276,7 @@ String DaemonCommand::GetShortDescription(void) const
}
void DaemonCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
@ -293,9 +292,14 @@ 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");
std::vector<String> DaemonCommand::GetArgumentSuggestions(const String& argument, const String& word) const
{
if (argument == "config" || argument == "errorlog")
return GetBashCompletionSuggestions("file", word);
else
return CLICommand::GetArgumentSuggestions(argument, word);
}
/**

View File

@ -40,8 +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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual std::vector<String> GetArgumentSuggestions(const String& argument, const String& word) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

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

View File

@ -39,8 +39,7 @@ 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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

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

View File

@ -39,8 +39,7 @@ 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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -44,13 +44,6 @@ String FeatureListCommand::GetShortDescription(void) const
return "lists all enabled features";
}
void FeatureListCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
/* Command doesn't support any parameters. */
}
/**
* The entry point for the "feature list" CLI command.
*

View File

@ -38,9 +38,6 @@ 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,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
private:

View File

@ -51,8 +51,7 @@ String ObjectListCommand::GetShortDescription(void) const
}
void ObjectListCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("count,c", "display object counts by types")

View File

@ -42,8 +42,7 @@ 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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
private:

View File

@ -38,13 +38,6 @@ String PKINewCACommand::GetShortDescription(void) const
return "sets up a new CA";
}
void PKINewCACommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
{
/* Command doesn't support any parameters. */
}
/**
* The entry point for the "pki new-ca" CLI command.
*

View File

@ -38,9 +38,6 @@ 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,
ArgumentCompletionDescription& argCompletionDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -38,18 +38,21 @@ String PKINewCertCommand::GetShortDescription(void) const
}
void PKINewCertCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("cn", po::value<std::string>(), "Common Name")
("keyfile", po::value<std::string>(), "Key file path (output")
("csrfile", po::value<std::string>(), "CSR file path (optional, output)")
("certfile", po::value<std::string>(), "Certificate file path (optional, output)");
}
argCompletionDesc["keyfile"] = BashArgumentCompletion("file");
argCompletionDesc["csrfile"] = BashArgumentCompletion("file");
argCompletionDesc["certfile"] = BashArgumentCompletion("file");
std::vector<String> PKINewCertCommand::GetArgumentSuggestions(const String& argument, const String& word) const
{
if (argument == "keyfile" || argument == "csrfile" || argument == "certfile")
return GetBashCompletionSuggestions("file", word);
else
return CLICommand::GetArgumentSuggestions(argument, word);
}
/**

View File

@ -39,8 +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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual std::vector<String> GetArgumentSuggestions(const String& argument, const String& word) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -44,8 +44,7 @@ String PKIRequestCommand::GetShortDescription(void) const
}
void PKIRequestCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("keyfile", po::value<std::string>(), "Key file path")
@ -54,12 +53,18 @@ void PKIRequestCommand::InitParameters(boost::program_options::options_descripti
("host", po::value<std::string>(), "Icinga 2 host")
("port", po::value<std::string>(), "Icinga 2 port")
("ticket", po::value<std::string>(), "Icinga 2 PKI ticket");
}
argCompletionDesc["keyfile"] = BashArgumentCompletion("file");
argCompletionDesc["certfile"] = BashArgumentCompletion("file");
argCompletionDesc["cafile"] = BashArgumentCompletion("file");
argCompletionDesc["host"] = BashArgumentCompletion("hostname");
argCompletionDesc["port"] = BashArgumentCompletion("service");
std::vector<String> PKIRequestCommand::GetArgumentSuggestions(const String& argument, const String& word) const
{
if (argument == "keyfile" || argument == "certfile" || argument == "cafile")
return GetBashCompletionSuggestions("file", word);
else if (argument == "host")
return GetBashCompletionSuggestions("hostname", word);
else if (argument == "port")
return GetBashCompletionSuggestions("service", word);
else
return CLICommand::GetArgumentSuggestions(argument, word);
}
/**

View File

@ -39,8 +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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual std::vector<String> GetArgumentSuggestions(const String& argument, const String& word) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -40,15 +40,19 @@ String PKISignCSRCommand::GetShortDescription(void) const
}
void PKISignCSRCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("csrfile", po::value<std::string>(), "CSR file path (input)")
("certfile", po::value<std::string>(), "Certificate file path (output)");
}
argCompletionDesc["csrfile"] = BashArgumentCompletion("file");
argCompletionDesc["certfile"] = BashArgumentCompletion("file");
std::vector<String> PKISignCSRCommand::GetArgumentSuggestions(const String& argument, const String& word) const
{
if (argument == "csrfile" || argument == "certfile")
return GetBashCompletionSuggestions("file", word);
else
return CLICommand::GetArgumentSuggestions(argument, word);
}
/**

View File

@ -39,8 +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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual std::vector<String> GetArgumentSuggestions(const String& argument, const String& word) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -44,8 +44,7 @@ String PKITicketCommand::GetShortDescription(void) const
}
void PKITicketCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("cn", po::value<std::string>(), "Certificate common name")

View File

@ -39,8 +39,7 @@ 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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
};

View File

@ -103,17 +103,17 @@ String RepositoryObjectCommand::GetShortDescription(void) const
}
void RepositoryObjectCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc,
ArgumentCompletionDescription& argCompletionDesc) const
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("name", po::value<std::string>(), "The name of the object");
}
if (m_Command == RepositoryCommandAdd) {
const Type *ptype = Type::GetByName(m_Type);
ASSERT(ptype);
AddTypeFields(ptype, visibleDesc);
}
std::vector<String> RepositoryObjectCommand::GetPositionalSuggestions(const String& word) const
{
const Type *ptype = Type::GetByName(m_Type);
ASSERT(ptype);
return GetFieldCompletionSuggestions(ptype, word);
}
/**

View File

@ -48,8 +48,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,
ArgumentCompletionDescription& argCompletionDesc) const;
boost::program_options::options_description& hiddenDesc) const;
virtual std::vector<String> GetPositionalSuggestions(const String& word) const;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const;
private: