/* $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 #include #include #include #include #include #include #include #include /** * 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, pj_size_t len) { struct cli_console_fe * cfe = (struct cli_console_fe *)fe; if (cfe->sess->log_level > level) printf("%.*s", (int)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; 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]; pj_size_t len; unsigned i; struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; send_data.ptr = data_str; 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;icfg.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; pj_size_t 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; static const pj_str_t sc_type = {"sc", 2}; static const pj_str_t choice_type = {"choice", 6}; send_data.ptr = data; 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;ihint_cnt;++i) { if (hint[i].type.slen > 0) { if (pj_stricmp(&hint[i].type, &sc_type) == 0) { if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) { cmd_length += (hint[i].name.slen + 3); } else { cmd_length = hint[i].name.slen; } } 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;ihint_cnt;++i) { if (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) { 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: /* Format : "Command | sc | description" */ { cmd_length += hint[i].name.slen; if ((i > 0) && (!pj_stricmp(&hint[i-1].desc, &hint[i].desc))) { pj_strcat2(&send_data, " | "); cmd_length += 3; } else { pj_strcat2(&send_data, "\r\n "); } pj_strcat(&send_data, &hint[i].name); } break; default: pj_strcat(&send_data, &hint[i].name); break; } if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || ((i+1) >= info->hint_cnt) || (pj_strncmp(&hint[i].desc, &hint[i+1].desc, hint[i].desc.slen))) { /* Add description info */ send_hint_arg(&send_data, &hint[i].desc, cmd_length, max_length); cmd_length = 0; } } 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) { pj_size_t 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) || \ (defined(PJ_WIN64) && PJ_WIN64!=0)) && \ (!defined(PJ_WIN32_WINCE) || PJ_WIN32_WINCE==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); }