[misc] Add error code 'GENERIC_BADNESS'
[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 /*!
22  * @file
23  */
24
25 #include <malloc.h>
26
27 #include <readline/readline.h>
28 #include <readline/history.h>
29
30 #include <libvscmisc/assert.h>
31 #include <libvscmisc/debug.h>
32 #include <libvscmisc/error.h>
33 #include <libvscmisc/memory.h>
34 #include <libvscmisc/string.h>
35
36 #include "../include/libvsccli/command.h"
37 #include "../include/libvsccli/core.h"
38 #include "../include/libvsccli/help.h"
39 #include "../include/libvsccli/option.h"
40 #include "command.h"
41 #include "option.h"
42
43 static int _initialized = FALSE;
44 static int _verbose_errors = FALSE;
45 static char *_histroy_path = FALSE;
46
47 char *_vsc_cli_name = NULL;
48 char *_vsc_cli_help = NULL;
49 const struct VscCliOptionInfo *_vsc_cli_option_infos = NULL;
50 const struct VscCliCommandInfo **_vsc_cli_command_infos = NULL;
51 int _vsc_cli_output_coloring = TRUE;
52 int _vsc_cli_interactive = FALSE;
53
54 static void
55 _quit (struct VscError *error, const struct VscCliCommandInfo *command_info,
56        const struct VscCliOption *option_list, void *user_data);
57
58 static const struct VscCliOptionInfo _quit_option_infos[] = {
59         VSC_CLI__NULL__OPTION_INFO,
60 };
61
62 const struct VscCliCommandInfo vsc_cli_quit_command_info = {
63         "quit",
64         "Quit interactive mode",
65         "",
66         _quit,
67         _quit_option_infos,
68 };
69
70 static void
71 _quit (struct VscError *error VSC__ATTR__UNUSED,
72        const struct VscCliCommandInfo *command_info VSC__ATTR__UNUSED,
73        const struct VscCliOption *option_list VSC__ATTR__UNUSED,
74        void *user_data VSC__ATTR__UNUSED)
75 {
76         _vsc_cli_interactive = FALSE;
77 }
78
79 static char *
80 _readline_command_generator (const char *text, int state)
81 {
82         static int last_index, text_length;
83         const struct VscCliCommandInfo *command_info = NULL;
84
85         if (state == 0) {
86                 last_index = 0;
87                 text_length = strlen (text);
88         }
89
90         while (_vsc_cli_command_infos[last_index] != NULL) {
91                 command_info = _vsc_cli_command_infos[last_index];
92                 ++last_index;
93
94                 if (strncmp (text, command_info->name, text_length) == 0) {
95                         return strdup (command_info->name);
96                 }
97         }
98
99         return NULL;
100 }
101
102 static char *
103 _readline_options_generator (const char *text, int state)
104 {
105         static int last_index, text_length;
106         static const struct VscCliCommandInfo *command_info = NULL;
107         const struct VscCliCommandInfo *help_command_info = NULL;
108         char *ptr = NULL;
109         char *command_name = NULL;
110         const struct VscCliOptionInfo *option_info;
111         char *result = NULL;
112
113         if (state == 0) {
114                 ptr = strchr (rl_line_buffer, ' ');
115
116                 if (ptr == NULL) {
117                         return NULL;
118                 }
119
120                 command_name = calloc (1, (ptr - rl_line_buffer) + 1);
121
122                 if (command_name == NULL) {
123                         return NULL;
124                 }
125
126                 memcpy (command_name, rl_line_buffer, ptr - rl_line_buffer);
127
128                 command_info = _vsc_cli_command_info_lookup (command_name);
129                 last_index = 0;
130                 text_length = strlen (text);
131
132                 vsc_free (&command_name);
133         }
134
135         if (command_info == NULL) {
136                 return NULL;
137         }
138
139         if (command_info->option_infos[0].long_name == NULL) {
140                 return NULL;
141         }
142
143         if (strcmp (command_info->name, "help") == 0) {
144                 while (_vsc_cli_command_infos[last_index] != NULL) {
145                         help_command_info = _vsc_cli_command_infos[last_index];
146                         ++last_index;
147
148                         if (strncmp (text, help_command_info->name, text_length) == 0) {
149                                 return strdup (help_command_info->name);
150                         }
151                 }
152         } else {
153                 while (command_info->option_infos[last_index].long_name != NULL) {
154                         option_info = &command_info->option_infos[last_index];
155                         ++last_index;
156
157                         if (! (option_info->flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
158                                 continue;
159                         }
160
161                         if (text_length > 2) {
162                                 if (strncmp (option_info->long_name, text + 2,
163                                              text_length - 2) != 0) {
164                                         continue;
165                                 }
166                         }
167
168                         result = calloc (strlen (option_info->long_name) + 3, 1);
169
170                         if (result == NULL) {
171                                 return NULL;
172                         }
173
174                         snprintf (result, strlen (option_info->long_name) + 3,  "--%s",
175                                   option_info->long_name);
176
177                         return result;
178                 }
179         }
180
181         return NULL;
182 }
183
184 static char **
185 _readline_completion (const char *text, int start, int end VSC__ATTR__UNUSED)
186 {
187         char **matches = (char **) NULL;
188
189         if (start == 0) {
190                 matches = rl_completion_matches(text, _readline_command_generator);
191         } else {
192                 matches = rl_completion_matches(text, _readline_options_generator);
193         }
194
195         return matches;
196 }
197
198 void
199 vsc_cli_init (struct VscError *error, const char *name, const char *help,
200               const char *histroy_path,
201               const struct VscCliOptionInfo *option_infos,
202               const struct VscCliCommandInfo **command_infos,
203               int output_coloring)
204 {
205         int i, k;
206         const struct VscCliCommandInfo *command_info;
207         const struct VscCliOptionInfo *option_info;
208
209         VSC__ASSERT (error != NULL);
210         VSC__ASSERT (! error->occured);
211         VSC__ASSERT (name != NULL);
212         VSC__ASSERT (help != NULL);
213         VSC__ASSERT (option_infos != NULL);
214         VSC__ASSERT (command_infos != NULL);
215
216         if (_initialized) {
217                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
218                              "Already initialized");
219                 return;
220         }
221
222         for (i = 0; command_infos[i] != NULL; i++) {
223                 command_info = command_infos[i];
224
225                 for (k = 0; command_info->option_infos[k].long_name != NULL; k++) {
226                         option_info = &command_info->option_infos[k];
227
228                         if (option_info->type == VSC_CLI__OPTION_TYPE__BOOLEAN &&
229                                 ! (option_info->flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
230                                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_ARGUMENT,
231                                              "Boolean options can not untagged");
232                                 return;
233                         }
234                 }
235         }
236
237         if (_vsc_cli_option_infos_lookup_untagged (option_infos, 0) != NULL) {
238                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_ARGUMENT,
239                              "Untagged options can not be used as direct options");
240                 return;
241         }
242
243         _vsc_cli_name = vsc_strdup (error, name);
244
245         if (error->occured) {
246                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
247                 goto failure;
248         }
249
250         _vsc_cli_help = vsc_strdup (error, help);
251
252         if (error->occured) {
253                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
254                 goto failure;
255         }
256
257         _vsc_cli_option_infos = option_infos;
258         _vsc_cli_command_infos = command_infos;
259         _vsc_cli_output_coloring = output_coloring;
260
261         rl_readline_name = name;
262         rl_attempted_completion_function = _readline_completion;
263
264         stifle_history (500);
265
266         if (histroy_path != NULL) {
267                 _histroy_path = vsc_strdup (error, histroy_path);
268
269                 if (error->occured) {
270                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
271                         goto failure;
272                 }
273
274                 read_history (histroy_path);
275         }
276
277         _initialized = TRUE;
278
279         return;
280
281 failure:
282         vsc_free (&_vsc_cli_name);
283         vsc_free (&_vsc_cli_help);
284         vsc_free (&_histroy_path);
285 }
286
287 void
288 vsc_cli_cleanup (void)
289 {
290         VSC__ASSERT (_initialized);
291
292         if (! _initialized) {
293                 return;
294         }
295
296         if (_histroy_path != NULL) {
297                 write_history (_histroy_path);
298         }
299
300         vsc_free (&_vsc_cli_name);
301         vsc_free (&_vsc_cli_help);
302         vsc_free (&_histroy_path);
303
304         _vsc_cli_option_infos = NULL;
305         _vsc_cli_command_infos = NULL;
306         _vsc_cli_output_coloring = TRUE;
307         _vsc_cli_interactive = FALSE;
308
309         _initialized = FALSE;
310 }
311
312 void
313 vsc_cli_error_fprint (struct VscError *error, FILE *stream)
314 {
315         static const char *prefix_colored = "\033[01;31mError\033[0m";
316         static const char *prefix = "Error";
317
318         if (_verbose_errors) {
319                 vsc_error_fprint (error, stream);
320         } else {
321                 if (error->message != NULL) {
322                         if (error->code == VSC__ERROR_CODE__XMLRPC_ERROR) {
323                                 fprintf (stream, "%s: XML-RPC: %s\n",
324                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
325                                          error->message);
326                         } else if (error->code == VSC__ERROR_CODE__REMOTE_ERROR) {
327                                 fprintf (stream, "%s: Remote: %s\n",
328                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
329                                          error->message);
330                         } else {
331                                 fprintf (stream, "%s: %s\n",
332                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
333                                          error->message);
334                         }
335                 } else {
336                         fprintf (stream, "%s: %s\n",
337                                  _vsc_cli_output_coloring ? prefix_colored : prefix,
338                                  vsc_error_string (error->code));
339                 }
340         }
341 }
342
343 struct VscCliOption * /* option_list */
344 vsc_cli_parse_option_list (struct VscError *error, const char *input,
345                            const char **input_tail)
346 {
347         int option_parsed;
348         struct VscCliOption option;
349         struct VscCliOption *option_list = NULL;
350
351         VSC__ASSERT (error != NULL);
352         VSC__ASSERT (! error->occured);
353         VSC__ASSERT (input != NULL);
354         VSC__ASSERT (input_tail != NULL);
355
356         *input_tail = input;
357
358         while (*input_tail != NULL && **input_tail != '\0') {
359                 option_parsed =
360                    _vsc_cli_option_parse (error, _vsc_cli_option_infos, -1, NULL,
361                                           *input_tail, input_tail, &option);
362
363                 if (error->occured) {
364                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
365                         goto failure;
366                 }
367
368                 if (option_parsed) {
369                         if (strcmp (option.info->long_name,
370                                     VSC_CLI__DEBUG_LEVEL__OPTION_INFO__LONG_NAME) == 0) {
371                                 vsc_debug_set_level (option.number);
372                         } else if (strcmp (option.info->long_name,
373                                     VSC_CLI__VERBOSE_ERRORS__OPTION_INFO__LONG_NAME) == 0) {
374                                 _verbose_errors = TRUE;
375                         } else if (strcmp (option.info->long_name,
376                                     VSC_CLI__NO_COLOR__OPTION_INFO__LONG_NAME) == 0) {
377                                 _vsc_cli_output_coloring = FALSE;
378                         }
379
380                         _vsc_cli_option_list_append_clone (error, &option_list, &option);
381                         vsc_free (&option.string);
382
383                         if (error->occured) {
384                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
385                                 goto failure;
386                         }
387                 } else {
388                         break;
389                 }
390         }
391
392         return option_list;
393
394 failure:
395         vsc_cli_option_list_free (&option_list);
396
397         return NULL;
398 }
399
400 struct VscCliCommand * /* command_list */
401 vsc_cli_parse_command_list (struct VscError *error, const char *input,
402                             const char **input_tail)
403 {
404         int command_parsed;
405         struct VscCliCommand command;
406         struct VscCliCommand *command_list = NULL;
407
408         VSC__ASSERT (error != NULL);
409         VSC__ASSERT (! error->occured);
410         VSC__ASSERT (input != NULL);
411         VSC__ASSERT (input_tail != NULL);
412
413         *input_tail = input;
414
415         while (*input_tail != NULL && **input_tail != '\0') {
416                 command_parsed =
417                    vsc_cli_command_parse (error, *input_tail, input_tail, &command);
418
419                 if (error->occured) {
420                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
421                         goto failure;
422                 }
423
424                 if (command_parsed) {
425                         _vsc_cli_command_list_append_clone (error, &command_list, &command);
426                         vsc_cli_option_list_free (&command.option_list);
427
428                         if (error->occured) {
429                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
430                                 goto failure;
431                         }
432                 } else {
433                         break;
434                 }
435         }
436
437         return command_list;
438
439 failure:
440         vsc_cli_command_list_free (&command_list);
441
442         return NULL;
443 }
444
445 void
446 vsc_cli_execute_command_list (struct VscError *error,
447                               const struct VscCliCommand *command_list,
448                               void *user_data)
449 {
450         struct VscCliCommand *command;
451
452         VSC__ASSERT (error != NULL);
453         VSC__ASSERT (! error->occured);
454         VSC__ASSERT (command_list != NULL);
455
456         for (command = (struct VscCliCommand *) command_list; command != NULL;
457              command = command->next) {
458                 command->info->function (error, command->info, command->option_list,
459                                          user_data);
460
461                 if (error->occured) {
462                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
463                         return;
464                 }
465         }
466 }
467
468 void
469 vsc_cli_enter_interactive (VscCliErrorFunction error_function,
470                            VscCliPromptFunction prompt_function,
471                            void *user_data)
472 {
473         struct VscError error;
474         char *input = NULL;
475         char prompt[256];
476         const char *input_tail = NULL;
477         struct VscCliCommand *command_list = NULL;
478
479         VSC__ASSERT (error_function != NULL);
480         VSC__ASSERT (prompt_function != NULL);
481         VSC__ASSERT (_vsc_cli_command_info_lookup ("quit") != NULL);
482
483         vsc_error_init (&error);
484
485         _vsc_cli_interactive = TRUE;
486
487         while (_vsc_cli_interactive) {
488                 prompt_function (&error, prompt, 256, user_data);
489
490                 if (error.occured) {
491                         error_function (&error, user_data);
492                         goto outer_cleanup;
493                 }
494
495                 input = readline (prompt);
496
497                 if (input == NULL) {
498                         goto outer_cleanup;
499                 }
500
501                 if (*input == '\0') {
502                         goto inner_cleanup;
503                 }
504
505                 add_history (input);
506
507                 VSC__DEBUG2 ("parsing '%s'", input);
508
509                 command_list = vsc_cli_parse_command_list
510                                   (&error, input, &input_tail);
511
512                 if (error.occured) {
513                         error_function (&error, user_data);
514                         goto inner_cleanup;
515                 }
516
517                 if (command_list != NULL) {
518                         VSC__DEBUG2 ("executing '%s'", input);
519
520                         vsc_cli_execute_command_list (&error, command_list, user_data);
521
522                         if (error.occured) {
523                                 error_function (&error, user_data);
524                                 goto inner_cleanup;
525                         }
526                 }
527
528 inner_cleanup:
529                 vsc_free (&input);
530                 vsc_cli_command_list_free (&command_list);
531                 vsc_error_cleanup (&error);
532                 vsc_error_init (&error);
533         }
534
535 outer_cleanup:
536         vsc_error_cleanup (&error);
537
538         _vsc_cli_interactive = FALSE;
539 }