summaryrefslogtreecommitdiff
path: root/pjlib-util/src/pjlib-util/cli_console.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjlib-util/src/pjlib-util/cli_console.c')
-rw-r--r--pjlib-util/src/pjlib-util/cli_console.c540
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, &param->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, &param->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);
+}