e56e37a62695d63b218d68792089523dd2e50e5e
[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         { -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 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->type == VSC_CLI__OPTION_TYPE__DATA) {
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         VSC__ASSERT (error != NULL);
199         VSC__ASSERT (! error->occured);
200         VSC__ASSERT (name != NULL);
201         VSC__ASSERT (help != NULL);
202         VSC__ASSERT (option_infos != NULL);
203         VSC__ASSERT (command_infos != NULL);
204
205         if (_initialized) {
206                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
207                              "Already initialized");
208                 return;
209         }
210
211         if (_vsc_cli_option_infos_lookup_data (option_infos, 0) != 0) {
212                 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_ARGUMENT,
213                              "Data options can not be used as direct options");
214                 return;
215         }
216
217         _vsc_cli_name = name;
218         _vsc_cli_help = help;
219         _vsc_cli_option_infos = option_infos;
220         _vsc_cli_command_infos = command_infos;
221         _vsc_cli_output_coloring = output_coloring;
222
223         rl_readline_name = name;
224         rl_attempted_completion_function = _readline_completion;
225
226         stifle_history (500);
227
228         _initialized = TRUE;
229 }
230
231 void
232 vsc_cli_cleanup (void)
233 {
234         VSC__ASSERT (_initialized);
235
236         if (! _initialized) {
237                 return;
238         }
239
240         _vsc_cli_name = NULL;
241         _vsc_cli_help = NULL;
242         _vsc_cli_option_infos = NULL;
243         _vsc_cli_command_infos = NULL;
244         _vsc_cli_output_coloring = TRUE;
245         _vsc_cli_interactive = FALSE;
246
247         _initialized = FALSE;
248 }
249
250 void
251 vsc_cli_error_fprint (struct VscError *error, FILE *stream)
252 {
253         static const char *prefix_colored = "\033[01;31mError\033[0m";
254         static const char *prefix = "Error";
255
256         if (_verbose_errors) {
257                 vsc_error_fprint (error, stream);
258         } else {
259                 if (error->message != NULL) {
260                         if (error->code == VSC__ERROR_CODE__REMOTE_ERROR) {
261                                 fprintf (stream, "%s: Remote: %s\n",
262                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
263                                          error->message);
264                         } else {
265                                 fprintf (stream, "%s: %s\n",
266                                          _vsc_cli_output_coloring ? prefix_colored : prefix,
267                                          error->message);
268                         }
269                 } else {
270                         fprintf (stream, "%s: %s\n",
271                                  _vsc_cli_output_coloring ? prefix_colored : prefix,
272                                  vsc_error_string(error->code));
273                 }
274         }
275 }
276
277 struct VscCliOption * /* option_list */
278 vsc_cli_parse_option_list (struct VscError *error, const char *input,
279                            const char **input_tail)
280 {
281         int option_parsed;
282         struct VscCliOption option;
283         struct VscCliOption *option_list = NULL;
284
285         VSC__ASSERT (error != NULL);
286         VSC__ASSERT (! error->occured);
287         VSC__ASSERT (input != NULL);
288         VSC__ASSERT (input_tail != NULL);
289
290         *input_tail = input;
291
292         while (*input_tail != NULL && **input_tail != '\0') {
293                 option_parsed =
294                    _vsc_cli_option_parse (error, _vsc_cli_option_infos, -1, NULL,
295                                           *input_tail, input_tail, &option);
296
297                 if (error->occured) {
298                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
299                         goto failure;
300                 }
301
302                 if (option_parsed) {
303                         if (strcmp (option.info->long_name,
304                                     VSC_CLI__VERBOSE_ERRORS__OPTION_INFO__LONG_NAME) == 0) {
305                                 _verbose_errors = TRUE;
306                         } else if (strcmp (option.info->long_name,
307                                     VSC_CLI__NO_COLOR__OPTION_INFO__LONG_NAME) == 0) {
308                                 _vsc_cli_output_coloring = FALSE;
309                         }
310
311                         _vsc_cli_option_list_append (error, &option_list, &option);
312                         vsc_free (&option.string);
313
314                         if (error->occured) {
315                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
316                                 goto failure;
317                         }
318                 } else {
319                         break;
320                 }
321         }
322
323         return option_list;
324
325 failure:
326         vsc_cli_option_list_free (&option_list);
327
328         return NULL;
329 }
330
331 struct VscCliCommand * /* command_list */
332 vsc_cli_parse_command_list (struct VscError *error, const char *input,
333                             const char **input_tail)
334 {
335         int command_parsed;
336         struct VscCliCommand command;
337         struct VscCliCommand *command_list = NULL;
338
339         VSC__ASSERT (error != NULL);
340         VSC__ASSERT (! error->occured);
341         VSC__ASSERT (input != NULL);
342         VSC__ASSERT (input_tail != NULL);
343
344         *input_tail = input;
345
346         while (*input_tail != NULL && **input_tail != '\0') {
347                 command_parsed =
348                    vsc_cli_command_parse (error, *input_tail, input_tail, &command);
349
350                 if (error->occured) {
351                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
352                         goto failure;
353                 }
354
355                 if (command_parsed) {
356                         _vsc_cli_command_list_append (error, &command_list, &command);
357                         vsc_cli_option_list_free (&command.option_list);
358
359                         if (error->occured) {
360                                 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
361                                 goto failure;
362                         }
363                 } else {
364                         break;
365                 }
366         }
367
368         return command_list;
369
370 failure:
371         vsc_cli_command_list_free (&command_list);
372
373         return NULL;
374 }
375
376 void
377 vsc_cli_run_command_list (struct VscError *error,
378                           const struct VscCliCommand *command_list,
379                           void *user_data)
380 {
381         struct VscCliCommand *command;
382
383         VSC__ASSERT (error != NULL);
384         VSC__ASSERT (! error->occured);
385         VSC__ASSERT (command_list != NULL);
386
387         for (command = (struct VscCliCommand *) command_list; command != NULL;
388              command = command->next) {
389                 command->info->function (error, command->info, command->option_list,
390                                          user_data);
391
392                 if (error->occured) {
393                         VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
394                         return;
395                 }
396         }
397 }
398
399 void
400 vsc_cli_enter_interactive (VscCliErrorFunction error_function,
401                            VscCliPromptFunction prompt_function,
402                            void *user_data)
403 {
404         struct VscError error;
405         char *input = NULL;
406         char prompt[256];
407         const char *input_tail = NULL;
408         struct VscCliCommand *command_list = NULL;
409
410         VSC__ASSERT (error_function != NULL);
411         VSC__ASSERT (prompt_function != NULL);
412         VSC__ASSERT (_vsc_cli_command_info_lookup ("quit") != NULL);
413
414         vsc_error_init (&error);
415
416         _vsc_cli_interactive = TRUE;
417
418         while (_vsc_cli_interactive) {
419                 prompt_function (&error, prompt, 256, user_data);
420
421                 if (error.occured) {
422                         error_function (&error, user_data);
423                         goto outer_cleanup;
424                 }
425
426                 input = readline (prompt);
427
428                 if (input == NULL) {
429                         goto outer_cleanup;
430                 }
431
432                 if (*input == '\0') {
433                         goto inner_cleanup;
434                 }
435
436                 add_history (input);
437
438                 command_list = vsc_cli_parse_command_list
439                                   (&error, input, &input_tail);
440
441                 if (error.occured) {
442                         error_function (&error, user_data);
443                         goto inner_cleanup;
444                 }
445
446                 if (command_list != NULL) {
447                         vsc_cli_run_command_list (&error, command_list, user_data);
448
449                         if (error.occured) {
450                                 error_function (&error, user_data);
451                                 goto inner_cleanup;
452                         }
453                 }
454
455 inner_cleanup:
456                 vsc_free (&input);
457                 vsc_cli_command_list_free (&command_list);
458                 vsc_error_cleanup (&error);
459                 vsc_error_init (&error);
460         }
461
462 outer_cleanup:
463         vsc_error_cleanup (&error);
464
465         _vsc_cli_interactive = FALSE;
466 }