diff options
Diffstat (limited to 'pjlib-util/src/pjlib-util/cli_console.c')
-rw-r--r-- | pjlib-util/src/pjlib-util/cli_console.c | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/pjlib-util/src/pjlib-util/cli_console.c b/pjlib-util/src/pjlib-util/cli_console.c new file mode 100644 index 00000000..76dedb35 --- /dev/null +++ b/pjlib-util/src/pjlib-util/cli_console.c @@ -0,0 +1,540 @@ +/* $Id$ */ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjlib-util/cli_imp.h> +#include <pjlib-util/cli_console.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pjlib-util/errno.h> + +/** + * This specify the state of output character parsing. + */ +typedef enum out_parse_state +{ + OP_NORMAL, + OP_TYPE, + OP_SHORTCUT, + OP_CHOICE +} out_parse_state; + +struct cli_console_fe +{ + pj_cli_front_end base; + pj_pool_t *pool; + pj_cli_sess *sess; + pj_thread_t *input_thread; + pj_bool_t thread_quit; + pj_sem_t *thread_sem; + pj_cli_console_cfg cfg; + + struct async_input_t + { + char *buf; + unsigned maxlen; + pj_sem_t *sem; + } input; +}; + +static void console_write_log(pj_cli_front_end *fe, int level, + const char *data, int len) +{ + struct cli_console_fe * cfe = (struct cli_console_fe *)fe; + + if (cfe->sess->log_level > level) + printf("%.*s", len, data); +} + +static void console_quit(pj_cli_front_end *fe, pj_cli_sess *req) +{ + struct cli_console_fe * cfe = (struct cli_console_fe *)fe; + + PJ_UNUSED_ARG(req); + + pj_assert(cfe); + if (cfe->input_thread) { + cfe->thread_quit = PJ_TRUE; + pj_sem_post(cfe->input.sem); + pj_sem_post(cfe->thread_sem); + } +} + +static void console_destroy(pj_cli_front_end *fe) +{ + struct cli_console_fe * cfe = (struct cli_console_fe *)fe; + + pj_assert(cfe); + console_quit(fe, NULL); + + if (cfe->input_thread) + pj_thread_join(cfe->input_thread); + + if (cfe->input_thread) { + pj_thread_destroy(cfe->input_thread); + cfe->input_thread = NULL; + } + + pj_sem_destroy(cfe->thread_sem); + pj_sem_destroy(cfe->input.sem); + pj_pool_release(cfe->pool); +} + +PJ_DEF(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param) +{ + pj_assert(param); + + param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL; + param->prompt_str.slen = 0; + param->quit_command.slen = 0; +} + +PJ_DEF(pj_status_t) pj_cli_console_create(pj_cli_t *cli, + const pj_cli_console_cfg *param, + pj_cli_sess **p_sess, + pj_cli_front_end **p_fe) +{ + pj_cli_sess *sess; + struct cli_console_fe *fe; + pj_cli_console_cfg cfg; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(cli && p_sess, PJ_EINVAL); + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "console_fe", + PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess); + fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe); + + if (!param) { + pj_cli_console_cfg_default(&cfg); + param = &cfg; + } + sess->fe = &fe->base; + sess->log_level = param->log_level; + sess->op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op); + fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op); + fe->base.cli = cli; + fe->base.type = PJ_CLI_CONSOLE_FRONT_END; + fe->base.op->on_write_log = &console_write_log; + fe->base.op->on_quit = &console_quit; + fe->base.op->on_destroy = &console_destroy; + fe->pool = pool; + fe->sess = sess; + status = pj_sem_create(pool, "console_fe", 0, 1, &fe->thread_sem); + if (status != PJ_SUCCESS) + return status; + + status = pj_sem_create(pool, "console_fe", 0, 1, &fe->input.sem); + if (status != PJ_SUCCESS) + return status; + + pj_cli_register_front_end(cli, &fe->base); + if (param->prompt_str.slen == 0) { + pj_str_t prompt_sign = pj_str(">>> "); + fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, prompt_sign.slen+1); + pj_strcpy(&fe->cfg.prompt_str, &prompt_sign); + } else { + fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, + param->prompt_str.slen+1); + pj_strcpy(&fe->cfg.prompt_str, ¶m->prompt_str); + } + fe->cfg.prompt_str.ptr[fe->cfg.prompt_str.slen] = 0; + + if (param->quit_command.slen) + pj_strdup(fe->pool, &fe->cfg.quit_command, ¶m->quit_command); + + *p_sess = sess; + if (p_fe) + *p_fe = &fe->base; + + return PJ_SUCCESS; +} + +static void send_prompt_str(pj_cli_sess *sess) +{ + pj_str_t send_data; + char data_str[128]; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + send_data.ptr = &data_str[0]; + send_data.slen = 0; + + pj_strcat(&send_data, &fe->cfg.prompt_str); + send_data.ptr[send_data.slen] = 0; + + printf("%s", send_data.ptr); +} + +static void send_err_arg(pj_cli_sess *sess, + const pj_cli_exec_info *info, + const pj_str_t *msg, + pj_bool_t with_return) +{ + pj_str_t send_data; + char data_str[256]; + unsigned len; + unsigned i; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + send_data.ptr = &data_str[0]; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i=0;i<len;++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, msg); + pj_strcat(&send_data, &fe->cfg.prompt_str); + + send_data.ptr[send_data.slen] = 0; + printf("%s", send_data.ptr); +} + +static void send_inv_arg(pj_cli_sess *sess, + const pj_cli_exec_info *info, + pj_bool_t with_return) +{ + static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; + send_err_arg(sess, info, &ERR_MSG, with_return); +} + +static void send_too_many_arg(pj_cli_sess *sess, + const pj_cli_exec_info *info, + pj_bool_t with_return) +{ + static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; + send_err_arg(sess, info, &ERR_MSG, with_return); +} + +static void send_hint_arg(pj_str_t *send_data, + const pj_str_t *desc, + pj_ssize_t cmd_len, + pj_ssize_t max_len) +{ + if ((desc) && (desc->slen > 0)) { + int j; + + for (j=0;j<(max_len-cmd_len);++j) { + pj_strcat2(send_data, " "); + } + pj_strcat2(send_data, " "); + pj_strcat(send_data, desc); + send_data->ptr[send_data->slen] = 0; + printf("%s", send_data->ptr); + send_data->slen = 0; + } +} + +static void send_ambi_arg(pj_cli_sess *sess, + const pj_cli_exec_info *info, + pj_bool_t with_return) +{ + unsigned i; + unsigned len; + pj_str_t send_data; + char data[1028]; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + const pj_cli_hint_info *hint = info->hint; + out_parse_state parse_state = OP_NORMAL; + pj_ssize_t max_length = 0; + pj_ssize_t cmd_length = 0; + const pj_str_t *cmd_desc = 0; + static const pj_str_t sc_type = {"sc", 2}; + static const pj_str_t choice_type = {"choice", 6}; + send_data.ptr = &data[0]; + send_data.slen = 0; + + if (with_return) + pj_strcat2(&send_data, "\r\n"); + + len = fe->cfg.prompt_str.slen + info->err_pos; + + for (i=0;i<len;++i) { + pj_strcat2(&send_data, " "); + } + pj_strcat2(&send_data, "^"); + /* Get the max length of the command name */ + for (i=0;i<info->hint_cnt;++i) { + if ((&hint[i].type) && (hint[i].type.slen > 0)) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + //Additional delimiter " | " + cmd_length += (hint[i].name.slen + 3); + } else { + cmd_length = hint[i].name.slen; + } + } else { + cmd_length = hint[i].name.slen; + } + + if (cmd_length > max_length) { + max_length = cmd_length; + } + } + + cmd_length = 0; + for (i=0;i<info->hint_cnt;++i) { + if ((&hint[i].type) && (hint[i].type.slen > 0)) { + if (pj_stricmp(&hint[i].type, &sc_type) == 0) { + parse_state = OP_SHORTCUT; + } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) { + parse_state = OP_CHOICE; + } else { + parse_state = OP_TYPE; + } + } else { + parse_state = OP_NORMAL; + } + + if ((parse_state != OP_SHORTCUT)) { + if (cmd_desc) { + /* Print data if previous hint is shortcut */ + send_hint_arg(&send_data, cmd_desc, cmd_length, max_length); + cmd_desc = 0; + } + pj_strcat2(&send_data, "\r\n "); + cmd_length = hint[i].name.slen; + } + + switch (parse_state) { + case OP_CHOICE: + pj_strcat2(&send_data, "["); + pj_strcat(&send_data, &hint[i].name); + pj_strcat2(&send_data, "]"); + break; + case OP_TYPE: + pj_strcat2(&send_data, "<"); + pj_strcat(&send_data, &hint[i].type); + pj_strcat2(&send_data, ">"); + break; + case OP_SHORTCUT: + pj_strcat2(&send_data, " | "); + pj_strcat(&send_data, &hint[i].name); + cmd_length += (hint[i].name.slen + 3); + break; + default: + pj_strcat(&send_data, &hint[i].name); + cmd_desc = &hint[i].desc; + break; + } + + if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || + ((i+1) >= info->hint_cnt)) + { + /* Add description info */ + send_hint_arg(&send_data, &hint[i].desc, cmd_length, max_length); + } + } + pj_strcat2(&send_data, "\r\n"); + pj_strcat(&send_data, &fe->cfg.prompt_str); + send_data.ptr[send_data.slen] = 0; + printf("%s", send_data.ptr); +} + +static pj_bool_t handle_hint(pj_cli_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_cmd_val *cmd_val; + pj_cli_exec_info info; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + char *recv_buf = fe->input.buf; + pj_cli_t *cli = sess->fe->cli; + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_hint", + PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + + cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); + + status = pj_cli_sess_parse(sess, recv_buf, cmd_val, + pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_TRUE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_TRUE); + break; + case PJ_CLI_EMISSINGARG: + case PJ_CLI_EAMBIGUOUS: + send_ambi_arg(sess, &info, PJ_TRUE); + break; + case PJ_SUCCESS: + if (info.hint_cnt > 0) { + /* Compelete command */ + send_ambi_arg(sess, &info, PJ_TRUE); + } else { + retval = PJ_FALSE; + } + break; + } + + pj_pool_release(pool); + return retval; +} + +static pj_bool_t handle_exec(pj_cli_sess *sess) +{ + pj_status_t status; + pj_bool_t retval = PJ_TRUE; + + pj_pool_t *pool; + pj_cli_exec_info info; + pj_cli_t *cli = sess->fe->cli; + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + char *recv_buf = fe->input.buf; + + printf("\r\n"); + + pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_exec", + PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, + NULL); + + status = pj_cli_sess_exec(sess, recv_buf, + pool, &info); + + switch (status) { + case PJ_CLI_EINVARG: + send_inv_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_ETOOMANYARGS: + send_too_many_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_EAMBIGUOUS: + case PJ_CLI_EMISSINGARG: + send_ambi_arg(sess, &info, PJ_FALSE); + break; + case PJ_CLI_EEXIT: + retval = PJ_FALSE; + break; + case PJ_SUCCESS: + send_prompt_str(sess); + break; + } + + pj_pool_release(pool); + return retval; +} + +static int readline_thread(void * p) +{ + struct cli_console_fe * fe = (struct cli_console_fe *)p; + + printf("%s", fe->cfg.prompt_str.ptr); + + while (!fe->thread_quit) { + unsigned input_len = 0; + pj_str_t input_str; + char *recv_buf = fe->input.buf; + pj_bool_t is_valid = PJ_TRUE; + + if (fgets(recv_buf, fe->input.maxlen, stdin) == NULL) { + /* + * Be friendly to users who redirect commands into + * program, when file ends, resume with kbd. + * If exit is desired end script with q for quit + */ + /* Reopen stdin/stdout/stderr to /dev/console */ +#if defined(PJ_WIN32) && PJ_WIN32!=0 + if (freopen ("CONIN$", "r", stdin) == NULL) { +#else + if (1) { +#endif + puts("Cannot switch back to console from file redirection"); + if (fe->cfg.quit_command.slen) { + pj_memcpy(recv_buf, fe->cfg.quit_command.ptr, + fe->input.maxlen); + } + recv_buf[fe->cfg.quit_command.slen] = '\0'; + } else { + puts("Switched back to console from file redirection"); + continue; + } + } + + input_str.ptr = recv_buf; + input_str.slen = pj_ansi_strlen(recv_buf); + pj_strrtrim(&input_str); + recv_buf[input_str.slen] = '\n'; + recv_buf[input_str.slen+1] = 0; + if (fe->thread_quit) { + break; + } + input_len = pj_ansi_strlen(fe->input.buf); + if ((input_len > 1) && (fe->input.buf[input_len-2] == '?')) { + fe->input.buf[input_len-1] = 0; + is_valid = handle_hint(fe->sess); + if (!is_valid) + printf("%s", fe->cfg.prompt_str.ptr); + } else { + is_valid = handle_exec(fe->sess); + } + + pj_sem_post(fe->input.sem); + pj_sem_wait(fe->thread_sem); + } + + return 0; +} + +PJ_DEF(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, + char *buf, + unsigned maxlen) +{ + struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; + + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + + fe->input.buf = buf; + fe->input.maxlen = maxlen; + + if (!fe->input_thread) { + pj_status_t status; + + status = pj_thread_create(fe->pool, NULL, &readline_thread, fe, + 0, 0, &fe->input_thread); + if (status != PJ_SUCCESS) + return status; + } else { + /* Wake up readline thread */ + pj_sem_post(fe->thread_sem); + } + + pj_sem_wait(fe->input.sem); + + return (pj_cli_is_quitting(fe->base.cli)? PJ_CLI_EEXIT : PJ_SUCCESS); +} |