summaryrefslogtreecommitdiffstats
path: root/lib/cli/clicommand.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cli/clicommand.cpp')
-rw-r--r--lib/cli/clicommand.cpp373
1 files changed, 373 insertions, 0 deletions
diff --git a/lib/cli/clicommand.cpp b/lib/cli/clicommand.cpp
new file mode 100644
index 0000000..cfdce09
--- /dev/null
+++ b/lib/cli/clicommand.cpp
@@ -0,0 +1,373 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "cli/clicommand.hpp"
+#include "base/logger.hpp"
+#include "base/console.hpp"
+#include "base/type.hpp"
+#include "base/serializer.hpp"
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/program_options.hpp>
+#include <algorithm>
+#include <iostream>
+
+using namespace icinga;
+namespace po = boost::program_options;
+
+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(word);
+ 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);
+ }
+
+ pclose(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;
+}
+
+std::vector<String> icinga::GetFieldCompletionSuggestions(const Type::Ptr& type, const String& word)
+{
+ std::vector<String> result;
+
+ for (int i = 0; i < type->GetFieldCount(); i++) {
+ Field field = type->GetFieldInfo(i);
+
+ if (field.Attributes & FANoUserView)
+ continue;
+
+ if (strcmp(field.TypeName, "int") != 0 && strcmp(field.TypeName, "double") != 0
+ && strcmp(field.TypeName, "bool") != 0 && strcmp(field.TypeName, "String") != 0)
+ continue;
+
+ String fname = field.Name;
+
+ String suggestion = fname + "=";
+
+ if (suggestion.Find(word) == 0)
+ result.push_back(suggestion);
+ }
+
+ return result;
+}
+
+int CLICommand::GetMinArguments() const
+{
+ return 0;
+}
+
+int CLICommand::GetMaxArguments() const
+{
+ return GetMinArguments();
+}
+
+bool CLICommand::IsHidden() const
+{
+ return false;
+}
+
+bool CLICommand::IsDeprecated() const
+{
+ return false;
+}
+
+std::mutex& CLICommand::GetRegistryMutex()
+{
+ static std::mutex mtx;
+ return mtx;
+}
+
+std::map<std::vector<String>, CLICommand::Ptr>& CLICommand::GetRegistry()
+{
+ static std::map<std::vector<String>, CLICommand::Ptr> registry;
+ return registry;
+}
+
+CLICommand::Ptr CLICommand::GetByName(const std::vector<String>& name)
+{
+ std::unique_lock<std::mutex> lock(GetRegistryMutex());
+
+ auto it = GetRegistry().find(name);
+
+ if (it == GetRegistry().end())
+ return nullptr;
+
+ return it->second;
+}
+
+void CLICommand::Register(const std::vector<String>& name, const CLICommand::Ptr& function)
+{
+ std::unique_lock<std::mutex> lock(GetRegistryMutex());
+ GetRegistry()[name] = function;
+}
+
+void CLICommand::Unregister(const std::vector<String>& name)
+{
+ std::unique_lock<std::mutex> lock(GetRegistryMutex());
+ GetRegistry().erase(name);
+}
+
+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
+{ }
+
+ImpersonationLevel CLICommand::GetImpersonationLevel() const
+{
+ return ImpersonateIcinga;
+}
+
+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)
+{
+ std::unique_lock<std::mutex> lock(GetRegistryMutex());
+
+ typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
+
+ std::vector<String> best_match;
+ int arg_end = 0;
+ bool tried_command = false;
+
+ for (const CLIKeyValue& kv : GetRegistry()) {
+ const std::vector<String>& vname = kv.first;
+
+ std::vector<String>::size_type i;
+ int k;
+ for (i = 0, k = 1; i < vname.size() && k < argc; i++, k++) {
+ if (strncmp(argv[k], "-", 1) == 0 || strncmp(argv[k], "--", 2) == 0) {
+ i--;
+ continue;
+ }
+
+ tried_command = true;
+
+ if (vname[i] != argv[k])
+ break;
+
+ if (i >= best_match.size())
+ best_match.push_back(vname[i]);
+
+ if (i == vname.size() - 1) {
+ cmdname = boost::algorithm::join(vname, " ");
+ command = kv.second;
+ arg_end = k;
+ goto found_command;
+ }
+ }
+ }
+
+found_command:
+ lock.unlock();
+
+ if (command) {
+ po::options_description vdesc("Command options");
+ command->InitParameters(vdesc, hiddenDesc);
+ visibleDesc.add(vdesc);
+ }
+
+ if (autocomplete || (tried_command && !command))
+ return true;
+
+ po::options_description adesc;
+ adesc.add(visibleDesc);
+ adesc.add(hiddenDesc);
+
+ if (command && command->IsDeprecated()) {
+ std::cerr << ConsoleColorTag(Console_ForegroundRed | Console_Bold)
+ << "Warning: CLI command '" << cmdname << "' is DEPRECATED! Please read the Changelog."
+ << ConsoleColorTag(Console_Normal) << std::endl << std::endl;
+ }
+
+ po::store(po::command_line_parser(argc - arg_end, argv + arg_end).options(adesc).positional(positionalDesc).run(), vm);
+ po::notify(vm);
+
+ return true;
+}
+
+void CLICommand::ShowCommands(int argc, char **argv, po::options_description *visibleDesc,
+ po::options_description *hiddenDesc,
+ ArgumentCompletionCallback globalArgCompletionCallback,
+ bool autocomplete, int autoindex)
+{
+ std::unique_lock<std::mutex> lock(GetRegistryMutex());
+
+ typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
+
+ std::vector<String> best_match;
+ int arg_begin = 0;
+ CLICommand::Ptr command;
+
+ for (const CLIKeyValue& kv : GetRegistry()) {
+ const std::vector<String>& vname = kv.first;
+
+ arg_begin = 0;
+
+ std::vector<String>::size_type i;
+ int k;
+ for (i = 0, k = 1; i < vname.size() && k < argc; i++, k++) {
+ if (strcmp(argv[k], "--no-stack-rlimit") == 0 || strcmp(argv[k], "--autocomplete") == 0 || strcmp(argv[k], "--scm") == 0) {
+ i--;
+ arg_begin++;
+ continue;
+ }
+
+ if (autocomplete && static_cast<int>(i) >= autoindex - 1)
+ break;
+
+ if (vname[i] != argv[k])
+ break;
+
+ if (i >= best_match.size()) {
+ best_match.push_back(vname[i]);
+ }
+
+ if (i == vname.size() - 1) {
+ command = kv.second;
+ break;
+ }
+ }
+ }
+
+ String aword;
+
+ if (autocomplete) {
+ if (autoindex < argc)
+ aword = argv[autoindex];
+
+ if (autoindex - 1 > static_cast<int>(best_match.size()) && !command)
+ return;
+ } else
+ std::cout << "Supported commands: " << std::endl;
+
+ for (const CLIKeyValue& kv : GetRegistry()) {
+ const std::vector<String>& vname = kv.first;
+
+ if (vname.size() < best_match.size() || kv.second->IsHidden())
+ continue;
+
+ bool match = true;
+
+ for (std::vector<String>::size_type i = 0; i < best_match.size(); i++) {
+ if (vname[i] != best_match[i]) {
+ match = false;
+ break;
+ }
+ }
+
+ if (!match)
+ continue;
+
+ if (autocomplete) {
+ String cname;
+
+ if (autoindex - 1 < static_cast<int>(vname.size())) {
+ cname = vname[autoindex - 1];
+
+ if (cname.Find(aword) == 0)
+ std::cout << cname << "\n";
+ }
+ } else {
+ std::cout << " * " << boost::algorithm::join(vname, " ")
+ << " (" << kv.second->GetShortDescription() << ")"
+ << (kv.second->IsDeprecated() ? " (DEPRECATED)" : "") << std::endl;
+ }
+ }
+
+ if (!autocomplete)
+ std::cout << std::endl;
+
+ if (command && autocomplete) {
+ String aname, prefix, pword;
+ 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 (autoindex - 1 >= 0 && argv[autoindex - 1][0] == '-' && argv[autoindex - 1][1] != '-') {
+ aname = argv[autoindex - 1];
+ pword = aword;
+
+ if (pword == "=")
+ pword = "";
+ } else if (aword.GetLength() > 1 && aword[0] == '-' && aword[1] != '-') {
+ aname = aword.SubStr(0, 2);
+ prefix = aname;
+ pword = aword.SubStr(2);
+ } else {
+ goto complete_option;
+ }
+
+ odesc = visibleDesc->find_nothrow(aname, false);
+
+ if (!odesc)
+ return;
+
+ if (odesc->semantic()->min_tokens() == 0)
+ goto complete_option;
+
+ for (const String& suggestion : globalArgCompletionCallback(odesc->long_name(), pword)) {
+ std::cout << prefix << suggestion << "\n";
+ }
+
+ for (const String& suggestion : command->GetArgumentSuggestions(odesc->long_name(), pword)) {
+ std::cout << prefix << suggestion << "\n";
+ }
+
+ return;
+
+complete_option:
+ for (const boost::shared_ptr<po::option_description>& odesc : visibleDesc->options()) {
+ String cname = "--" + odesc->long_name();
+
+ if (cname.Find(aword) == 0)
+ std::cout << cname << "\n";
+ }
+
+ for (const String& suggestion : command->GetPositionalSuggestions(aword)) {
+ std::cout << suggestion << "\n";
+ }
+ }
+
+ return;
+}