Add commercialcredit command
[shigofumi.git] / src / completion.c
bloba54bf74c38c813a8b9265a0e7adf2f8ce7a94f53
1 #define _XOPEN_SOURCE 500
2 #include <stdio.h>
3 #include <string.h>
4 #include <readline/readline.h>
5 #include <readline/history.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <stdlib.h>
9 #include <termios.h>
10 #include <signal.h>
11 #include <langinfo.h> /* nl_langinfo(3) */
12 #include <sys/types.h> /* regcomp(3) */
13 #include <regex.h> /* regcomp(3) */
15 #include "shigofumi.h"
16 #include <isds.h>
17 #include "completion.h"
19 static _Bool progress_started = 0;
20 static _Bool progress_abort_requested = 0;
21 static char *upload_current_formated = NULL;
22 static char *upload_total_formated = NULL;
23 static char *download_current_formated = NULL;
24 static char *download_total_formated = NULL;
26 /* Generates possible completions for base commands set
27 * @text is partial user input word
28 * @state is 0 for first completion attempt, non-zero otherwise
29 * @return next suggested complete word or NULL if no possibility */
30 static char *shi_command_generator(const char *text, int state) {
31 static size_t text_length, index;
32 char *command_name;
34 if (!state) {
35 text_length = strlen(text);
36 index = 0;
39 if (!commands) return NULL;
41 while ((command_name = (*commands)[index++].name)) {
42 if (!strncmp(command_name, text, text_length))
43 return strdup(command_name);
46 return NULL;
50 /* Generates possible message ID completions
51 * @text_locale is partial user input word in locale encoding
52 * @state is 0 for first completion attempt, non-zero otherwise
53 * @return next suggested complete word or NULL if no possibility */
54 static char *shi_msgid_generator(const char *text_locale, int state) {
55 static char *text = NULL;
56 static size_t text_length;
57 static struct isds_list *item;
58 char *id;
60 if (!state) {
61 free(text);
62 text = locale2utf8(text_locale);
63 if (text) text_length = strlen(text);
64 item = messages;
67 if (text) {
68 for (; item; item = item->next) {
69 if (item->data && ((struct isds_message *)item->data)->envelope) {
70 id = ((struct isds_message *)item->data)->envelope->dmID;
71 if (id && !strncmp(id, text, text_length)) {
72 item = item->next;
73 return utf82locale(id);
79 return NULL;
83 /* Generates possible document ID completions
84 * @text is partial user input word
85 * @state is 0 for first completion attempt, non-zero otherwise
86 * @return next suggested complete word or NULL if no possibility
87 * TODO: Implement match on string representation */
88 static char *shi_docid_generator(const char *text, int state) {
89 static struct isds_list *item;
90 static int order;
91 char *document_id;
93 if (!state) {
94 if (message)
95 item = message->documents;
96 else
97 item = NULL;
98 order = 0;
101 for (; item; item = item->next) {
102 if (item->data) {
103 item = item->next;
104 order++;
105 document_id = NULL;
106 shi_asprintf(&document_id, "%d", order);
107 return document_id;
111 return NULL;
115 /* Generates possible box ID completions
116 * @text_locale is partial user input word in locale encoding
117 * @state is 0 for first completion attempt, non-zero otherwise
118 * @return next suggested complete word or NULL if no possibility */
119 static char *shi_boxid_generator(const char *text_locale, int state) {
120 static char *text = NULL;
121 static size_t text_length;
122 static struct isds_list *item;
123 char *id;
125 if (!state) {
126 free(text);
127 text = locale2utf8(text_locale);
128 if (text) text_length = strlen(text);
129 item = boxes;
132 if (text) {
133 for (; item; item = item->next) {
134 if (item->data && ((struct isds_DbOwnerInfo *)item->data)->dbID) {
135 id = ((struct isds_DbOwnerInfo *)item->data)->dbID;
136 if (id && !strncmp(id, text, text_length)) {
137 item = item->next;
138 return utf82locale(id);
144 return NULL;
148 struct command *find_command(const char *text) {
149 int index = 0;
150 if (!text || !commands) return NULL;
152 for (index = 0; (*commands)[index].name; index++) {
153 if (!strcmp((*commands)[index].name, text))
154 return &(*commands)[index];
157 return NULL;
161 arg_type find_arg_type(const char *line) {
162 int index;
163 size_t length;
165 if (!line || !commands) return ARGTYPE_NONE;
167 for (index = 0; (*commands)[index].name; index++) {
168 length = strlen((*commands)[index].name);
169 if (rl_point >= length &&
170 !strncmp(line, (*commands)[index].name, length) &&
171 isspace(line[length]))
172 return (*commands)[index].arg;
175 return ARGTYPE_NONE;
179 static char **shi_completion_none(const char *text, int start, int end) {
180 rl_attempted_completion_over = 1;
181 return NULL;
184 /* Readline completion hook
185 * @text is a word to complete
186 * @start is index of begining of @text in rl_line_buffer (counts from 0)
187 * @end is index of cursor in rl_line_buffer (character after end of @text)
188 * @return dynamicly allocated array of possible completions or NULL if none
189 * @side-effect set rl_attempted_completion_over to disable (0) file name
190 * completion */
191 static char **shi_completion_command(const char *text, int start, int end) {
192 rl_attempted_completion_over = 1;
194 #if ENABLE_DEBUG
195 fprintf(stderr, "DEBUG: shi_completion_command(): "
196 "text=<%s>, start=%d, end=%d, rl_line_buffer=<%s>, "
197 "rl_completion_found_quote=%d, rl_completion_quote_character=<%c> "
198 "rl_filename_quoting_desired=%d, rl_filename_quoting_function=%p "
199 "(shi_quote_filename=%p)\n",
200 text, start, end, rl_line_buffer,
201 rl_completion_found_quote, rl_completion_quote_character,
202 rl_filename_quoting_desired, rl_filename_quoting_function,
203 shi_quote_filename);
204 #endif
206 if (!start)
207 /* Command */
208 return rl_completion_matches(text, shi_command_generator);
210 /* Command argument */
211 switch (find_arg_type(rl_line_buffer)) {
212 case ARGTYPE_COMMAND:
213 return rl_completion_matches(text, shi_command_generator);
214 case ARGTYPE_FILE:
215 rl_attempted_completion_over = 0;
216 break;
217 case ARGTYPE_MSGID:
218 return rl_completion_matches(text, shi_msgid_generator);
219 case ARGTYPE_DOCID:
220 return rl_completion_matches(text, shi_docid_generator);
221 case ARGTYPE_BOXID:
222 return rl_completion_matches(text, shi_boxid_generator);
223 default:
224 rl_attempted_completion_over = 1;
226 return NULL;
230 /* Compares two command names */
231 int commandcmp(const void *a, const void *b) {
232 if (!a) return 1;
233 return strcoll(((struct command *)a)->name, ((struct command *)b)->name);
237 /* Make list of currently available commands */
238 static int build_command_list(const struct command unsorted_commands[]) {
239 int count, i;
241 for (count = 0; unsorted_commands[count].name; count++);
243 zfree(commands);
244 commands = calloc(count + 1, sizeof(struct command));
245 if (!commands) {
246 fprintf(stderr,
247 _("Fatal error: Non enough memory to sort commands\n"));
248 return -1;
251 for (i = 0; i < count; i++) {
252 (*commands)[i].name = unsorted_commands[i].name;
253 (*commands)[i].function = unsorted_commands[i].function;
254 (*commands)[i].description = unsorted_commands[i].description;
255 (*commands)[i].usage = unsorted_commands[i].usage;
256 (*commands)[i].arg = unsorted_commands[i].arg;
259 qsort(*commands, count, sizeof(struct command), commandcmp);
261 return 0;
265 /* Switch completion function */
266 int select_completion(const completion_type completion) {
267 switch (completion) {
268 case COMPL_NONE:
269 rl_attempted_completion_function = shi_completion_none;
270 break;
271 case COMPL_COMMAND:
272 if (build_command_list(base_commands)) return -1;
273 rl_attempted_completion_function = shi_completion_command;
274 break;
275 case COMPL_MSG:
276 if (build_command_list(message_commands)) return -1;
277 rl_attempted_completion_function = shi_completion_command;
278 break;
279 case COMPL_LIST:
280 if (build_command_list(list_commands)) return -1;
281 rl_attempted_completion_function = shi_completion_command;
282 break;
284 return 0;
288 /* Free list of chars recursively */
289 void argv_free(char **argv) {
290 if (argv) {
291 for (char **arg = argv; *arg; arg++)
292 free(*arg);
293 free(argv);
298 #define ESCAPER '\\'
299 /* Decides whether character at @index offset of @text is quoted */
300 int shi_char_is_quoted(char *text, int index) {
301 int i;
302 _Bool escaped = 0;
304 #if ENABLE_DEBUG
305 fprintf(stderr, "shi_char_is_quoted(text=%s, index=%d)\n", text, index);
306 #endif
307 if (!text || index < 0) return 0;
309 for (i = 0; i <= index; i++) {
310 if (text[i] == ESCAPER && !escaped) {
311 escaped = 1;
312 continue;
314 escaped = 0;
317 return escaped;
321 /* Escapes file name */
322 char *shi_quote_filename(char *text, int match_type, char *quote_pointer) {
323 char *quoted_text = NULL;
324 int i;
326 #if ENABLE_DEBUG
327 fprintf(stderr,
328 "shi_quote_filename(text=%s, match_type=%d, quote_pointer='%c')\n",
329 text, match_type, (quote_pointer)?*quote_pointer:0);
330 #endif
331 if (!text) return NULL;
333 quoted_text = malloc(strlen(text) * 2 + 1);
334 if (!quoted_text) return strdup(text);
336 for (i = 0; *text; text++, i++) {
337 if (*text == ESCAPER) quoted_text[i++] = ESCAPER;
338 if (isspace(*text)) quoted_text[i++] = ESCAPER;
339 quoted_text[i] = *text;
341 quoted_text[i] = '\0';
343 return quoted_text;
347 /* Deescapes file name */
348 char *shi_dequote_filename(char *text, int quote_char) {
349 _Bool escaped;
350 char *unquoted_text = NULL;
351 int i;
353 #if ENABLE_DEBUG
354 fprintf(stderr,
355 "shi_dequote_filename(text=%s, quote_char=%d)\n",
356 text, quote_char);
357 #endif
358 if (!text) return NULL;
360 unquoted_text = malloc(strlen(text) + 1);
361 if (!unquoted_text) return strdup(text);
363 for (escaped = 0, i = 0; *text; text++) {
364 if (*text == ESCAPER && !escaped) {
365 escaped = 1;
366 continue;
368 escaped = 0;
370 unquoted_text[i++] = *text;
372 unquoted_text[i] = '\0';
374 return unquoted_text;
378 #define MAX_TOKENS 256
379 #define PIPE '|'
380 #define QUOTE '"'
381 /* Split string into tokens. Backslash escapes following space or double quote.
382 * Double quote escapes white spaces until next double quote.
383 * @command_line is line to parse
384 * @argc outputs number of parsed arguments
385 * @shell is optional automatically reallocated shell command following pipe
386 * in @command_line
387 * @return NULL-terminated array of tokens or NULL in case of error */
388 char **tokenize(const char *command_line, int *argc, char **shell) {
389 char **argv = NULL;
390 const char *start, *end;
391 _Bool escaped = 0, quoted = 0;
392 char *target;
393 char *home = getenv("HOME");
395 if (!argc) return NULL;
396 *argc = 0;
398 if (!command_line) return NULL;
400 if (!shell) return NULL;
401 zfree(*shell);
403 argv = calloc(MAX_TOKENS, sizeof(*argv));
404 if (!argv) {
405 fprintf(stderr, _("Not enough memory to tokenize command line\n"));
406 goto error;
409 for (start = command_line; *start; start++) {
410 if (isspace(*start)) continue;
411 if (*start == PIPE) break;
413 if (*argc >= MAX_TOKENS - 1) {
414 fprintf(stderr, _("To much arguments\n"));
415 goto error;
418 /* Locate token boundaries */
419 escaped = quoted = 0;
420 for (end = start; *end; end++) {
421 if (*end == ESCAPER && !escaped) {
422 escaped = 1;
423 continue;
425 if (*end == QUOTE && !escaped) {
426 quoted = !quoted;
427 continue;
429 if ((isspace(*end) || *end == PIPE ) && !escaped && !quoted) break;
430 if (escaped) escaped = 0;
433 /* Allocate memory and expand first tilde to $HOME */
434 if (*start == '~' && home) {
435 size_t home_length = strlen(home);
436 start++;
437 argv[*argc] = malloc(home_length + end-start + 1);
438 if (argv[*argc]) {
439 strcpy(argv[*argc], home);
440 target = argv[*argc] + home_length;
442 } else {
443 argv[*argc] = malloc(end-start + 1);
444 target = argv[*argc];
446 if (!argv[*argc]) {
447 fprintf(stderr, _("Not enough memory to tokenize command line\n"));
448 goto error;
451 /* Copy unquoted token */
452 for (escaped = quoted = 0; start < end; start++) {
453 if (*start == ESCAPER && !escaped) {
454 escaped = 1;
455 continue;
457 if (*start == QUOTE && !escaped) {
458 quoted = !quoted;
459 continue;
461 escaped = 0;
462 *target++ = *start;
464 *target = '\0';
466 (*argc)++;
467 start = end;
468 if (!*start || *start == PIPE) break;
471 /* Copy shell command */
472 if (*start == PIPE) {
473 start++;
474 size_t length = strlen(start);
476 if (length > 0) {
477 *shell = malloc(length + 1);
478 if (!*shell) goto error;
479 strcpy(*shell, start);
483 return argv;
485 error:
486 argv_free(argv);
487 zfree(shell);
488 return NULL;
491 #undef MAX_TOKENS
492 #undef PIPE
493 #undef QUOTE
494 #undef ESCAPER
496 /* Add line into history if not NULL or empty string */
497 void shi_add_history(const char *line) {
498 if (line && *line) add_history(line);
502 /* Ask user for a password */
503 char *ask_for_password(const char *prompt) {
504 struct termios terminal;
505 int tcerr;
506 char *password = NULL;
508 tcerr = tcgetattr(fileno(stdin), &terminal);
509 if (tcerr) {
510 fprintf(stderr, "Could not get terminal characteristics\n");
511 } else {
512 /* TODO: handle SIGINT to restore echo */
513 terminal.c_lflag &= ~ECHO;
514 tcerr = tcsetattr(fileno(stdin), TCSAFLUSH, &terminal);
515 if (tcerr) {
516 fprintf(stderr, "Could not switch off local echo\n");
519 if (tcerr) {
520 fprintf(stderr, "Password will be visible on your terminal\n");
523 password = readline(prompt);
525 if (!tcerr) {
526 terminal.c_lflag |= ECHO;
527 tcerr = tcsetattr(fileno(stdin), TCSAFLUSH, &terminal);
528 if (tcerr) {
529 fprintf(stderr, "Could not switch on local echo\n");
531 printf("\n");
534 return password;
538 /* Prompt user and supply default value if user does input nothing. Original
539 * default value can be deallocated in this function. If *@value is NULL, use
540 * as default read-only @backup_value. You can always free *@value. * */
541 void shi_ask_for_string(char **value, const char *prompt,
542 const char *backup_value, _Bool batch_mode) {
543 char *answer = NULL;
545 if (!value) return;
547 shi_add_history(backup_value);
548 shi_add_history(*value);
550 if (!*value && backup_value) *value = strdup(backup_value);
552 if (batch_mode) {
553 if (prompt) printf(_("%s%s\n"), prompt,
554 (*value) ? *value : _("<Empty value>"));
555 } else {
556 if (*value) printf(_("Default value: %s\n"), *value);
557 answer = readline(prompt);
558 if (answer && answer[0] == '\0') zfree(answer);
560 if (answer) {
561 zfree(*value);
562 *value = answer;
563 return;
565 if (*value) printf(_("Using default value `%s'.\n"), *value);
567 return;
571 /* Prompt user for password and supply default value if user does input
572 * nothing. Original default value can be deallocated in this function. If
573 * *@value is NULL, use as default read-only @backup_value. You can always
574 * free *@value. * */
575 void shi_ask_for_password(char **value, const char *prompt,
576 const char *backup_value, _Bool batch_mode) {
577 char *answer = NULL;
579 if (!value) return;
581 if (!*value && backup_value) *value = strdup(backup_value);
583 if (batch_mode) {
584 if (prompt) printf(_("%s%s\n"), prompt,
585 (*value) ? _("<Password provided>") : _("<Empty value>"));
586 } else {
587 if (*value) printf(_("Default password exists\n"));
588 answer = ask_for_password(prompt);
589 if (answer && answer[0] == '\0') zfree(answer);
591 if (answer) {
592 zfree(*value);
593 *value = answer;
594 return;
596 if (*value) printf(_("Using default value.\n"));
598 return;
602 /* Ask Yes-No question.
603 * @question to ask the user
604 * @default_value specifies default answer if user puts nothing. True is for
605 * Yes, false is for No.
606 * @batch_mode is true for non-interctive mode, true for interactive
607 * @return true for yes, false for no. */
608 _Bool shi_ask_yes_no(const char *question, _Bool default_value,
609 _Bool batch_mode) {
610 _Bool answer = default_value;
611 const char *choices = (default_value) ? _("Y/n") : _("y/N");
612 const char *yes_expr, *no_expr;
613 regex_t yes_compiled, no_compiled;
614 char *input = NULL;
615 char *prompt = NULL;
616 int retval;
618 if (-1 == shi_asprintf(&prompt,
619 (question == NULL)? _("[%2$s]> ") : _("%1$s [%2$s]> "),
620 question, choices)) {
621 fprintf(stderr, _("Could not format yes-no prompt\n"));
622 goto leave;
625 if (batch_mode) {
626 printf("%s%s\n", prompt, (answer) ? _("Yes") : _("No") );
627 goto leave;
630 yes_expr = nl_langinfo(YESEXPR);
631 no_expr = nl_langinfo(NOEXPR);
632 if (NULL == yes_expr || !strcmp(yes_expr, "")) yes_expr = "^[yY].*";
633 if (NULL == no_expr || !strcmp(no_expr, "")) no_expr = "^[nN].*";
634 memset(&yes_compiled, 0, sizeof(yes_compiled));
635 memset(&no_compiled, 0, sizeof(no_compiled));
636 if (regcomp(&yes_compiled, yes_expr, REG_EXTENDED|REG_NOSUB)) {
637 fprintf(stderr, _("Error while compiling regular expression for "
638 "affirmation\n"));
639 goto refree;
641 if (regcomp(&no_compiled, no_expr, REG_EXTENDED|REG_NOSUB)) {
642 fprintf(stderr, _("Error while compiling regular expression for "
643 "negation\n"));
644 goto refree;
647 while (1) {
648 input = readline(prompt);
649 if (input == NULL || input[0] == '\0') break;
651 retval = regexec(&yes_compiled, input, 0, NULL, 0);
652 if (0 == retval) {
653 answer = 1;
654 break;
655 } else if (REG_NOMATCH != retval) {
656 fprintf(stderr, _("Error while matching answer for affirmation\n"));
657 break;
659 retval = regexec(&no_compiled, input, 0, NULL, 0);
660 if (0 == retval) {
661 answer = 0;
662 break;
663 } else if (REG_NOMATCH != retval) {
664 fprintf(stderr, _("Error while matching answer for negation\n"));
665 break;
667 printf(_("Unrecognized answer. Affirmation must match `%s', "
668 "negation must match `%s'.\n"), yes_expr, no_expr);
669 free(input);
671 free(input);
673 refree:
674 regfree(&yes_compiled);
675 regfree(&no_compiled);
677 leave:
678 free(prompt);
679 return answer;
683 static void shi_format_size(char **buffer, double value) {
684 if (value <= 0) {
685 shi_asprintf(buffer, _("---"));
686 } else if (value < (1<<10)) {
687 shi_asprintf(buffer, _("%0.2f B"), value);
688 } else if (value < (1<<20)) {
689 shi_asprintf(buffer, _("%0.2f KiB"), value/(1<<10));
690 } else {
691 shi_asprintf(buffer, _("%0.2f MiB"), value/(1<<20));
696 /* Signal handler aborting ISDS transfer */
697 static void shi_abort_transfer(int signo) {
698 if (progress_started) progress_abort_requested = 1;
702 /* This is ISDS context network progress call back. It prints progress meter
703 * and allows user to abort current network transfer. */
704 int shi_progressbar(double upload_total, double upload_current,
705 double download_total, double download_current, void *data) {
706 if (!progress_started) {
707 progress_started = 1;
708 progress_abort_requested = 0;
709 signal(SIGINT, shi_abort_transfer);
712 shi_format_size(&upload_current_formated, upload_current);
713 shi_format_size(&upload_total_formated, upload_total);
714 shi_format_size(&download_current_formated, download_current);
715 shi_format_size(&download_total_formated, download_total);
717 printf("\r");
718 printf(_("Progress: uploaded %s/%s, downloaded %s/%s"),
719 upload_current_formated, upload_total_formated,
720 download_current_formated, download_total_formated);
722 return progress_abort_requested;
726 /* Finish progress meter output. */
727 void shi_progressbar_finish(void) {
728 if (progress_started) {
729 progress_started = 0;
730 /* Remove progress bar info */
731 /* FIXME: Progress bar can be longer than summary. We must gather
732 * longest tainted column in shi_progressbar() and blank only that
733 * necessary width. */
734 printf("\r "
735 " ");
736 printf("\r");
737 printf(_("Transfer summary: uploaded %s, downloaded %s\n"),
738 upload_current_formated, download_current_formated);