2 * core.c: Library for interactive commandline interfaces
4 * Copyright (C) 2009 Matthias Bolte <matthias.bolte@googlemail.com>
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.
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.
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
27 #include <readline/readline.h>
28 #include <readline/history.h>
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>
36 #include "../include/libvsccli/command.h"
37 #include "../include/libvsccli/core.h"
38 #include "../include/libvsccli/help.h"
39 #include "../include/libvsccli/option.h"
43 static int _initialized = FALSE;
44 static int _verbose_errors = FALSE;
45 static char *_histroy_path = FALSE;
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;
55 _quit (struct VscError *error, const struct VscCliCommandInfo *command_info,
56 const struct VscCliOption *option_list, void *user_data);
58 static const struct VscCliOptionInfo _quit_option_infos[] = {
59 VSC_CLI__NULL__OPTION_INFO,
62 const struct VscCliCommandInfo vsc_cli_quit_command_info = {
64 "Quit interactive mode",
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)
76 _vsc_cli_interactive = FALSE;
80 _readline_command_generator (const char *text, int state)
82 static int last_index, text_length;
83 const struct VscCliCommandInfo *command_info = NULL;
87 text_length = strlen (text);
90 while (_vsc_cli_command_infos[last_index] != NULL) {
91 command_info = _vsc_cli_command_infos[last_index];
94 if (strncmp (text, command_info->name, text_length) == 0) {
95 return strdup (command_info->name);
103 _readline_options_generator (const char *text, int state)
105 static int last_index, text_length;
106 static const struct VscCliCommandInfo *command_info = NULL;
107 const struct VscCliCommandInfo *help_command_info = NULL;
109 char *command_name = NULL;
110 const struct VscCliOptionInfo *option_info;
114 ptr = strchr (rl_line_buffer, ' ');
120 command_name = calloc (1, (ptr - rl_line_buffer) + 1);
122 if (command_name == NULL) {
126 memcpy (command_name, rl_line_buffer, ptr - rl_line_buffer);
128 command_info = _vsc_cli_command_info_lookup (command_name);
130 text_length = strlen (text);
132 vsc_free (&command_name);
135 if (command_info == NULL) {
139 if (command_info->option_infos[0].long_name == NULL) {
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];
148 if (strncmp (text, help_command_info->name, text_length) == 0) {
149 return strdup (help_command_info->name);
153 while (command_info->option_infos[last_index].long_name != NULL) {
154 option_info = &command_info->option_infos[last_index];
157 if (! (option_info->flags & VSC_CLI__OPTION_FLAG__TAGGED)) {
161 if (text_length > 2) {
162 if (strncmp (option_info->long_name, text + 2,
163 text_length - 2) != 0) {
168 result = calloc (strlen (option_info->long_name) + 3, 1);
170 if (result == NULL) {
174 snprintf (result, strlen (option_info->long_name) + 3, "--%s",
175 option_info->long_name);
185 _readline_completion (const char *text, int start, int end VSC__ATTR__UNUSED)
187 char **matches = (char **) NULL;
190 matches = rl_completion_matches(text, _readline_command_generator);
192 matches = rl_completion_matches(text, _readline_options_generator);
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,
206 const struct VscCliCommandInfo *command_info;
207 const struct VscCliOptionInfo *option_info;
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);
217 VSC__ERROR1 (error, VSC__ERROR_CODE__INVALID_CALL,
218 "Already initialized");
222 for (i = 0; command_infos[i] != NULL; i++) {
223 command_info = command_infos[i];
225 for (k = 0; command_info->option_infos[k].long_name != NULL; k++) {
226 option_info = &command_info->option_infos[k];
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");
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");
243 _vsc_cli_name = vsc_strdup (error, name);
245 if (error->occured) {
246 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
250 _vsc_cli_help = vsc_strdup (error, help);
252 if (error->occured) {
253 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
257 _vsc_cli_option_infos = option_infos;
258 _vsc_cli_command_infos = command_infos;
259 _vsc_cli_output_coloring = output_coloring;
261 rl_readline_name = name;
262 rl_attempted_completion_function = _readline_completion;
264 stifle_history (500);
266 if (histroy_path != NULL) {
267 _histroy_path = vsc_strdup (error, histroy_path);
269 if (error->occured) {
270 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
274 read_history (histroy_path);
282 vsc_free (&_vsc_cli_name);
283 vsc_free (&_vsc_cli_help);
284 vsc_free (&_histroy_path);
288 vsc_cli_cleanup (void)
290 VSC__ASSERT (_initialized);
292 if (! _initialized) {
296 if (_histroy_path != NULL) {
297 write_history (_histroy_path);
300 vsc_free (&_vsc_cli_name);
301 vsc_free (&_vsc_cli_help);
302 vsc_free (&_histroy_path);
304 _vsc_cli_option_infos = NULL;
305 _vsc_cli_command_infos = NULL;
306 _vsc_cli_output_coloring = TRUE;
307 _vsc_cli_interactive = FALSE;
309 _initialized = FALSE;
313 vsc_cli_error_fprint (struct VscError *error, FILE *stream)
315 static const char *prefix_colored = "\033[01;31mError\033[0m";
316 static const char *prefix = "Error";
318 if (_verbose_errors) {
319 vsc_error_fprint (error, stream);
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,
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,
331 fprintf (stream, "%s: %s\n",
332 _vsc_cli_output_coloring ? prefix_colored : prefix,
336 fprintf (stream, "%s: %s\n",
337 _vsc_cli_output_coloring ? prefix_colored : prefix,
338 vsc_error_string (error->code));
343 struct VscCliOption * /* option_list */
344 vsc_cli_parse_option_list (struct VscError *error, const char *input,
345 const char **input_tail)
348 struct VscCliOption option;
349 struct VscCliOption *option_list = NULL;
351 VSC__ASSERT (error != NULL);
352 VSC__ASSERT (! error->occured);
353 VSC__ASSERT (input != NULL);
354 VSC__ASSERT (input_tail != NULL);
358 while (*input_tail != NULL && **input_tail != '\0') {
360 _vsc_cli_option_parse (error, _vsc_cli_option_infos, -1, NULL,
361 *input_tail, input_tail, &option);
363 if (error->occured) {
364 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
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;
380 _vsc_cli_option_list_append (error, &option_list, &option);
381 vsc_free (&option.string);
383 if (error->occured) {
384 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
395 vsc_cli_option_list_free (&option_list);
400 struct VscCliCommand * /* command_list */
401 vsc_cli_parse_command_list (struct VscError *error, const char *input,
402 const char **input_tail)
405 struct VscCliCommand command;
406 struct VscCliCommand *command_list = NULL;
408 VSC__ASSERT (error != NULL);
409 VSC__ASSERT (! error->occured);
410 VSC__ASSERT (input != NULL);
411 VSC__ASSERT (input_tail != NULL);
415 while (*input_tail != NULL && **input_tail != '\0') {
417 vsc_cli_command_parse (error, *input_tail, input_tail, &command);
419 if (error->occured) {
420 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
424 if (command_parsed) {
425 _vsc_cli_command_list_append (error, &command_list, &command);
426 vsc_cli_option_list_free (&command.option_list);
428 if (error->occured) {
429 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
440 vsc_cli_command_list_free (&command_list);
446 vsc_cli_execute_command_list (struct VscError *error,
447 const struct VscCliCommand *command_list,
450 struct VscCliCommand *command;
452 VSC__ASSERT (error != NULL);
453 VSC__ASSERT (! error->occured);
454 VSC__ASSERT (command_list != NULL);
456 for (command = (struct VscCliCommand *) command_list; command != NULL;
457 command = command->next) {
458 command->info->function (error, command->info, command->option_list,
461 if (error->occured) {
462 VSC__APPEND_ERROR0 (error, VSC__ERROR_CODE__TRACE);
469 vsc_cli_enter_interactive (VscCliErrorFunction error_function,
470 VscCliPromptFunction prompt_function,
473 struct VscError error;
476 const char *input_tail = NULL;
477 struct VscCliCommand *command_list = NULL;
479 VSC__ASSERT (error_function != NULL);
480 VSC__ASSERT (prompt_function != NULL);
481 VSC__ASSERT (_vsc_cli_command_info_lookup ("quit") != NULL);
483 vsc_error_init (&error);
485 _vsc_cli_interactive = TRUE;
487 while (_vsc_cli_interactive) {
488 prompt_function (&error, prompt, 256, user_data);
491 error_function (&error, user_data);
495 input = readline (prompt);
501 if (*input == '\0') {
507 VSC__DEBUG2 ("parsing '%s'", input);
509 command_list = vsc_cli_parse_command_list
510 (&error, input, &input_tail);
513 error_function (&error, user_data);
517 if (command_list != NULL) {
518 VSC__DEBUG2 ("executing '%s'", input);
520 vsc_cli_execute_command_list (&error, command_list, user_data);
523 error_function (&error, user_data);
530 vsc_cli_command_list_free (&command_list);
531 vsc_error_cleanup (&error);
532 vsc_error_init (&error);
536 vsc_error_cleanup (&error);
538 _vsc_cli_interactive = FALSE;