1 #define _XOPEN_SOURCE 500
4 #include <readline/readline.h>
5 #include <readline/history.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"
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
;
35 text_length
= strlen(text
);
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
);
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
;
62 text
= locale2utf8(text_locale
);
63 if (text
) text_length
= strlen(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
)) {
73 return utf82locale(id
);
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
;
95 item
= message
->documents
;
101 for (; item
; item
= item
->next
) {
106 shi_asprintf(&document_id
, "%d", order
);
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
;
127 text
= locale2utf8(text_locale
);
128 if (text
) text_length
= strlen(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
)) {
138 return utf82locale(id
);
148 struct command
*find_command(const char *text
) {
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
];
161 arg_type
find_arg_type(const char *line
) {
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
;
179 static char **shi_completion_none(const char *text
, int start
, int end
) {
180 rl_attempted_completion_over
= 1;
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
191 static char **shi_completion_command(const char *text
, int start
, int end
) {
192 rl_attempted_completion_over
= 1;
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
,
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
);
215 rl_attempted_completion_over
= 0;
218 return rl_completion_matches(text
, shi_msgid_generator
);
220 return rl_completion_matches(text
, shi_docid_generator
);
222 return rl_completion_matches(text
, shi_boxid_generator
);
224 rl_attempted_completion_over
= 1;
230 /* Compares two command names */
231 int commandcmp(const void *a
, const void *b
) {
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
[]) {
241 for (count
= 0; unsorted_commands
[count
].name
; count
++);
244 commands
= calloc(count
+ 1, sizeof(struct command
));
247 _("Fatal error: Non enough memory to sort commands\n"));
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
);
265 /* Switch completion function */
266 int select_completion(const completion_type completion
) {
267 switch (completion
) {
269 rl_attempted_completion_function
= shi_completion_none
;
272 if (build_command_list(base_commands
)) return -1;
273 rl_attempted_completion_function
= shi_completion_command
;
276 if (build_command_list(message_commands
)) return -1;
277 rl_attempted_completion_function
= shi_completion_command
;
280 if (build_command_list(list_commands
)) return -1;
281 rl_attempted_completion_function
= shi_completion_command
;
288 /* Free list of chars recursively */
289 void argv_free(char **argv
) {
291 for (char **arg
= argv
; *arg
; arg
++)
299 /* Decides whether character at @index offset of @text is quoted */
300 int shi_char_is_quoted(char *text
, int index
) {
305 fprintf(stderr
, "shi_char_is_quoted(text=%s, index=%d)\n", text
, index
);
307 if (!text
|| index
< 0) return 0;
309 for (i
= 0; i
<= index
; i
++) {
310 if (text
[i
] == ESCAPER
&& !escaped
) {
321 /* Escapes file name */
322 char *shi_quote_filename(char *text
, int match_type
, char *quote_pointer
) {
323 char *quoted_text
= NULL
;
328 "shi_quote_filename(text=%s, match_type=%d, quote_pointer='%c')\n",
329 text
, match_type
, (quote_pointer
)?*quote_pointer
:0);
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';
347 /* Deescapes file name */
348 char *shi_dequote_filename(char *text
, int quote_char
) {
350 char *unquoted_text
= NULL
;
355 "shi_dequote_filename(text=%s, quote_char=%d)\n",
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
) {
370 unquoted_text
[i
++] = *text
;
372 unquoted_text
[i
] = '\0';
374 return unquoted_text
;
378 #define MAX_TOKENS 256
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
387 * @return NULL-terminated array of tokens or NULL in case of error */
388 char **tokenize(const char *command_line
, int *argc
, char **shell
) {
390 const char *start
, *end
;
391 _Bool escaped
= 0, quoted
= 0;
393 char *home
= getenv("HOME");
395 if (!argc
) return NULL
;
398 if (!command_line
) return NULL
;
400 if (!shell
) return NULL
;
403 argv
= calloc(MAX_TOKENS
, sizeof(*argv
));
405 fprintf(stderr
, _("Not enough memory to tokenize command line\n"));
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"));
418 /* Locate token boundaries */
419 escaped
= quoted
= 0;
420 for (end
= start
; *end
; end
++) {
421 if (*end
== ESCAPER
&& !escaped
) {
425 if (*end
== QUOTE
&& !escaped
) {
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
);
437 argv
[*argc
] = malloc(home_length
+ end
-start
+ 1);
439 strcpy(argv
[*argc
], home
);
440 target
= argv
[*argc
] + home_length
;
443 argv
[*argc
] = malloc(end
-start
+ 1);
444 target
= argv
[*argc
];
447 fprintf(stderr
, _("Not enough memory to tokenize command line\n"));
451 /* Copy unquoted token */
452 for (escaped
= quoted
= 0; start
< end
; start
++) {
453 if (*start
== ESCAPER
&& !escaped
) {
457 if (*start
== QUOTE
&& !escaped
) {
468 if (!*start
|| *start
== PIPE
) break;
471 /* Copy shell command */
472 if (*start
== PIPE
) {
474 size_t length
= strlen(start
);
477 *shell
= malloc(length
+ 1);
478 if (!*shell
) goto error
;
479 strcpy(*shell
, start
);
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
;
506 char *password
= NULL
;
508 tcerr
= tcgetattr(fileno(stdin
), &terminal
);
510 fprintf(stderr
, "Could not get terminal characteristics\n");
512 /* TODO: handle SIGINT to restore echo */
513 terminal
.c_lflag
&= ~ECHO
;
514 tcerr
= tcsetattr(fileno(stdin
), TCSAFLUSH
, &terminal
);
516 fprintf(stderr
, "Could not switch off local echo\n");
520 fprintf(stderr
, "Password will be visible on your terminal\n");
523 password
= readline(prompt
);
526 terminal
.c_lflag
|= ECHO
;
527 tcerr
= tcsetattr(fileno(stdin
), TCSAFLUSH
, &terminal
);
529 fprintf(stderr
, "Could not switch on local echo\n");
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
) {
547 shi_add_history(backup_value
);
548 shi_add_history(*value
);
550 if (!*value
&& backup_value
) *value
= strdup(backup_value
);
553 if (prompt
) printf(_("%s%s\n"), prompt
,
554 (*value
) ? *value
: _("<Empty value>"));
556 if (*value
) printf(_("Default value: %s\n"), *value
);
557 answer
= readline(prompt
);
558 if (answer
&& answer
[0] == '\0') zfree(answer
);
565 if (*value
) printf(_("Using default value `%s'.\n"), *value
);
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
575 void shi_ask_for_password(char **value
, const char *prompt
,
576 const char *backup_value
, _Bool batch_mode
) {
581 if (!*value
&& backup_value
) *value
= strdup(backup_value
);
584 if (prompt
) printf(_("%s%s\n"), prompt
,
585 (*value
) ? _("<Password provided>") : _("<Empty value>"));
587 if (*value
) printf(_("Default password exists\n"));
588 answer
= ask_for_password(prompt
);
589 if (answer
&& answer
[0] == '\0') zfree(answer
);
596 if (*value
) printf(_("Using default value.\n"));
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
,
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
;
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"));
626 printf("%s%s\n", prompt
, (answer
) ? _("Yes") : _("No") );
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 "
641 if (regcomp(&no_compiled
, no_expr
, REG_EXTENDED
|REG_NOSUB
)) {
642 fprintf(stderr
, _("Error while compiling regular expression for "
648 input
= readline(prompt
);
649 if (input
== NULL
|| input
[0] == '\0') break;
651 retval
= regexec(&yes_compiled
, input
, 0, NULL
, 0);
655 } else if (REG_NOMATCH
!= retval
) {
656 fprintf(stderr
, _("Error while matching answer for affirmation\n"));
659 retval
= regexec(&no_compiled
, input
, 0, NULL
, 0);
663 } else if (REG_NOMATCH
!= retval
) {
664 fprintf(stderr
, _("Error while matching answer for negation\n"));
667 printf(_("Unrecognized answer. Affirmation must match `%s', "
668 "negation must match `%s'.\n"), yes_expr
, no_expr
);
674 regfree(&yes_compiled
);
675 regfree(&no_compiled
);
683 static void shi_format_size(char **buffer
, double value
) {
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));
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
);
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. */
737 printf(_("Transfer summary: uploaded %s, downloaded %s\n"),
738 upload_current_formated
, download_current_formated
);