Correct typos
[shigofumi.git] / src / completition.c
blobb721cf9a4d9c4759aedce5d0c059bb1d83ae19fd
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>
12 #include "shigofumi.h"
13 #include <isds.h>
14 #include "completition.h"
16 static _Bool progress_started = 0;
17 static _Bool progress_abort_requested = 0;
18 static char *upload_current_formated = NULL;
19 static char *upload_total_formated = NULL;
20 static char *download_current_formated = NULL;
21 static char *download_total_formated = NULL;
23 /* Generates possible completions for base commands set
24 * @text is partial user input word
25 * @state is 0 for first completion attempt, non-zero otherwise
26 * @return next suggested complete word or NULL if no possibility */
27 static char *shi_command_generator(const char *text, int state) {
28 static size_t text_length, index;
29 char *command_name;
31 if (!state) {
32 text_length = strlen(text);
33 index = 0;
36 if (!commands) return NULL;
38 while ((command_name = (*commands)[index++].name)) {
39 if (!strncmp(command_name, text, text_length))
40 return strdup(command_name);
43 return NULL;
47 /* Generates possible message ID completions
48 * @text_locale is partial user input word in locale encoding
49 * @state is 0 for first completion attempt, non-zero otherwise
50 * @return next suggested complete word or NULL if no possibility */
51 static char *shi_msgid_generator(const char *text_locale, int state) {
52 static char *text = NULL;
53 static size_t text_length;
54 static struct isds_list *item;
55 char *id;
57 if (!state) {
58 free(text);
59 text = locale2utf8(text_locale);
60 if (text) text_length = strlen(text);
61 item = messages;
64 if (text) {
65 for (; item; item = item->next) {
66 if (item->data && ((struct isds_message *)item->data)->envelope) {
67 id = ((struct isds_message *)item->data)->envelope->dmID;
68 if (id && !strncmp(id, text, text_length)) {
69 item = item->next;
70 return utf82locale(id);
76 return NULL;
80 /* Generates possible document ID completions
81 * @text is partial user input word
82 * @state is 0 for first completion attempt, non-zero otherwise
83 * @return next suggested complete word or NULL if no possibility */
84 static char *shi_docid_generator(const char *text, int state) {
85 static size_t text_length;
86 static struct isds_list *item;
87 static int order;
88 char *document_id;
90 if (!state) {
91 text_length = strlen(text);
92 if (message)
93 item = message->documents;
94 else
95 item = NULL;
96 order = 0;
99 for (; item; item = item->next) {
100 if (item->data) {
101 item = item->next;
102 order++;
103 document_id = NULL;
104 shi_asprintf(&document_id, "%d", order);
105 return document_id;
109 return NULL;
113 /* Generates possible box ID completions
114 * @text_locale is partial user input word in locale encoding
115 * @state is 0 for first completion attempt, non-zero otherwise
116 * @return next suggested complete word or NULL if no possibility */
117 static char *shi_boxid_generator(const char *text_locale, int state) {
118 static char *text = NULL;
119 static size_t text_length;
120 static struct isds_list *item;
121 char *id;
123 if (!state) {
124 free(text);
125 text = locale2utf8(text_locale);
126 if (text) text_length = strlen(text);
127 item = boxes;
130 if (text) {
131 for (; item; item = item->next) {
132 if (item->data && ((struct isds_DbOwnerInfo *)item->data)->dbID) {
133 id = ((struct isds_DbOwnerInfo *)item->data)->dbID;
134 if (id && !strncmp(id, text, text_length)) {
135 item = item->next;
136 return utf82locale(id);
142 return NULL;
146 struct command *find_command(const char *text) {
147 int index = 0;
148 if (!text || !commands) return NULL;
150 for (index = 0; (*commands)[index].name; index++) {
151 if (!strcmp((*commands)[index].name, text))
152 return &(*commands)[index];
155 return NULL;
159 arg_type find_arg_type(const char *line) {
160 int index;
161 size_t length;
163 if (!line || !commands) return ARGTYPE_NONE;
165 for (index = 0; (*commands)[index].name; index++) {
166 length = strlen((*commands)[index].name);
167 if (rl_point >= length &&
168 !strncmp(line, (*commands)[index].name, length) &&
169 isspace(line[length]))
170 return (*commands)[index].arg;
173 return ARGTYPE_NONE;
177 static char **shi_completition_none(const char *text, int start, int end) {
178 rl_attempted_completion_over = 1;
179 return NULL;
182 static char **shi_completition_command(const char *text, int start, int end) {
183 rl_attempted_completion_over = 1;
185 if (!start)
186 /* Command */
187 return rl_completion_matches(text, shi_command_generator);
189 /* Command argument */
190 switch (find_arg_type(rl_line_buffer)) {
191 case ARGTYPE_COMMAND:
192 return rl_completion_matches(text, shi_command_generator);
193 case ARGTYPE_FILE:
194 rl_attempted_completion_over = 0;
195 break;
196 case ARGTYPE_MSGID:
197 return rl_completion_matches(text, shi_msgid_generator);
198 case ARGTYPE_DOCID:
199 return rl_completion_matches(text, shi_docid_generator);
200 case ARGTYPE_BOXID:
201 return rl_completion_matches(text, shi_boxid_generator);
202 default:
203 rl_attempted_completion_over = 1;
205 return NULL;
209 /* Compares two command names */
210 int commandcmp(const void *a, const void *b) {
211 if (!a) return 1;
212 return strcoll(((struct command *)a)->name, ((struct command *)b)->name);
216 /* Make list of currently available commands */
217 static int build_command_list(const struct command unsorted_commands[]) {
218 int count, i;
220 for (count = 0; unsorted_commands[count].name; count++);
222 zfree(commands);
223 commands = calloc(count + 1, sizeof(struct command));
224 if (!commands) {
225 fprintf(stderr,
226 _("Fatal error: Non enough memory to sort commands\n"));
227 return -1;
230 for (i = 0; i < count; i++) {
231 (*commands)[i].name = unsorted_commands[i].name;
232 (*commands)[i].function = unsorted_commands[i].function;
233 (*commands)[i].description = unsorted_commands[i].description;
234 (*commands)[i].usage = unsorted_commands[i].usage;
235 (*commands)[i].arg = unsorted_commands[i].arg;
238 qsort(*commands, count, sizeof(struct command), commandcmp);
240 return 0;
244 /* Switch completion function */
245 int select_completition(const completition_type completition) {
246 switch (completition) {
247 case COMPL_NONE:
248 rl_attempted_completion_function = shi_completition_none;
249 break;
250 case COMPL_COMMAND:
251 if (build_command_list(base_commands)) return -1;
252 rl_attempted_completion_function = shi_completition_command;
253 break;
254 case COMPL_MSG:
255 if (build_command_list(message_commands)) return -1;
256 rl_attempted_completion_function = shi_completition_command;
257 break;
258 case COMPL_LIST:
259 if (build_command_list(list_commands)) return -1;
260 rl_attempted_completion_function = shi_completition_command;
261 break;
263 return 0;
267 /* Free list of chars recursively */
268 void argv_free(char **argv) {
269 if (argv) {
270 for (char **arg = argv; *arg; arg++)
271 free(*arg);
272 free(argv);
277 #define ESCAPER '\\'
278 /* Decides whether character at @index offset of @text is quoted */
279 int shi_char_is_quoted(char *text, int index) {
280 int i;
281 _Bool escaped = 0;
283 fprintf(stderr, "shi_char_is_quoted(text=%s, index=%d)\n", text, index);
284 if (!text || index < 0) return 0;
286 for (i = 0; i <= index; i++) {
287 if (text[i] == ESCAPER && !escaped) {
288 escaped = 1;
289 continue;
291 escaped = 0;
294 return escaped;
298 /* Escapes file name */
299 char *shi_quote_filename(char *text, int match_type, char *quote_pointer) {
300 char *quoted_text = NULL;
301 int i;
303 fprintf(stderr,
304 "shi_quote_filename(text=%s, match_type=%d, quote_pointer='%c')\n",
305 text, match_type, (quote_pointer)?*quote_pointer:0);
306 if (!text) return NULL;
308 quoted_text = malloc(strlen(text) * 2 + 1);
309 if (!quoted_text) return strdup(text);
311 for (i = 0; *text; text++, i++) {
312 if (*text == ESCAPER) quoted_text[i++] = ESCAPER;
313 if (isspace(*text)) quoted_text[i++] = ESCAPER;
314 quoted_text[i] = *text;
316 quoted_text[i] = '\0';
318 return quoted_text;
322 /* Deescapes file name */
323 char *shi_dequote_filename(char *text, int quote_char) {
324 _Bool escaped;
325 char *unquoted_text = NULL;
326 int i;
328 fprintf(stderr,
329 "shi_dequote_filename(text=%s, quote_char=%d)\n",
330 text, quote_char);
331 if (!text) return NULL;
333 unquoted_text = malloc(strlen(text) + 1);
334 if (!unquoted_text) return strdup(text);
336 for (escaped = 0, i = 0; *text; text++) {
337 if (*text == ESCAPER && !escaped) {
338 escaped = 1;
339 continue;
341 escaped = 0;
343 unquoted_text[i++] = *text;
345 unquoted_text[i] = '\0';
347 return unquoted_text;
351 #define MAX_TOKENS 256
352 /* Split string into tokens
353 * @command_line is line to parse
354 * @argc outputs number of parsed arguments
355 * @return NULL-terminated array of tokens or NULL in case of error */
356 char **tokenize(const char *command_line, int *argc) {
357 char **argv = NULL;
358 const char *start, *end;
359 _Bool escaped = 0;
360 char *target;
361 char *home = getenv("HOME");
363 if (!argc) return NULL;
364 *argc = 0;
366 if (!command_line) return NULL;
368 argv = calloc(MAX_TOKENS, sizeof(*argv));
369 if (!argv) {
370 fprintf(stderr, _("Not enough memory to tokenize command line\n"));
371 goto error;
374 /* TODO: quoting */
375 /*for (start = command_line; *start; start++) {
376 if (isspace(*start)) continue;
378 if (*argc >= MAX_TOKENS - 1) {
379 fprintf(stderr, _("To much arguments\n"));
380 goto error;
383 for (end = start; *end && !isspace(*end); end++);
384 argv[*argc] = malloc(end-start + 1);
385 if (!argv[*argc]) {
386 fprintf(stderr, _("Not enough memory to tokenize command line\n"));
387 goto error;
389 memcpy(argv[*argc], start, end-start);
390 argv[*argc][end-start] = '\0';
392 (*argc)++;
393 start = end;
394 if (!*start) break;
396 for (start = command_line; *start; start++) {
397 if (isspace(*start)) continue;
399 if (*argc >= MAX_TOKENS - 1) {
400 fprintf(stderr, _("To much arguments\n"));
401 goto error;
404 /* Lokalize token boundaries */
405 for (end = start; *end; end++) {
406 if (*end == ESCAPER && !escaped) {
407 escaped = 1;
408 continue;
410 if (isspace(*end) && !escaped) break;
411 if (escaped) escaped = 0;
414 /* Allocate memory and expand first tilde to $HOME */
415 if (*start == '~' && home) {
416 size_t home_length = strlen(home);
417 start++;
418 argv[*argc] = malloc(home_length + end-start + 1);
419 if (argv[*argc]) {
420 strcpy(argv[*argc], home);
421 target = argv[*argc] + home_length;
423 } else {
424 argv[*argc] = malloc(end-start + 1);
425 target = argv[*argc];
427 if (!argv[*argc]) {
428 fprintf(stderr, _("Not enough memory to tokenize command line\n"));
429 goto error;
432 /* Copy unquoted token */
433 for (escaped = 0; start < end; start++) {
434 if (*start == ESCAPER && !escaped) {
435 escaped = 1;
436 continue;
438 escaped = 0;
439 *target++ = *start;
441 *target = '\0';
443 (*argc)++;
444 start = end;
445 if (!*start) break;
448 return argv;
450 error:
451 argv_free(argv);
452 return NULL;
455 #undef MAX_TOKENS
456 #undef ESCAPER
458 /* Add line into history if not NULL or empty string */
459 void shi_add_history(const char *line) {
460 if (line && *line) add_history(line);
464 /* Ask user for a password */
465 char *ask_for_password(const char *prompt) {
466 struct termios terminal;
467 int tcerr;
468 char *password = NULL;
470 tcerr = tcgetattr(fileno(stdin), &terminal);
471 if (tcerr) {
472 fprintf(stderr, "Could not get terminal characteristics\n");
473 } else {
474 /* TODO: handle SIGINT to restore echo */
475 terminal.c_lflag &= ~ECHO;
476 tcerr = tcsetattr(fileno(stdin), TCSAFLUSH, &terminal);
477 if (tcerr) {
478 fprintf(stderr, "Could not switch off local echo\n");
481 if (tcerr) {
482 fprintf(stderr, "Password will be visible on your terminal\n");
485 password = readline(prompt);
487 if (!tcerr) {
488 terminal.c_lflag |= ECHO;
489 tcerr = tcsetattr(fileno(stdin), TCSAFLUSH, &terminal);
490 if (tcerr) {
491 fprintf(stderr, "Could not switch on local echo\n");
493 printf("\n");
496 return password;
500 /* Prompt user and supply default value if user does input nothing. Original
501 * default value can be deallocated in this function. If *@value is NULL, use
502 * as default read-only @backup_value. You can always free *@value. * */
503 void shi_replace_string(char **value, const char *prompt,
504 const char *backup_value, _Bool batch_mode) {
505 char *answer = NULL;
507 if (!value) return;
509 shi_add_history(backup_value);
510 shi_add_history(*value);
512 if (!*value && backup_value) *value = strdup(backup_value);
514 if (batch_mode) {
515 if (prompt) printf(_("%s%s\n"), prompt,
516 (*value) ? *value : _("<Empty value>"));
517 } else {
518 if (*value) printf(_("Default value: %s\n"), *value);
519 answer = readline(prompt);
520 if (answer && answer[0] == '\0') zfree(answer);
522 if (answer) {
523 zfree(*value);
524 *value = answer;
525 return;
527 if (*value) printf(_("Using default value `%s'.\n"), *value);
529 return;
533 /* Prompt user for password and supply default value if user does input
534 * nothing. Original default value can be deallocated in this function. If
535 * *@value is NULL, use as default read-only @backup_value. You can always
536 * free *@value. * */
537 void shi_replace_password(char **value, const char *prompt,
538 const char *backup_value, _Bool batch_mode) {
539 char *answer = NULL;
541 if (!value) return;
543 if (!*value && backup_value) *value = strdup(backup_value);
545 if (batch_mode) {
546 if (prompt) printf(_("%s%s\n"), prompt,
547 (*value) ? _("<Password provided>") : _("<Empty value>"));
548 } else {
549 if (*value) printf(_("Default password exists\n"));
550 answer = ask_for_password(prompt);
551 if (answer && answer[0] == '\0') zfree(answer);
553 if (answer) {
554 zfree(*value);
555 *value = answer;
556 return;
558 if (*value) printf(_("Using default value.\n"));
560 return;
564 static void shi_format_size(char **buffer, double value) {
565 if (value <= 0) {
566 shi_asprintf(buffer, _("---"));
567 } else if (value < (1<<10)) {
568 shi_asprintf(buffer, _("%0.2f B"), value);
569 } else if (value < (1<<20)) {
570 shi_asprintf(buffer, _("%0.2f KiB"), value/(1<<10));
571 } else {
572 shi_asprintf(buffer, _("%0.2f MiB"), value/(1<<20));
577 /* Signal handler aborting ISDS transfer */
578 static void shi_abort_transfer(int signo) {
579 if (progress_started) progress_abort_requested = 1;
583 /* This is ISDS context network progress call back. It prints progress meter
584 * and allows user to abort current network transfer. */
585 int shi_progressbar(double upload_total, double upload_current,
586 double download_total, double download_current, void *data) {
587 if (!progress_started) {
588 progress_started = 1;
589 progress_abort_requested = 0;
590 signal(SIGINT, shi_abort_transfer);
593 shi_format_size(&upload_current_formated, upload_current);
594 shi_format_size(&upload_total_formated, upload_total);
595 shi_format_size(&download_current_formated, download_current);
596 shi_format_size(&download_total_formated, download_total);
598 printf("\r");
599 printf(_("Progress: upload %s/%s, download %s/%s"),
600 upload_current_formated, upload_total_formated,
601 download_current_formated, download_total_formated);
603 return progress_abort_requested;
607 /* Finish progress meter output. */
608 void shi_progressbar_finish(void) {
609 if (progress_started) {
610 progress_started = 0;
611 /* Remove progress bar info */
612 /* FIXME: Progress bar can be longer than summary. We must gather
613 * longest tainted column in shi_progressbar() and blank only that
614 * necessary width. */
615 printf("\r "
616 " ");
617 printf("\r");
618 printf(_("Transfer summary: upload %s, download %s\n"),
619 upload_current_formated, download_current_formated);