From 2a60ce8625f0d282b22dae0bbeb77bc727cfe7b9 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Tue, 14 Oct 2014 16:45:00 +0200 Subject: [PATCH] Improve auto-completion for arguments fixes #7375 --- etc/bash_completion.d/icinga2 | 1 + etc/icinga2/init.conf.cmake | 2 + icinga-app/icinga.cpp | 35 +++++++++++- lib/base/clicommand.cpp | 94 ++++++++++++++++++++++++++++--- lib/base/clicommand.hpp | 18 +++++- lib/cli/daemoncommand.cpp | 6 +- lib/cli/daemoncommand.hpp | 3 +- lib/cli/featuredisablecommand.cpp | 3 +- lib/cli/featuredisablecommand.hpp | 3 +- lib/cli/featureenablecommand.cpp | 3 +- lib/cli/featureenablecommand.hpp | 3 +- lib/cli/featurelistcommand.cpp | 3 +- lib/cli/featurelistcommand.hpp | 3 +- lib/cli/pkinewcacommand.cpp | 3 +- lib/cli/pkinewcacommand.hpp | 3 +- lib/cli/pkinewcertcommand.cpp | 3 +- lib/cli/pkinewcertcommand.hpp | 3 +- 17 files changed, 164 insertions(+), 25 deletions(-) diff --git a/etc/bash_completion.d/icinga2 b/etc/bash_completion.d/icinga2 index 75044b328..1b9013e86 100644 --- a/etc/bash_completion.d/icinga2 +++ b/etc/bash_completion.d/icinga2 @@ -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 } diff --git a/etc/icinga2/init.conf.cmake b/etc/icinga2/init.conf.cmake index 9f57bca82..0772f320b 100644 --- a/etc/icinga2/init.conf.cmake +++ b/etc/icinga2/init.conf.cmake @@ -5,3 +5,5 @@ const RunAsUser = "@ICINGA2_USER@" const RunAsGroup = "@ICINGA2_GROUP@" + +library "cli" diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 89c0df7f6..2a09c9bf6 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -48,6 +48,33 @@ SERVICE_STATUS l_SvcStatus; SERVICE_STATUS_HANDLE l_SvcStatusHandle; #endif /* _WIN32 */ +static std::vector LogLevelCompletion(const String& arg) +{ + std::vector 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 args; diff --git a/lib/base/clicommand.cpp b/lib/base/clicommand.cpp index 41d373f25..0dfd358d7 100644 --- a/lib/base/clicommand.cpp +++ b/lib/base/clicommand.cpp @@ -21,6 +21,7 @@ #include "base/logger_fwd.hpp" #include #include +#include #include #include #include @@ -32,6 +33,46 @@ namespace po = boost::program_options; boost::mutex l_RegistryMutex; std::map, CLICommand::Ptr> l_Registry; +static std::vector BashArgumentCompletionHelper(const String& type, const String& arg) +{ + std::vector 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& 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& odesc, visibleDesc->options()) { String cname = "--" + odesc->long_name(); diff --git a/lib/base/clicommand.hpp b/lib/base/clicommand.hpp index 931160701..5abdea6bc 100644 --- a/lib/base/clicommand.hpp +++ b/lib/base/clicommand.hpp @@ -29,6 +29,11 @@ namespace icinga { +typedef boost::function (const String&)> ArgumentCompletionCallback; +typedef std::map 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& ap) const = 0; static CLICommand::Ptr GetByName(const std::vector& 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); }; /** diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp index 24286968a..f53e972f4 100644 --- a/lib/cli/daemoncommand.cpp +++ b/lib/cli/daemoncommand.cpp @@ -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 >(), "parse a configuration file") @@ -292,6 +293,9 @@ void DaemonCommand::InitParameters(boost::program_options::options_description& hiddenDesc.add_options() ("reload-internal", po::value(), "used internally to implement config reload: do not call manually, send SIGHUP instead"); #endif /* _WIN32 */ + + argCompletionDesc["config"] = BashArgumentCompletion("file"); + argCompletionDesc["errorlog"] = BashArgumentCompletion("file"); } /** diff --git a/lib/cli/daemoncommand.hpp b/lib/cli/daemoncommand.hpp index 6de3bf34f..3341bcb30 100644 --- a/lib/cli/daemoncommand.hpp +++ b/lib/cli/daemoncommand.hpp @@ -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& ap) const; }; diff --git a/lib/cli/featuredisablecommand.cpp b/lib/cli/featuredisablecommand.cpp index bf20fac08..725c0c5c1 100644 --- a/lib/cli/featuredisablecommand.cpp +++ b/lib/cli/featuredisablecommand.cpp @@ -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. */ } diff --git a/lib/cli/featuredisablecommand.hpp b/lib/cli/featuredisablecommand.hpp index fcb6f7839..32d9e8c4d 100644 --- a/lib/cli/featuredisablecommand.hpp +++ b/lib/cli/featuredisablecommand.hpp @@ -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& ap) const; }; diff --git a/lib/cli/featureenablecommand.cpp b/lib/cli/featureenablecommand.cpp index 4e7774aea..13b8b87c9 100644 --- a/lib/cli/featureenablecommand.cpp +++ b/lib/cli/featureenablecommand.cpp @@ -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. */ } diff --git a/lib/cli/featureenablecommand.hpp b/lib/cli/featureenablecommand.hpp index 982e9ce3e..41f066660 100644 --- a/lib/cli/featureenablecommand.hpp +++ b/lib/cli/featureenablecommand.hpp @@ -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& ap) const; }; diff --git a/lib/cli/featurelistcommand.cpp b/lib/cli/featurelistcommand.cpp index 9e2f59cd8..eaa66ced2 100644 --- a/lib/cli/featurelistcommand.cpp +++ b/lib/cli/featurelistcommand.cpp @@ -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. */ } diff --git a/lib/cli/featurelistcommand.hpp b/lib/cli/featurelistcommand.hpp index 5ebdedc92..350bca2a6 100644 --- a/lib/cli/featurelistcommand.hpp +++ b/lib/cli/featurelistcommand.hpp @@ -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& ap) const; private: diff --git a/lib/cli/pkinewcacommand.cpp b/lib/cli/pkinewcacommand.cpp index 19b4aeed5..71ee57cb1 100644 --- a/lib/cli/pkinewcacommand.cpp +++ b/lib/cli/pkinewcacommand.cpp @@ -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. */ } diff --git a/lib/cli/pkinewcacommand.hpp b/lib/cli/pkinewcacommand.hpp index b2434ddea..e063f70b8 100644 --- a/lib/cli/pkinewcacommand.hpp +++ b/lib/cli/pkinewcacommand.hpp @@ -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& ap) const; }; diff --git a/lib/cli/pkinewcertcommand.cpp b/lib/cli/pkinewcertcommand.cpp index 4c36266f8..3c38cf2b8 100644 --- a/lib/cli/pkinewcertcommand.cpp +++ b/lib/cli/pkinewcertcommand.cpp @@ -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(), "Common Name") diff --git a/lib/cli/pkinewcertcommand.hpp b/lib/cli/pkinewcertcommand.hpp index 051d085ce..98f356a5e 100644 --- a/lib/cli/pkinewcertcommand.hpp +++ b/lib/cli/pkinewcertcommand.hpp @@ -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& ap) const; };