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