/* * shell_tinyrl.c * * This is a specialisation of the tinyrl_t class which maps the readline * functionality to the CLISH environment. */ #include "private.h" #include #include #include #include #include #include #include "tinyrl/tinyrl.h" #include "tinyrl/history.h" #include "lub/string.h" /*-------------------------------------------------------- */ static void clish_shell_renew_prompt(clish_shell_t *this) { clish_context_t prompt_context; char *prompt = NULL; const clish_view_t *view; char *str = NULL; /* Create appropriate context */ memset(&prompt_context, 0, sizeof(prompt_context)); prompt_context.shell = this; /* Obtain the prompt */ view = clish_shell__get_view(this); assert(view); lub_string_cat(&str, "${_PROMPT_PREFIX}"); lub_string_cat(&str, clish_view__get_prompt(view)); lub_string_cat(&str, "${_PROMPT_SUFFIX}"); prompt = clish_shell_expand(str, SHELL_VAR_NONE, &prompt_context); assert(prompt); lub_string_free(str); tinyrl__set_prompt(this->tinyrl, prompt); lub_string_free(prompt); } /*-------------------------------------------------------- */ static bool_t clish_shell_tinyrl_key_help(tinyrl_t * this, int key) { bool_t result = BOOL_TRUE; if (tinyrl_is_quoting(this)) { /* if we are in the middle of a quote then simply enter a space */ result = tinyrl_insert_text(this, "?"); } else { /* get the context */ clish_context_t *context = tinyrl__get_context(this); tinyrl_crlf(this); clish_shell_help(context->shell, tinyrl__get_line(this)); tinyrl_crlf(this); tinyrl_reset_line_state(this); } /* keep the compiler happy */ key = key; return result; } /*lint +e818 */ /*-------------------------------------------------------- */ /* * Expand the current line with any history substitutions */ static clish_pargv_status_t clish_shell_tinyrl_expand(tinyrl_t * this) { clish_pargv_status_t status = CLISH_LINE_OK; int rtn; char *buffer; /* first of all perform any history substitutions */ rtn = tinyrl_history_expand(tinyrl__get_history(this), tinyrl__get_line(this), &buffer); switch (rtn) { case -1: /* error in expansion */ status = CLISH_BAD_HISTORY; break; case 0: /*no expansion */ break; case 1: /* expansion occured correctly */ tinyrl_replace_line(this, buffer, 1); break; case 2: /* just display line */ tinyrl_printf(this, "\n%s", buffer); free(buffer); buffer = NULL; break; default: break; } free(buffer); return status; } /*-------------------------------------------------------- */ /* * This is a CLISH specific completion function. * If the current prefix is not a recognised prefix then * an error is flagged. * If it is a recognisable prefix then possible completions are displayed * or a unique completion is inserted. */ static tinyrl_match_e clish_shell_tinyrl_complete(tinyrl_t * this) { /* context_t *context = tinyrl__get_context(this); */ tinyrl_match_e status; /* first of all perform any history expansion */ (void)clish_shell_tinyrl_expand(this); /* perform normal completion */ status = tinyrl_complete(this); switch (status) { case TINYRL_NO_MATCH: if (BOOL_FALSE == tinyrl_is_completion_error_over(this)) { /* The user hasn't even entered a valid prefix! */ /* tinyrl_crlf(this); clish_shell_help(context->shell, tinyrl__get_line(this)); tinyrl_crlf(this); tinyrl_reset_line_state(this); */ } break; default: /* the default completion function will have prompted for completions as * necessary */ break; } return status; } /*--------------------------------------------------------- */ static bool_t clish_shell_tinyrl_key_space(tinyrl_t * this, int key) { bool_t result = BOOL_FALSE; tinyrl_match_e status; clish_context_t *context = tinyrl__get_context(this); const char *line = tinyrl__get_line(this); clish_pargv_status_t arg_status; const clish_command_t *cmd = NULL; clish_pargv_t *pargv = NULL; if(tinyrl_is_empty(this)) { /* ignore space at the begining of the line, don't display commands */ return BOOL_TRUE; } else if (tinyrl_is_quoting(this)) { /* if we are in the middle of a quote then simply enter a space */ result = BOOL_TRUE; } else { /* Find out if current line is legal. It can be * fully completed or partially completed. */ arg_status = clish_shell_parse(context->shell, line, &cmd, &pargv); if (pargv) clish_pargv_delete(pargv); switch (arg_status) { case CLISH_LINE_OK: case CLISH_LINE_PARTIAL: if (' ' != line[strlen(line) - 1]) result = BOOL_TRUE; break; default: break; } /* If current line is illegal try to make auto-comletion. */ if (!result) { /* perform word completion */ status = clish_shell_tinyrl_complete(this); switch (status) { case TINYRL_NO_MATCH: case TINYRL_AMBIGUOUS: /* ambiguous result signal an issue */ break; case TINYRL_COMPLETED_AMBIGUOUS: /* perform word completion again in case we just did case modification the first time */ status = clish_shell_tinyrl_complete(this); if (status == TINYRL_MATCH_WITH_EXTENSIONS) { /* all is well with the world just enter a space */ result = BOOL_TRUE; } break; case TINYRL_MATCH: case TINYRL_MATCH_WITH_EXTENSIONS: case TINYRL_COMPLETED_MATCH: /* all is well with the world just enter a space */ result = BOOL_TRUE; break; } } } if (result) result = tinyrl_insert_text(this, " "); /* keep compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ static bool_t clish_shell_tinyrl_key_enter(tinyrl_t *this, int key) { clish_context_t *context = tinyrl__get_context(this); const clish_command_t *cmd = NULL; const char *line = tinyrl__get_line(this); bool_t result = BOOL_FALSE; char *errmsg = NULL; /* Inc line counter */ if (context->shell->current_file) context->shell->current_file->line++; /* Renew prompt */ clish_shell_renew_prompt(context->shell); /* nothing to pass simply move down the screen */ if (!*line) { tinyrl_crlf(this); tinyrl_done(this); return BOOL_TRUE; } /* try and parse the command */ cmd = clish_shell_resolve_command(context->shell, line); if (!cmd) { tinyrl_match_e status = clish_shell_tinyrl_complete(this); switch (status) { case TINYRL_MATCH: case TINYRL_MATCH_WITH_EXTENSIONS: case TINYRL_COMPLETED_MATCH: /* re-fetch the line as it may have changed * due to auto-completion */ line = tinyrl__get_line(this); /* get the command to parse? */ cmd = clish_shell_resolve_command(context->shell, line); /* * We have had a match but it is not a command * so add a space so as not to confuse the user */ if (!cmd) result = tinyrl_insert_text(this, " "); break; default: /* failed to get a unique match... */ if (!tinyrl__get_isatty(this)) { /* batch mode */ tinyrl_crlf(this); errmsg = "Unknown command"; } break; } } if (cmd) { clish_pargv_status_t arg_status; tinyrl_crlf(this); /* we've got a command so check the syntax */ arg_status = clish_shell_parse(context->shell, line, &context->cmd, &context->pargv); switch (arg_status) { case CLISH_LINE_OK: tinyrl_done(this); result = BOOL_TRUE; break; case CLISH_BAD_HISTORY: errmsg = "Bad history entry"; break; case CLISH_BAD_CMD: errmsg = "Illegal command line"; break; case CLISH_BAD_PARAM: errmsg = "Illegal parameter"; break; case CLISH_LINE_PARTIAL: errmsg = "The command is not completed"; break; default: errmsg = "Unknown problem"; break; } } /* If error then print message */ if (errmsg) { if (tinyrl__get_isatty(this) || !context->shell->current_file) { fprintf(stderr, "Syntax error: %s\n", errmsg); tinyrl_reset_line_state(this); } else { char *fname = "stdin"; if (context->shell->current_file->fname) fname = context->shell->current_file->fname; fprintf(stderr, "Syntax error on line %s:%u \"%s\": " "%s\n", fname, context->shell->current_file->line, line, errmsg); } } /* keep the compiler happy */ key = key; return result; } /*-------------------------------------------------------- */ /* This is the completion function provided for CLISH */ tinyrl_completion_func_t clish_shell_tinyrl_completion; char **clish_shell_tinyrl_completion(tinyrl_t * tinyrl, const char *line, unsigned start, unsigned end) { lub_argv_t *matches; clish_context_t *context = tinyrl__get_context(tinyrl); clish_shell_t *this = context->shell; clish_shell_iterator_t iter; const clish_command_t *cmd = NULL; char *text; char **result = NULL; if (tinyrl_is_quoting(tinyrl)) return result; matches = lub_argv_new(NULL, 0); text = lub_string_dupn(line, end); /* Don't bother to resort to filename completion */ tinyrl_completion_over(tinyrl); /* Search for COMMAND completions */ clish_shell_iterator_init(&iter, CLISH_NSPACE_COMPLETION); while ((cmd = clish_shell_find_next_completion(this, text, &iter))) lub_argv_add(matches, clish_command__get_suffix(cmd)); /* Try and resolve a command */ cmd = clish_shell_resolve_command(this, text); /* Search for PARAM completion */ if (cmd) clish_shell_param_generator(this, matches, cmd, text, start); lub_string_free(text); /* Matches were found */ if (lub_argv__get_count(matches) > 0) { unsigned i; char *subst = lub_string_dup(lub_argv__get_arg(matches, 0)); /* Find out substitution */ for (i = 1; i < lub_argv__get_count(matches); i++) { char *p = subst; const char *match = lub_argv__get_arg(matches, i); size_t match_len = strlen(p); /* identify the common prefix */ while ((tolower(*p) == tolower(*match)) && match_len--) { p++; match++; } /* Terminate the prefix string */ *p = '\0'; } result = lub_argv__get_argv(matches, subst); lub_string_free(subst); } lub_argv_delete(matches); return result; } /*-------------------------------------------------------- */ static void clish_shell_tinyrl_init(tinyrl_t * this) { bool_t status; /* bind the '?' key to the help function */ status = tinyrl_bind_key(this, '?', clish_shell_tinyrl_key_help); assert(status); /* bind the key to the help function */ status = tinyrl_bind_key(this, '\r', clish_shell_tinyrl_key_enter); assert(status); status = tinyrl_bind_key(this, '\n', clish_shell_tinyrl_key_enter); assert(status); /* bind the key to auto-complete if necessary */ status = tinyrl_bind_key(this, ' ', clish_shell_tinyrl_key_space); assert(status); /* Assign timeout callback */ tinyrl__set_timeout_fn(this, clish_shell_timeout_fn); /* Assign keypress callback */ tinyrl__set_keypress_fn(this, clish_shell_keypress_fn); } /*-------------------------------------------------------- */ /* * Create an instance of the specialised class */ tinyrl_t *clish_shell_tinyrl_new(FILE * istream, FILE * ostream, unsigned stifle) { /* call the parent constructor */ tinyrl_t *this = tinyrl_new(istream, ostream, stifle, clish_shell_tinyrl_completion); /* now call our own constructor */ if (this) clish_shell_tinyrl_init(this); return this; } /*-------------------------------------------------------- */ void clish_shell_tinyrl_fini(tinyrl_t * this) { /* nothing to do... yet */ this = this; } /*-------------------------------------------------------- */ void clish_shell_tinyrl_delete(tinyrl_t * this) { /* call our destructor */ clish_shell_tinyrl_fini(this); /* and call the parent destructor */ tinyrl_delete(this); } /*-------------------------------------------------------- */ int clish_shell_execline(clish_shell_t *this, const char *line, char **out) { char *str; clish_context_t context; tinyrl_history_t *history; int lerror = 0; assert(this); this->state = SHELL_STATE_OK; if (!line && !tinyrl__get_istream(this->tinyrl)) { this->state = SHELL_STATE_SYSTEM_ERROR; return -1; } /* Renew prompt */ clish_shell_renew_prompt(this); /* Set up the context for tinyrl */ context.cmd = NULL; context.pargv = NULL; context.shell = this; /* Push the specified line or interactive line */ if (line) str = tinyrl_forceline(this->tinyrl, &context, line); else str = tinyrl_readline(this->tinyrl, &context); lerror = errno; if (!str) { switch (lerror) { case ENOENT: this->state = SHELL_STATE_EOF; break; case ENOEXEC: this->state = SHELL_STATE_SYNTAX_ERROR; break; default: this->state = SHELL_STATE_SYSTEM_ERROR; break; }; return -1; } /* Deal with the history list */ if (tinyrl__get_isatty(this->tinyrl)) { history = tinyrl__get_history(this->tinyrl); tinyrl_history_add(history, str); } /* Let the client know the command line has been entered */ if (this->client_hooks->cmd_line_fn) this->client_hooks->cmd_line_fn(&context, str); free(str); /* Execute the provided command */ if (context.cmd && context.pargv) { int res; if ((res = clish_shell_execute(&context, out))) { this->state = SHELL_STATE_SCRIPT_ERROR; if (context.pargv) clish_pargv_delete(context.pargv); return res; } } if (context.pargv) clish_pargv_delete(context.pargv); return 0; } /*-------------------------------------------------------- */ int clish_shell_forceline(clish_shell_t *this, const char *line, char **out) { return clish_shell_execline(this, line, out); } /*-------------------------------------------------------- */ int clish_shell_readline(clish_shell_t *this, char **out) { return clish_shell_execline(this, NULL, out); } /*-------------------------------------------------------- */ FILE * clish_shell__get_istream(const clish_shell_t * this) { return tinyrl__get_istream(this->tinyrl); } /*-------------------------------------------------------- */ FILE * clish_shell__get_ostream(const clish_shell_t * this) { return tinyrl__get_ostream(this->tinyrl); } /*-------------------------------------------------------- */ void clish_shell__set_interactive(clish_shell_t * this, bool_t interactive) { assert(this); this->interactive = interactive; } /*-------------------------------------------------------- */ bool_t clish_shell__get_interactive(const clish_shell_t * this) { assert(this); return this->interactive; } /*-------------------------------------------------------- */ bool_t clish_shell__get_utf8(const clish_shell_t * this) { assert(this); return tinyrl__get_utf8(this->tinyrl); } /*-------------------------------------------------------- */ void clish_shell__set_utf8(clish_shell_t * this, bool_t utf8) { assert(this); tinyrl__set_utf8(this->tinyrl, utf8); } /*-------------------------------------------------------- */ void clish_shell__set_timeout(clish_shell_t *this, int timeout) { assert(this); this->idle_timeout = timeout; } /*--------------------------------------------------------- */ tinyrl_t *clish_shell__get_tinyrl(const clish_shell_t * this) { return this->tinyrl; } /*----------------------------------------------------------*/ int clish_shell__save_history(const clish_shell_t *this, const char *fname) { return tinyrl__save_history(this->tinyrl, fname); } /*----------------------------------------------------------*/ int clish_shell__restore_history(clish_shell_t *this, const char *fname) { return tinyrl__restore_history(this->tinyrl, fname); } /*----------------------------------------------------------*/ void clish_shell__stifle_history(clish_shell_t *this, unsigned int stifle) { tinyrl__stifle_history(this->tinyrl, stifle); } /*-------------------------------------------------------- */