1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "cli/clicommand.hpp"
4 #include "base/logger.hpp"
5 #include "base/console.hpp"
6 #include "base/type.hpp"
7 #include "base/serializer.hpp"
8 #include <boost/algorithm/string/join.hpp>
9 #include <boost/algorithm/string/trim.hpp>
10 #include <boost/program_options.hpp>
14 using namespace icinga;
15 namespace po = boost::program_options;
17 std::vector<String> icinga::GetBashCompletionSuggestions(const String& type, const String& word)
19 std::vector<String> result;
22 String bashArg = "compgen -A " + Utility::EscapeShellArg(type) + " " + Utility::EscapeShellArg(word);
23 String cmd = "bash -c " + Utility::EscapeShellArg(bashArg);
25 FILE *fp = popen(cmd.CStr(), "r");
28 while (fgets(line, sizeof(line), fp)) {
30 boost::algorithm::trim_right_if(wline, boost::is_any_of("\r\n"));
31 result.push_back(wline);
36 /* Append a slash if there's only one suggestion and it's a directory */
37 if ((type == "file" || type == "directory") && result.size() == 1) {
38 String path = result[0];
41 if (lstat(path.CStr(), &statbuf) >= 0) {
42 if (S_ISDIR(statbuf.st_mode)) {
44 result.push_back(path + "/");
53 std::vector<String> icinga::GetFieldCompletionSuggestions(const Type::Ptr& type, const String& word)
55 std::vector<String> result;
57 for (int i = 0; i < type->GetFieldCount(); i++) {
58 Field field = type->GetFieldInfo(i);
60 if (field.Attributes & FANoUserView)
63 if (strcmp(field.TypeName, "int") != 0 && strcmp(field.TypeName, "double") != 0
64 && strcmp(field.TypeName, "bool") != 0 && strcmp(field.TypeName, "String") != 0)
67 String fname = field.Name;
69 String suggestion = fname + "=";
71 if (suggestion.Find(word) == 0)
72 result.push_back(suggestion);
78 int CLICommand::GetMinArguments() const
83 int CLICommand::GetMaxArguments() const
85 return GetMinArguments();
88 bool CLICommand::IsHidden() const
93 bool CLICommand::IsDeprecated() const
98 boost::mutex& CLICommand::GetRegistryMutex()
100 static boost::mutex mtx;
104 std::map<std::vector<String>, CLICommand::Ptr>& CLICommand::GetRegistry()
106 static std::map<std::vector<String>, CLICommand::Ptr> registry;
110 CLICommand::Ptr CLICommand::GetByName(const std::vector<String>& name)
112 boost::mutex::scoped_lock lock(GetRegistryMutex());
114 auto it = GetRegistry().find(name);
116 if (it == GetRegistry().end())
122 void CLICommand::Register(const std::vector<String>& name, const CLICommand::Ptr& function)
124 boost::mutex::scoped_lock lock(GetRegistryMutex());
125 GetRegistry()[name] = function;
128 void CLICommand::Unregister(const std::vector<String>& name)
130 boost::mutex::scoped_lock lock(GetRegistryMutex());
131 GetRegistry().erase(name);
134 std::vector<String> CLICommand::GetArgumentSuggestions(const String& argument, const String& word) const
136 return std::vector<String>();
139 std::vector<String> CLICommand::GetPositionalSuggestions(const String& word) const
141 return std::vector<String>();
144 void CLICommand::InitParameters(boost::program_options::options_description& visibleDesc,
145 boost::program_options::options_description& hiddenDesc) const
148 ImpersonationLevel CLICommand::GetImpersonationLevel() const
150 return ImpersonateIcinga;
153 bool CLICommand::ParseCommand(int argc, char **argv, po::options_description& visibleDesc,
154 po::options_description& hiddenDesc,
155 po::positional_options_description& positionalDesc,
156 po::variables_map& vm, String& cmdname, CLICommand::Ptr& command, bool autocomplete)
158 boost::mutex::scoped_lock lock(GetRegistryMutex());
160 typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
162 std::vector<String> best_match;
164 bool tried_command = false;
166 for (const CLIKeyValue& kv : GetRegistry()) {
167 const std::vector<String>& vname = kv.first;
169 std::vector<String>::size_type i;
171 for (i = 0, k = 1; i < vname.size() && k < argc; i++, k++) {
172 if (strncmp(argv[k], "-", 1) == 0 || strncmp(argv[k], "--", 2) == 0) {
177 tried_command = true;
179 if (vname[i] != argv[k])
182 if (i >= best_match.size())
183 best_match.push_back(vname[i]);
185 if (i == vname.size() - 1) {
186 cmdname = boost::algorithm::join(vname, " ");
198 po::options_description vdesc("Command options");
199 command->InitParameters(vdesc, hiddenDesc);
200 visibleDesc.add(vdesc);
203 if (autocomplete || (tried_command && !command))
206 po::options_description adesc;
207 adesc.add(visibleDesc);
208 adesc.add(hiddenDesc);
210 if (command && command->IsDeprecated()) {
211 std::cerr << ConsoleColorTag(Console_ForegroundRed | Console_Bold)
212 << "Warning: CLI command '" << cmdname << "' is DEPRECATED! Please read the Changelog."
213 << ConsoleColorTag(Console_Normal) << std::endl << std::endl;
216 po::store(po::command_line_parser(argc - arg_end, argv + arg_end).options(adesc).positional(positionalDesc).run(), vm);
222 void CLICommand::ShowCommands(int argc, char **argv, po::options_description *visibleDesc,
223 po::options_description *hiddenDesc,
224 ArgumentCompletionCallback globalArgCompletionCallback,
225 bool autocomplete, int autoindex)
227 boost::mutex::scoped_lock lock(GetRegistryMutex());
229 typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
231 std::vector<String> best_match;
233 CLICommand::Ptr command;
235 for (const CLIKeyValue& kv : GetRegistry()) {
236 const std::vector<String>& vname = kv.first;
240 std::vector<String>::size_type i;
242 for (i = 0, k = 1; i < vname.size() && k < argc; i++, k++) {
243 if (strcmp(argv[k], "--no-stack-rlimit") == 0 || strcmp(argv[k], "--autocomplete") == 0 || strcmp(argv[k], "--scm") == 0) {
249 if (autocomplete && static_cast<int>(i) >= autoindex - 1)
252 if (vname[i] != argv[k])
255 if (i >= best_match.size()) {
256 best_match.push_back(vname[i]);
259 if (i == vname.size() - 1) {
269 if (autoindex < argc)
270 aword = argv[autoindex];
272 if (autoindex - 1 > static_cast<int>(best_match.size()) && !command)
275 std::cout << "Supported commands: " << std::endl;
277 for (const CLIKeyValue& kv : GetRegistry()) {
278 const std::vector<String>& vname = kv.first;
280 if (vname.size() < best_match.size() || kv.second->IsHidden())
285 for (std::vector<String>::size_type i = 0; i < best_match.size(); i++) {
286 if (vname[i] != best_match[i]) {
298 if (autoindex - 1 < static_cast<int>(vname.size())) {
299 cname = vname[autoindex - 1];
301 if (cname.Find(aword) == 0)
302 std::cout << cname << "\n";
305 std::cout << " * " << boost::algorithm::join(vname, " ")
306 << " (" << kv.second->GetShortDescription() << ")"
307 << (kv.second->IsDeprecated() ? " (DEPRECATED)" : "") << std::endl;
312 std::cout << std::endl;
314 if (command && autocomplete) {
315 String aname, prefix, pword;
316 const po::option_description *odesc;
318 if (autoindex - 2 >= 0 && strcmp(argv[autoindex - 1], "=") == 0 && strstr(argv[autoindex - 2], "--") == argv[autoindex - 2]) {
319 aname = argv[autoindex - 2] + 2;
321 } else if (autoindex - 1 >= 0 && argv[autoindex - 1][0] == '-' && argv[autoindex - 1][1] == '-') {
322 aname = argv[autoindex - 1] + 2;
327 } else if (autoindex - 1 >= 0 && argv[autoindex - 1][0] == '-' && argv[autoindex - 1][1] != '-') {
328 aname = argv[autoindex - 1];
333 } else if (aword.GetLength() > 1 && aword[0] == '-' && aword[1] != '-') {
334 aname = aword.SubStr(0, 2);
336 pword = aword.SubStr(2);
338 goto complete_option;
341 odesc = visibleDesc->find_nothrow(aname, false);
346 if (odesc->semantic()->min_tokens() == 0)
347 goto complete_option;
349 for (const String& suggestion : globalArgCompletionCallback(odesc->long_name(), pword)) {
350 std::cout << prefix << suggestion << "\n";
353 for (const String& suggestion : command->GetArgumentSuggestions(odesc->long_name(), pword)) {
354 std::cout << prefix << suggestion << "\n";
360 for (const boost::shared_ptr<po::option_description>& odesc : visibleDesc->options()) {
361 String cname = "--" + odesc->long_name();
363 if (cname.Find(aword) == 0)
364 std::cout << cname << "\n";
367 for (const String& suggestion : command->GetPositionalSuggestions(aword)) {
368 std::cout << suggestion << "\n";