]> granicus.if.org Git - icinga2/blob - lib/cli/clicommand.cpp
Merge pull request #7145 from Icinga/feature/dotnet-4.6
[icinga2] / lib / cli / clicommand.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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>
11 #include <algorithm>
12 #include <iostream>
13
14 using namespace icinga;
15 namespace po = boost::program_options;
16
17 std::vector<String> icinga::GetBashCompletionSuggestions(const String& type, const String& word)
18 {
19         std::vector<String> result;
20
21 #ifndef _WIN32
22         String bashArg = "compgen -A " + Utility::EscapeShellArg(type) + " " + Utility::EscapeShellArg(word);
23         String cmd = "bash -c " + Utility::EscapeShellArg(bashArg);
24
25         FILE *fp = popen(cmd.CStr(), "r");
26
27         char line[4096];
28         while (fgets(line, sizeof(line), fp)) {
29                 String wline = line;
30                 boost::algorithm::trim_right_if(wline, boost::is_any_of("\r\n"));
31                 result.push_back(wline);
32         }
33
34         pclose(fp);
35
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];
39
40                 struct stat statbuf;
41                 if (lstat(path.CStr(), &statbuf) >= 0) {
42                         if (S_ISDIR(statbuf.st_mode)) {
43                                 result.clear(),
44                                 result.push_back(path + "/");
45                         }
46                 }
47         }
48 #endif /* _WIN32 */
49
50         return result;
51 }
52
53 std::vector<String> icinga::GetFieldCompletionSuggestions(const Type::Ptr& type, const String& word)
54 {
55         std::vector<String> result;
56
57         for (int i = 0; i < type->GetFieldCount(); i++) {
58                 Field field = type->GetFieldInfo(i);
59
60                 if (field.Attributes & FANoUserView)
61                         continue;
62
63                 if (strcmp(field.TypeName, "int") != 0 && strcmp(field.TypeName, "double") != 0
64                         && strcmp(field.TypeName, "bool") != 0 && strcmp(field.TypeName, "String") != 0)
65                         continue;
66
67                 String fname = field.Name;
68
69                 String suggestion = fname + "=";
70
71                 if (suggestion.Find(word) == 0)
72                         result.push_back(suggestion);
73         }
74
75         return result;
76 }
77
78 int CLICommand::GetMinArguments() const
79 {
80         return 0;
81 }
82
83 int CLICommand::GetMaxArguments() const
84 {
85         return GetMinArguments();
86 }
87
88 bool CLICommand::IsHidden() const
89 {
90         return false;
91 }
92
93 bool CLICommand::IsDeprecated() const
94 {
95         return false;
96 }
97
98 boost::mutex& CLICommand::GetRegistryMutex()
99 {
100         static boost::mutex mtx;
101         return mtx;
102 }
103
104 std::map<std::vector<String>, CLICommand::Ptr>& CLICommand::GetRegistry()
105 {
106         static std::map<std::vector<String>, CLICommand::Ptr> registry;
107         return registry;
108 }
109
110 CLICommand::Ptr CLICommand::GetByName(const std::vector<String>& name)
111 {
112         boost::mutex::scoped_lock lock(GetRegistryMutex());
113
114         auto it = GetRegistry().find(name);
115
116         if (it == GetRegistry().end())
117                 return nullptr;
118
119         return it->second;
120 }
121
122 void CLICommand::Register(const std::vector<String>& name, const CLICommand::Ptr& function)
123 {
124         boost::mutex::scoped_lock lock(GetRegistryMutex());
125         GetRegistry()[name] = function;
126 }
127
128 void CLICommand::Unregister(const std::vector<String>& name)
129 {
130         boost::mutex::scoped_lock lock(GetRegistryMutex());
131         GetRegistry().erase(name);
132 }
133
134 std::vector<String> CLICommand::GetArgumentSuggestions(const String& argument, const String& word) const
135 {
136         return std::vector<String>();
137 }
138
139 std::vector<String> CLICommand::GetPositionalSuggestions(const String& word) const
140 {
141         return std::vector<String>();
142 }
143
144 void CLICommand::InitParameters(boost::program_options::options_description& visibleDesc,
145         boost::program_options::options_description& hiddenDesc) const
146 { }
147
148 ImpersonationLevel CLICommand::GetImpersonationLevel() const
149 {
150         return ImpersonateIcinga;
151 }
152
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)
157 {
158         boost::mutex::scoped_lock lock(GetRegistryMutex());
159
160         typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
161
162         std::vector<String> best_match;
163         int arg_end = 0;
164         bool tried_command = false;
165
166         for (const CLIKeyValue& kv : GetRegistry()) {
167                 const std::vector<String>& vname = kv.first;
168
169                 std::vector<String>::size_type i;
170                 int k;
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) {
173                                 i--;
174                                 continue;
175                         }
176
177                         tried_command = true;
178
179                         if (vname[i] != argv[k])
180                                 break;
181
182                         if (i >= best_match.size())
183                                 best_match.push_back(vname[i]);
184
185                         if (i == vname.size() - 1) {
186                                 cmdname = boost::algorithm::join(vname, " ");
187                                 command = kv.second;
188                                 arg_end = k;
189                                 goto found_command;
190                         }
191                 }
192         }
193
194 found_command:
195         lock.unlock();
196
197         if (command) {
198                 po::options_description vdesc("Command options");
199                 command->InitParameters(vdesc, hiddenDesc);
200                 visibleDesc.add(vdesc);
201         }
202
203         if (autocomplete || (tried_command && !command))
204                 return true;
205
206         po::options_description adesc;
207         adesc.add(visibleDesc);
208         adesc.add(hiddenDesc);
209
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;
214         }
215
216         po::store(po::command_line_parser(argc - arg_end, argv + arg_end).options(adesc).positional(positionalDesc).run(), vm);
217         po::notify(vm);
218
219         return true;
220 }
221
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)
226 {
227         boost::mutex::scoped_lock lock(GetRegistryMutex());
228
229         typedef std::map<std::vector<String>, CLICommand::Ptr>::value_type CLIKeyValue;
230
231         std::vector<String> best_match;
232         int arg_begin = 0;
233         CLICommand::Ptr command;
234
235         for (const CLIKeyValue& kv : GetRegistry()) {
236                 const std::vector<String>& vname = kv.first;
237
238                 arg_begin = 0;
239
240                 std::vector<String>::size_type i;
241                 int k;
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) {
244                                 i--;
245                                 arg_begin++;
246                                 continue;
247                         }
248
249                         if (autocomplete && static_cast<int>(i) >= autoindex - 1)
250                                 break;
251
252                         if (vname[i] != argv[k])
253                                 break;
254
255                         if (i >= best_match.size()) {
256                                 best_match.push_back(vname[i]);
257                         }
258
259                         if (i == vname.size() - 1) {
260                                 command = kv.second;
261                                 break;
262                         }
263                 }
264         }
265
266         String aword;
267
268         if (autocomplete) {
269                 if (autoindex < argc)
270                         aword = argv[autoindex];
271
272                 if (autoindex - 1 > static_cast<int>(best_match.size()) && !command)
273                         return;
274         } else
275                 std::cout << "Supported commands: " << std::endl;
276
277         for (const CLIKeyValue& kv : GetRegistry()) {
278                 const std::vector<String>& vname = kv.first;
279
280                 if (vname.size() < best_match.size() || kv.second->IsHidden())
281                         continue;
282
283                 bool match = true;
284
285                 for (std::vector<String>::size_type i = 0; i < best_match.size(); i++) {
286                         if (vname[i] != best_match[i]) {
287                                 match = false;
288                                 break;
289                         }
290                 }
291
292                 if (!match)
293                         continue;
294
295                 if (autocomplete) {
296                         String cname;
297
298                         if (autoindex - 1 < static_cast<int>(vname.size())) {
299                                 cname = vname[autoindex - 1];
300
301                                 if (cname.Find(aword) == 0)
302                                         std::cout << cname << "\n";
303                         }
304                 } else {
305                         std::cout << "  * " << boost::algorithm::join(vname, " ")
306                                 << " (" << kv.second->GetShortDescription() << ")"
307                                 << (kv.second->IsDeprecated() ? " (DEPRECATED)" : "") << std::endl;
308                 }
309         }
310
311         if (!autocomplete)
312                 std::cout << std::endl;
313
314         if (command && autocomplete) {
315                 String aname, prefix, pword;
316                 const po::option_description *odesc;
317
318                 if (autoindex - 2 >= 0 && strcmp(argv[autoindex - 1], "=") == 0 && strstr(argv[autoindex - 2], "--") == argv[autoindex - 2]) {
319                         aname = argv[autoindex - 2] + 2;
320                         pword = aword;
321                 } else if (autoindex - 1 >= 0 && argv[autoindex - 1][0] == '-' && argv[autoindex - 1][1] == '-') {
322                         aname = argv[autoindex - 1] + 2;
323                         pword = aword;
324
325                         if (pword == "=")
326                                 pword = "";
327                 } else if (autoindex - 1 >= 0 && argv[autoindex - 1][0] == '-' && argv[autoindex - 1][1] != '-') {
328                         aname = argv[autoindex - 1];
329                         pword = aword;
330
331                         if (pword == "=")
332                                 pword = "";
333                 } else if (aword.GetLength() > 1 && aword[0] == '-' && aword[1] != '-') {
334                         aname = aword.SubStr(0, 2);
335                         prefix = aname;
336                         pword = aword.SubStr(2);
337                 } else {
338                         goto complete_option;
339                 }
340
341                 odesc = visibleDesc->find_nothrow(aname, false);
342
343                 if (!odesc)
344                         return;
345
346                 if (odesc->semantic()->min_tokens() == 0)
347                         goto complete_option;
348
349                 for (const String& suggestion : globalArgCompletionCallback(odesc->long_name(), pword)) {
350                         std::cout << prefix << suggestion << "\n";
351                 }
352
353                 for (const String& suggestion : command->GetArgumentSuggestions(odesc->long_name(), pword)) {
354                         std::cout << prefix << suggestion << "\n";
355                 }
356
357                 return;
358
359 complete_option:
360                 for (const boost::shared_ptr<po::option_description>& odesc : visibleDesc->options()) {
361                         String cname = "--" + odesc->long_name();
362
363                         if (cname.Find(aword) == 0)
364                                 std::cout << cname << "\n";
365                 }
366
367                 for (const String& suggestion : command->GetPositionalSuggestions(aword)) {
368                         std::cout << suggestion << "\n";
369                 }
370         }
371
372         return;
373 }