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