eeae6fde883086923dc2700794edbc3bba10386d
[vsc-common.git] / cli / core.c
1 /*
2  * core.c: Library for interactive commandline interfaces
3  *
4  * Copyright (C) 2009 Matthias Bolte <matthias.bolte@googlemail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
19  */
20
21 #include <malloc.h>
22
23 #include <readline/readline.h>
24 #include <readline/history.h>
25
26 #include <libvscmisc/assert.h>
27 #include <libvscmisc/error.h>
28 #include <libvscmisc/memory.h>
29
30 #include "../include/libvsccli/command.h"
31 #include "../include/libvsccli/core.h"
32 #include "../include/libvsccli/help.h"
33 #include "../include/libvsccli/option.h"
34 #include "command.h"
35 #include "option.h"
36
37 static int _initialized = FALSE;
38 static int _verbose_errors = FALSE;
39
40 const char *_vsc_cli_name = NULL;
41 const char *_vsc_cli_help = NULL;
42 const struct VscCliOptionInfo *_vsc_cli_option_infos = NULL;
43 const struct VscCliCommandInfo **_vsc_cli_command_infos = NULL;
44 int _vsc_cli_output_coloring = TRUE;
45 int _vsc_cli_interactive = FALSE;
46
47 static void
48 _quit (struct VscError *error, const struct VscCliOption *option_list,
49        void *user_data);
50
51 static const struct VscCliOptionInfo _quit_option_infos[] = {
52         { -1, NULL, NULL, 0, FALSE },
53 };
54
55 const struct VscCliCommandInfo vsc_cli_quit_command_info = {
56         "quit",
57         "quit interactive mode",
58         "",
59         _quit,
60         _quit_option_infos,
61 };
62
63 static void
64 _quit (struct VscError *error VSC__ATTR__UNUSED,
65        const struct VscCliOption *option_list VSC__ATTR__UNUSED,
66        void *user_data VSC__ATTR__UNUSED)
67 {
68         _vsc_cli_interactive = FALSE;
69 }
70
71 static char *
72 _readline_command_generator (const char *text, int state)
73 {
74         static int last_index, text_length;
75         const struct VscCliCommandInfo *command_info = NULL;
76
77         if (state == 0) {
78                 last_index = 0;
79                 text_length = strlen (text);
80         }
81
82         while (_vsc_cli_command_infos[last_index] != NULL) {
83                 command_info = _vsc_cli_command_infos[last_index];
84                 ++last_index;
85
86                 if (strncmp (text, command_info->name, text_length) == 0) {
87                         return strdup (command_info->name);
88                 }
89         }
90
91         return NULL;
92 }
93
94 static char *
95 _readline_options_generator(const char *text, int state)
96 {
97         static int last_index, text_length;
98         static const struct VscCliCommandInfo *command_info = NULL;
99         const struct VscCliCommandInfo *help_command_info = NULL;
100         char *ptr = NULL;
101         char *command_name = NULL;
102         const struct VscCliOptionInfo *option_info;
103         char *result = NULL;
104
105         if (state == 0) {
106                 ptr = strchr (rl_line_buffer, ' ');
107
108                 if (ptr == NULL) {
109                         return NULL;
110                 }
111
112                 command_name = calloc (1, (ptr - rl_line_buffer) + 1);
113
114                 if (command_name == NULL) {
115                         return NULL;
116                 }
117
118                 memcpy (command_name, rl_line_buffer, ptr - rl_line_buffer);
119
120                 command_info = _vsc_cli_command_info_lookup (command_name);
121                 last_index = 0;
122                 text_length = strlen (text);
123
124                 vsc_free (&command_name);
125         }
126
127         if (command_info == NULL) {
128                 return NULL;
129         }
130
131         if (command_info->option_infos[0].long_name == NULL) {
132                 return NULL;
133         }
134
135         if (strcmp (command_info->name, "help") == 0) {
136                 while (_vsc_cli_command_infos[last_index] != NULL) {
137                         help_command_info = _vsc_cli_command_infos[last_index];
138                         ++last_index;
139
140                         if (strncmp (text, help_command_info->name, text_length) == 0) {
141                                 return strdup (help_command_info->name);
142                         }
143                 }
144         } else {
145                 while (command_info->option_infos[last_index].long_name != NULL) {
146                         option_info = &command_info->option_infos[last_index];
147                         ++last_index;
148
149                         if (option_info->type == VSC_CLI__OPTION_TYPE__DATA) {
150                                 continue;
151                         }
152
153                         if (text_length > 2) {
154                                 if (strncmp (option_info->long_name, text + 2,
155                                              text_length - 2) != 0) {
156                                         continue;
157                                 }
158                         }
159
160                         result = calloc (strlen (option_info->long_name) + 3, 1);
161
162                         if (result == NULL) {
163                                 return NULL;
164                         }
165
166                         snprintf (result, strlen (option_info->long_name) + 3,  "--%s",
167                                   option_info->long_name);
168
169                         return result;
170                 }
171         }
172
173         return NULL;
174 }
175
176 static char **
177 _readline_completion (const char *text, int start, int end VSC__ATTR__UNUSED)
178 {
179         char **matches = (char **) NULL;
180
181         if (start == 0) {
182                 matches = rl_completion_matches(text, _readline_command_generator);
183         } else {
184                 matches = rl_completion_matches(text, _readline_options_generator);
185         }
186
187         return matches;
188 }
189
190 void
191 vsc_cli_init (struct VscError *error,
192               const char *name, const char *help,
193               const struct VscCliOptionInfo *option_infos,
194               const struct VscCliCommandInfo **command_infos,
195               int output_coloring)
196 {
197         VSC__ASSERT (error != NULL);
198         VSC__ASSERT (! error->occured);
199         VSC__ASSERT (name != NULL);
200         VSC__ASSERT (help != NULL);
201         VSC__ASSERT (option_infos != NULL);
202         VSC__ASSERT (command_infos != NULL);
203
204         if (_initialized) {
205                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
206                              "Already initialized");
207                 return;
208         }
209
210         if (_vsc_cli_option_infos_lookup_data (option_infos, 0) != 0) {
211                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_ARGUMENT,
212                              "Data options can not be used as direct options");
213                 return;
214         }
215
216         _vsc_cli_name = name;
217         _vsc_cli_help = help;
218         _vsc_cli_option_infos = option_infos;
219         _vsc_cli_command_infos = command_infos;
220         _vsc_cli_output_coloring = output_coloring;
221
222         rl_readline_name = "vscmgmt-cli";
223         rl_attempted_completion_function = _readline_completion;
224
225         stifle_history (500);
226
227         _initialized = TRUE;
228 }
229
230 void
231 vsc_cli_cleanup (void)
232 {
233         VSC__ASSERT (_initialized);
234
235         if (! _initialized) {
236                 return;
237         }
238
239         _initialized = FALSE;
240 }
241
242 void
243 vsc_cli_error_fprint (struct VscError *error, FILE *stream)
244 {
245         static const char *prefix_colored = "\033[01;31mError\033[0m";
246         static const char *prefix = "Error";
247
248         if (_verbose_errors) {
249                 vsc_error_fprint (error, stream);
250         } else {
251                 if (error->message != NULL) {
252                         if (error->code == VSC__ERROR_CODE__REMOTE_ERROR) {
253                                 fprintf (stream, "%s: Remote: %s\n",
254                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
255                                          error->message);
256                         } else {
257                                 fprintf (stream, "%s: %s\n",
258                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
259                                          error->message);
260                         }
261                 } else {
262                         fprintf (stream, "%s: %s\n",
263                                  _vsc_cli_output_coloring ? prefix_colored : prefix,
264                                  vsc_error_string(error->code));
265                 }
266         }
267 }
268
269 struct VscCliOption * /* option_list */
270 vsc_cli_parse_option_list (struct VscError *error, const char *input,
271                            const char **input_tail)
272 {
273         int option_parsed;
274         struct VscCliOption option;
275         struct VscCliOption *option_list = NULL;
276
277         VSC__ASSERT (error != NULL);
278         VSC__ASSERT (! error->occured);
279         VSC__ASSERT (input != NULL);
280         VSC__ASSERT (input_tail != NULL);
281
282         *input_tail = input;
283
284         while (*input_tail != NULL && **input_tail != '\0') {
285                 option_parsed =
286                    _vsc_cli_option_parse (error, _vsc_cli_option_infos, -1, NULL,
287                                           *input_tail, input_tail, &option);
288
289                 if (error->occured) {
290                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
291                         goto failure;
292                 }
293
294                 if (option_parsed) {
295                         if (strcmp (option.info->long_name,
296                                     VSC_CLI__VERBOSE_ERRORS__OPTION_INFO__LONG_NAME) == 0) {
297                                 _verbose_errors = TRUE;
298                         } else if (strcmp (option.info->long_name,
299                                     VSC_CLI__NO_COLOR__OPTION_INFO__LONG_NAME) == 0) {
300                                 _vsc_cli_output_coloring = FALSE;
301                         }
302
303                         _vsc_cli_option_list_append (error, &option_list, &option);
304                         vsc_free (&option.string);
305
306                         if (error->occured) {
307                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
308                                 goto failure;
309                         }
310                 } else {
311                         break;
312                 }
313         }
314
315         return option_list;
316
317 failure:
318         vsc_cli_option_list_free (&option_list);
319
320         return NULL;
321 }
322
323 struct VscCliCommand * /* command_list */
324 vsc_cli_parse_command_list (struct VscError *error, const char *input,
325                             const char **input_tail)
326 {
327         int command_parsed;
328         struct VscCliCommand command;
329         struct VscCliCommand *command_list = NULL;
330
331         VSC__ASSERT (error != NULL);
332         VSC__ASSERT (! error->occured);
333         VSC__ASSERT (input != NULL);
334         VSC__ASSERT (input_tail != NULL);
335
336         *input_tail = input;
337
338         while (*input_tail != NULL && **input_tail != '\0') {
339                 command_parsed =
340                    vsc_cli_command_parse (error, *input_tail, input_tail, &command);
341
342                 if (error->occured) {
343                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
344                         goto failure;
345                 }
346
347                 if (command_parsed) {
348                         _vsc_cli_command_list_append (error, &command_list, &command);
349                         vsc_cli_option_list_free (&command.option_list);
350
351                         if (error->occured) {
352                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
353                                 goto failure;
354                         }
355                 } else {
356                         break;
357                 }
358         }
359
360         return command_list;
361
362 failure:
363         vsc_cli_command_list_free (&command_list);
364
365         return NULL;
366 }
367
368 void
369 vsc_cli_run_command_list (struct VscError *error,
370                           const struct VscCliCommand *command_list,
371                           void *user_data)
372 {
373         struct VscCliCommand *command;
374
375         VSC__ASSERT (error != NULL);
376         VSC__ASSERT (! error->occured);
377         VSC__ASSERT (command_list != NULL);
378
379         for (command = (struct VscCliCommand *) command_list; command != NULL;
380              command = command->next) {
381                 command->info->function (error, command->option_list, user_data);
382
383                 if (error->occured) {
384                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
385                         return;
386                 }
387         }
388 }
389
390 void
391 vsc_cli_enter_interactive (VscCliErrorFunction error_function,
392                            VscCliPromptFunction prompt_function,
393                            void *user_data)
394 {
395         struct VscError error;
396         char *input = NULL;
397         char prompt[256];
398         const char *input_tail = NULL;
399         struct VscCliCommand *command_list = NULL;
400
401         VSC__ASSERT (error_function != NULL);
402         VSC__ASSERT (prompt_function != NULL);
403         VSC__ASSERT (_vsc_cli_command_info_lookup ("quit") != NULL);
404
405         vsc_error_init (&error);
406
407         _vsc_cli_interactive = TRUE;
408
409         while (_vsc_cli_interactive) {
410                 prompt_function (prompt, 256, user_data);
411
412                 input = readline (prompt);
413
414                 if (input == NULL) {
415                         goto outer_cleanup;
416                 }
417
418                 if (*input == '\0') {
419                         goto inner_cleanup;
420                 }
421
422                 add_history (input);
423
424                 command_list = vsc_cli_parse_command_list
425                                   (&error, input, &input_tail);
426
427                 if (error.occured) {
428                         error_function (&error, user_data);
429                         goto inner_cleanup;
430                 }
431
432                 if (command_list != NULL) {
433                         vsc_cli_run_command_list (&error, command_list, user_data);
434
435                         if (error.occured) {
436                                 error_function (&error, user_data);
437                                 goto inner_cleanup;
438                         }
439                 }
440
441 inner_cleanup:
442                 vsc_free (&input);
443                 vsc_cli_command_list_free (&command_list);
444                 vsc_error_cleanup (&error);
445                 vsc_error_init (&error);
446         }
447
448 outer_cleanup:
449         vsc_error_cleanup (&error);
450
451         _vsc_cli_interactive = FALSE;
452 }