From 024789d21681848c4eff7a72e824f0b7b0791759 Mon Sep 17 00:00:00 2001 From: Riza Sulistyo Date: Fri, 3 May 2013 08:47:14 +0000 Subject: Re #1643: - Modification to shortcut handling(execution&display). - Add exact match check to the parse input command process. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4513 74dad513-b988-da41-8d7b-12977e46ad98 --- pjlib-util/src/pjlib-util/cli.c | 2559 ++++++++++++++++--------------- pjlib-util/src/pjlib-util/cli_console.c | 45 +- pjlib-util/src/pjlib-util/cli_telnet.c | 68 +- 3 files changed, 1417 insertions(+), 1255 deletions(-) (limited to 'pjlib-util') diff --git a/pjlib-util/src/pjlib-util/cli.c b/pjlib-util/src/pjlib-util/cli.c index 68cd6f04..7b3548bb 100644 --- a/pjlib-util/src/pjlib-util/cli.c +++ b/pjlib-util/src/pjlib-util/cli.c @@ -1,1206 +1,1353 @@ -/* $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 -#include -#include - -#define CMD_HASH_TABLE_SIZE 63 /* Hash table size */ - -#define CLI_CMD_CHANGE_LOG 30000 -#define CLI_CMD_EXIT 30001 - -#if 1 - /* Enable some tracing */ - #define THIS_FILE "cli.c" - #define TRACE_(arg) PJ_LOG(3,arg) -#else - #define TRACE_(arg) -#endif - -/** - * This structure describes the full specification of a CLI command. A CLI - * command mainly consists of the name of the command, zero or more arguments, - * and a callback function to be called to execute the command. - * - * Application can create this specification by forming an XML document and - * calling pj_cli_add_cmd_from_xml() to instantiate the spec. A sample XML - * document containing a command spec is as follows: - * - \verbatim - - - - - - \endverbatim - */ -struct pj_cli_cmd_spec -{ - /** - * To make list of child cmds. - */ - PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); - - /** - * Command ID assigned to this command by the application during command - * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is - * a command group and it can't be executed. - */ - pj_cli_cmd_id id; - - /** - * The command name. - */ - pj_str_t name; - - /** - * The full description of the command. - */ - pj_str_t desc; - - /** - * Number of optional shortcuts - */ - unsigned sc_cnt; - - /** - * Optional array of shortcuts, if any. Shortcut is a short name version - * of the command. If the command doesn't have any shortcuts, this - * will be initialized to NULL. - */ - pj_str_t *sc; - - /** - * The command handler, to be executed when a command matching this command - * specification is invoked by the end user. The value may be NULL if this - * is a command group. - */ - pj_cli_cmd_handler handler; - - /** - * Number of arguments. - */ - unsigned arg_cnt; - - /** - * Array of arguments. - */ - pj_cli_arg_spec *arg; - - /** - * Child commands, if any. A command will only have subcommands if it is - * a group. If the command doesn't have subcommands, this field will be - * initialized with NULL. - */ - pj_cli_cmd_spec *sub_cmd; -}; - -struct pj_cli_t -{ - pj_pool_t *pool; /* Pool to allocate memory from */ - pj_cli_cfg cfg; /* CLI configuration */ - pj_cli_cmd_spec root; /* Root of command tree structure */ - pj_cli_front_end fe_head; /* List of front-ends */ - pj_hash_table_t *cmd_name_hash; /* Command name hash table */ - pj_hash_table_t *cmd_id_hash; /* Command id hash table */ - - pj_bool_t is_quitting; - pj_bool_t is_restarting; -}; - -/** - * Reserved command id constants. - */ -typedef enum pj_cli_std_cmd_id -{ - /** - * Constant to indicate an invalid command id. - */ - PJ_CLI_INVALID_CMD_ID = -1, - - /** - * A special command id to indicate that a command id denotes - * a command group. - */ - PJ_CLI_CMD_ID_GROUP = -2 - -} pj_cli_std_cmd_id; - -/** - * This describes the type of an argument (pj_cli_arg_spec). - */ -typedef enum pj_cli_arg_type -{ - /** - * Unformatted string. - */ - PJ_CLI_ARG_TEXT, - - /** - * An integral number. - */ - PJ_CLI_ARG_INT, - - /** - * Choice type - */ - PJ_CLI_ARG_CHOICE - -} pj_cli_arg_type; - -struct arg_type -{ - const pj_str_t msg; -} arg_type[3] = -{ - {{"Text", 4}}, - {{"Int", 3}}, - {{"Choice", 6}} -}; - -/** - * This structure describe the specification of a command argument. - */ -struct pj_cli_arg_spec -{ - /** - * Argument id - */ - pj_cli_arg_id id; - - /** - * Argument name. - */ - pj_str_t name; - - /** - * Helpful description of the argument. This text will be used when - * displaying help texts for the command/argument. - */ - pj_str_t desc; - - /** - * Argument type, which will be used for rendering the argument and - * to perform basic validation against an input value. - */ - pj_cli_arg_type type; - - /** - * Argument status - */ - pj_bool_t optional; - - /** - * Validate choice values - */ - pj_bool_t validate; - - /** - * Static Choice Values count - */ - unsigned stat_choice_cnt; - - /** - * Static Choice Values - */ - pj_cli_arg_choice_val *stat_choice_val; - - /** - * Argument callback to get the valid values - */ - pj_cli_get_dyn_choice get_dyn_choice; - -}; - -/** - * This describe the parse mode of the command line - */ -typedef enum pj_cli_parse_mode { - PARSE_NONE, - PARSE_COMPLETION, /* Complete the command line */ - PARSE_NEXT_AVAIL, /* Find the next available command line */ - PARSE_EXEC /* Exec the command line */ -} pj_cli_parse_mode; - -/** - * This is used to get the matched command/argument from the - * command/argument structure. - * - * @param sess The session on which the command is execute on. - * @param cmd The active command. - * @param cmd_val The command value to match. - * @param argc The number of argument that the - * current active command have. - * @param pool The memory pool to allocate memory. - * @param get_cmd Set true to search matching command from sub command. - * @param parse_mode The parse mode. - * @param p_cmd The command that mathes the command value. - * @param info The output information containing any hints for - * matching command/arg. - * @return This function return the status of the - * matching process.Please see the return value - * of pj_cli_sess_parse() for possible return values. - */ -static pj_status_t get_available_cmds(pj_cli_sess *sess, - pj_cli_cmd_spec *cmd, - pj_str_t *cmd_val, - unsigned argc, - pj_pool_t *pool, - pj_bool_t get_cmd, - pj_cli_parse_mode parse_mode, - pj_cli_cmd_spec **p_cmd, - pj_cli_exec_info *info); - -PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) -{ - return cmd->id; -} - -PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) -{ - pj_assert(param); - pj_bzero(param, sizeof(*param)); - pj_strset2(¶m->name, ""); -} - -PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param) -{ - pj_assert(param); - pj_bzero(param, sizeof(*param)); - param->err_pos = -1; - param->cmd_id = PJ_CLI_INVALID_CMD_ID; - param->cmd_ret = PJ_SUCCESS; -} - -PJ_DEF(void) pj_cli_write_log(pj_cli_t *cli, - int level, - const char *buffer, - int len) -{ - struct pj_cli_front_end *fe; - - pj_assert(cli); - - fe = cli->fe_head.next; - while (fe != &cli->fe_head) { - if (fe->op && fe->op->on_write_log) - (*fe->op->on_write_log)(fe, level, buffer, len); - fe = fe->next; - } -} - -PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess, - const char *buffer, - int len) -{ - struct pj_cli_front_end *fe; - - pj_assert(sess); - - fe = sess->fe; - if (fe->op && fe->op->on_write_log) - (*fe->op->on_write_log)(fe, 0, buffer, len); -} - -/* Command handler */ -static pj_status_t cmd_handler(pj_cli_cmd_val *cval) -{ - unsigned level; - - switch(cval->cmd->id) { - case CLI_CMD_CHANGE_LOG: - level = pj_strtoul(&cval->argv[1]); - if (!level && cval->argv[1].slen > 0 && (cval->argv[1].ptr[0] < '0' || - cval->argv[1].ptr[0] > '9')) - { - return PJ_CLI_EINVARG; - } - cval->sess->log_level = level; - return PJ_SUCCESS; - case CLI_CMD_EXIT: - pj_cli_sess_end_session(cval->sess); - return PJ_CLI_EEXIT; - default: - return PJ_SUCCESS; - } -} - -PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, - pj_cli_t **p_cli) -{ - pj_pool_t *pool; - pj_cli_t *cli; - unsigned i; - - /* This is an example of the command structure */ - char* cmd_xmls[] = { - "" - " " - "", - "" - "", - }; - - PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL); - - pool = pj_pool_create(cfg->pf, "cli", PJ_CLI_POOL_SIZE, - PJ_CLI_POOL_INC, NULL); - if (!pool) - return PJ_ENOMEM; - cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t); - - pj_memcpy(&cli->cfg, cfg, sizeof(*cfg)); - cli->pool = pool; - pj_list_init(&cli->fe_head); - - cli->cmd_name_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); - cli->cmd_id_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); - - cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec); - pj_list_init(cli->root.sub_cmd); - - /* Register some standard commands. */ - for (i = 0; i < sizeof(cmd_xmls)/sizeof(cmd_xmls[0]); i++) { - pj_str_t xml = pj_str(cmd_xmls[i]); - - if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, - &cmd_handler, NULL, NULL) != PJ_SUCCESS) - { - TRACE_((THIS_FILE, "Failed to add command #%d", i)); - } - } - - *p_cli = cli; - - return PJ_SUCCESS; -} - -PJ_DEF(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli) -{ - PJ_ASSERT_RETURN(cli, NULL); - - return &cli->cfg; -} - -PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli, - pj_cli_front_end *fe) -{ - pj_list_push_back(&cli->fe_head, fe); -} - -PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, - pj_bool_t restart) -{ - pj_cli_front_end *fe; - - pj_assert(cli); - if (cli->is_quitting) - return; - - cli->is_quitting = PJ_TRUE; - cli->is_restarting = restart; - - fe = cli->fe_head.next; - while (fe != &cli->fe_head) { - if (fe->op && fe->op->on_quit) - (*fe->op->on_quit)(fe, req); - fe = fe->next; - } -} - -PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli) -{ - PJ_ASSERT_RETURN(cli, PJ_FALSE); - - return cli->is_quitting; -} - -PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli) -{ - PJ_ASSERT_RETURN(cli, PJ_FALSE); - - return cli->is_restarting; -} - -PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli) -{ - pj_cli_front_end *fe; - - if (!cli) - return; - - if (!pj_cli_is_quitting(cli)) - pj_cli_quit(cli, NULL, PJ_FALSE); - - fe = cli->fe_head.next; - while (fe != &cli->fe_head) { - pj_list_erase(fe); - if (fe->op && fe->op->on_destroy) - (*fe->op->on_destroy)(fe); - - fe = cli->fe_head.next; - } - cli->is_quitting = PJ_FALSE; - pj_pool_release(cli->pool); -} - -PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) -{ - pj_assert(sess); - - if (sess->op && sess->op->destroy) - (*sess->op->destroy)(sess); -} - -/* Syntax error handler for parser. */ -static void on_syntax_error(pj_scanner *scanner) -{ - PJ_UNUSED_ARG(scanner); - PJ_THROW(PJ_EINVAL); -} - -/* Check if command already added to command hash */ -static pj_bool_t cmd_name_exists(pj_cli_t *cli, pj_cli_cmd_spec *group, - pj_str_t *cmd) -{ - pj_str_t cmd_val; - char cmd_ptr[64]; - - cmd_val.ptr = &cmd_ptr[0]; - cmd_val.slen = 0; - - if (group) { - char cmd_str[16]; - pj_ansi_sprintf(&cmd_str[0], "%d", group->id); - pj_strcat2(&cmd_val, &cmd_str[0]); - } - pj_strcat(&cmd_val, cmd); - return (pj_hash_get(cli->cmd_name_hash, - cmd_val.ptr, cmd_val.slen, NULL) != 0); -} - -/* Add command to the command hash */ -static void add_cmd_name(pj_cli_t *cli, pj_cli_cmd_spec *group, - pj_cli_cmd_spec *cmd, pj_str_t *cmd_name) -{ - pj_str_t cmd_val; - pj_str_t add_cmd; - char cmd_ptr[64]; - - cmd_val.ptr = &cmd_ptr[0]; - cmd_val.slen = 0; - - if (group) { - pj_strcat(&cmd_val, &group->name); - } - pj_strcat(&cmd_val, cmd_name); - pj_strdup(cli->pool, &add_cmd, &cmd_val); - - pj_hash_set(cli->pool, cli->cmd_name_hash, - cmd_val.ptr, cmd_val.slen, 0, cmd); -} - -/** - * This method is to parse and add the choice type - * argument values to command structure. - **/ -static pj_status_t add_choice_node(pj_cli_t *cli, - pj_xml_node *xml_node, - pj_cli_arg_spec *arg, - pj_cli_get_dyn_choice get_choice) -{ - pj_xml_node *choice_node; - pj_xml_node *sub_node; - pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL]; - pj_status_t status = PJ_SUCCESS; - - sub_node = xml_node; - arg->type = PJ_CLI_ARG_CHOICE; - arg->get_dyn_choice = get_choice; - - choice_node = sub_node->node_head.next; - while (choice_node != (pj_xml_node*)&sub_node->node_head) { - pj_xml_attr *choice_attr; - unsigned *stat_cnt = &arg->stat_choice_cnt; - pj_cli_arg_choice_val *choice_val = &choice_values[*stat_cnt]; - pj_bzero(choice_val, sizeof(*choice_val)); - - choice_attr = choice_node->attr_head.next; - while (choice_attr != &choice_node->attr_head) { - if (!pj_stricmp2(&choice_attr->name, "value")) { - pj_strassign(&choice_val->value, &choice_attr->value); - } else if (!pj_stricmp2(&choice_attr->name, "desc")) { - pj_strassign(&choice_val->desc, &choice_attr->value); - } - choice_attr = choice_attr->next; - } - if (++(*stat_cnt) >= PJ_CLI_MAX_CHOICE_VAL) - break; - choice_node = choice_node->next; - } - if (arg->stat_choice_cnt > 0) { - unsigned i; - - arg->stat_choice_val = (pj_cli_arg_choice_val *) - pj_pool_zalloc(cli->pool, - arg->stat_choice_cnt * - sizeof(pj_cli_arg_choice_val)); - - for (i = 0; i < arg->stat_choice_cnt; i++) { - pj_strdup(cli->pool, &arg->stat_choice_val[i].value, - &choice_values[i].value); - pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, - &choice_values[i].desc); - } - } - return status; -} - -/** - * This method is to parse and add the argument attribute to command structure. - **/ -static pj_status_t add_arg_node(pj_cli_t *cli, - pj_xml_node *xml_node, - pj_cli_cmd_spec *cmd, - pj_cli_arg_spec *arg, - pj_cli_get_dyn_choice get_choice) -{ - pj_xml_attr *attr; - pj_status_t status = PJ_SUCCESS; - pj_xml_node *sub_node = xml_node; - - if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) - return PJ_CLI_ETOOMANYARGS; - - pj_bzero(arg, sizeof(*arg)); - attr = sub_node->attr_head.next; - arg->optional = PJ_FALSE; - arg->validate = PJ_TRUE; - while (attr != &sub_node->attr_head) { - if (!pj_stricmp2(&attr->name, "name")) { - pj_strassign(&arg->name, &attr->value); - } else if (!pj_stricmp2(&attr->name, "id")) { - arg->id = pj_strtol(&attr->value); - } else if (!pj_stricmp2(&attr->name, "type")) { - if (!pj_stricmp2(&attr->value, "text")) { - arg->type = PJ_CLI_ARG_TEXT; - } else if (!pj_stricmp2(&attr->value, "int")) { - arg->type = PJ_CLI_ARG_INT; - } else if (!pj_stricmp2(&attr->value, "choice")) { - /* Get choice value */ - add_choice_node(cli, xml_node, arg, get_choice); - } - } else if (!pj_stricmp2(&attr->name, "desc")) { - pj_strassign(&arg->desc, &attr->value); - } else if (!pj_stricmp2(&attr->name, "optional")) { - if (!pj_strcmp2(&attr->value, "1")) { - arg->optional = PJ_TRUE; - } - } else if (!pj_stricmp2(&attr->name, "validate")) { - if (!pj_strcmp2(&attr->value, "1")) { - arg->validate = PJ_TRUE; - } else { - arg->validate = PJ_FALSE; - } - } - attr = attr->next; - } - cmd->arg_cnt++; - return status; -} - -/** - * This method is to parse and add the command attribute to command structure. - **/ -static pj_status_t add_cmd_node(pj_cli_t *cli, - pj_cli_cmd_spec *group, - pj_xml_node *xml_node, - pj_cli_cmd_handler handler, - pj_cli_cmd_spec **p_cmd, - pj_cli_get_dyn_choice get_choice) -{ - pj_xml_node *root = xml_node; - pj_xml_attr *attr; - pj_xml_node *sub_node; - pj_cli_cmd_spec *cmd; - pj_cli_arg_spec args[PJ_CLI_MAX_ARGS]; - pj_str_t sc[PJ_CLI_MAX_SHORTCUTS]; - pj_status_t status = PJ_SUCCESS; - - if (pj_stricmp2(&root->name, "CMD")) - return PJ_EINVAL; - - /* Initialize the command spec */ - cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec); - - /* Get the command attributes */ - attr = root->attr_head.next; - while (attr != &root->attr_head) { - if (!pj_stricmp2(&attr->name, "name")) { - pj_strltrim(&attr->value); - if (!attr->value.slen || - cmd_name_exists(cli, group, &attr->value)) - { - return PJ_CLI_EBADNAME; - } - pj_strdup(cli->pool, &cmd->name, &attr->value); - } else if (!pj_stricmp2(&attr->name, "id")) { - pj_bool_t is_valid = PJ_FALSE; - if (attr->value.slen) { - pj_cli_cmd_id cmd_id = pj_strtol(&attr->value); - if (!pj_hash_get(cli->cmd_id_hash, &cmd_id, - sizeof(pj_cli_cmd_id), NULL)) - is_valid = PJ_TRUE; - } - if (!is_valid) - return PJ_CLI_EBADID; - cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value); - } else if (!pj_stricmp2(&attr->name, "sc")) { - pj_scanner scanner; - pj_str_t str; - - PJ_USE_EXCEPTION; - - pj_scan_init(&scanner, attr->value.ptr, attr->value.slen, - PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); - - PJ_TRY { - while (!pj_scan_is_eof(&scanner)) { - pj_scan_get_until_ch(&scanner, ',', &str); - pj_strrtrim(&str); - if (!pj_scan_is_eof(&scanner)) - pj_scan_advance_n(&scanner, 1, PJ_TRUE); - if (!str.slen) - continue; - - if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) { - PJ_THROW(PJ_CLI_ETOOMANYARGS); - } - /* Check whether the shortcuts are already used */ - if (cmd_name_exists(cli, group, &str)) { - PJ_THROW(PJ_CLI_EBADNAME); - } - - pj_strassign(&sc[cmd->sc_cnt++], &str); - } - } - PJ_CATCH_ANY { - pj_scan_fini(&scanner); - return (PJ_GET_EXCEPTION()); - } - PJ_END; - - } else if (!pj_stricmp2(&attr->name, "desc")) { - pj_strdup(cli->pool, &cmd->desc, &attr->value); - } - attr = attr->next; - } - - /* Get the command childs/arguments */ - sub_node = root->node_head.next; - while (sub_node != (pj_xml_node*)&root->node_head) { - if (!pj_stricmp2(&sub_node->name, "CMD")) { - status = add_cmd_node(cli, cmd, sub_node, handler, NULL, - get_choice); - if (status != PJ_SUCCESS) - return status; - } else if (!pj_stricmp2(&sub_node->name, "ARG")) { - /* Get argument attribute */ - status = add_arg_node(cli, sub_node, - cmd, &args[cmd->arg_cnt], - get_choice); - - if (status != PJ_SUCCESS) - return status; - } - sub_node = sub_node->next; - } - - if (!cmd->name.slen) - return PJ_CLI_EBADNAME; - - if (!cmd->id) - return PJ_CLI_EBADID; - - if (cmd->arg_cnt) { - unsigned i; - - cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt * - sizeof(pj_cli_arg_spec)); - - for (i = 0; i < cmd->arg_cnt; i++) { - pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name); - pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); - cmd->arg[i].id = args[i].id; - cmd->arg[i].type = args[i].type; - cmd->arg[i].optional = args[i].optional; - cmd->arg[i].validate = args[i].validate; - cmd->arg[i].get_dyn_choice = args[i].get_dyn_choice; - cmd->arg[i].stat_choice_cnt = args[i].stat_choice_cnt; - cmd->arg[i].stat_choice_val = args[i].stat_choice_val; - } - } - - if (cmd->sc_cnt) { - unsigned i; - - cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt * - sizeof(pj_str_t)); - for (i = 0; i < cmd->sc_cnt; i++) { - pj_strdup(cli->pool, &cmd->sc[i], &sc[i]); - add_cmd_name(cli, group, cmd, &sc[i]); - } - } - - add_cmd_name(cli, group, cmd, &cmd->name); - pj_hash_set(cli->pool, cli->cmd_id_hash, - &cmd->id, sizeof(pj_cli_cmd_id), 0, cmd); - - cmd->handler = handler; - - if (group) { - if (!group->sub_cmd) { - group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); - pj_list_init(group->sub_cmd); - } - pj_list_push_back(group->sub_cmd, cmd); - } else { - pj_list_push_back(cli->root.sub_cmd, cmd); - } - - if (p_cmd) - *p_cmd = cmd; - - return status; -} - -PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli, - pj_cli_cmd_spec *group, - const pj_str_t *xml, - pj_cli_cmd_handler handler, - pj_cli_cmd_spec **p_cmd, - pj_cli_get_dyn_choice get_choice) -{ - pj_pool_t *pool; - pj_xml_node *root; - pj_status_t status = PJ_SUCCESS; - - PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); - - /* Parse the xml */ - pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); - if (!pool) - return PJ_ENOMEM; - - root = pj_xml_parse(pool, xml->ptr, xml->slen); - if (!root) { - TRACE_((THIS_FILE, "Error: unable to parse XML")); - pj_pool_release(pool); - return PJ_CLI_EBADXML; - } - status = add_cmd_node(cli, group, root, handler, p_cmd, get_choice); - pj_pool_release(pool); - return status; -} - -PJ_DEF(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess, - char *cmdline, - pj_cli_cmd_val *val, - pj_pool_t *pool, - pj_cli_exec_info *info) -{ - pj_scanner scanner; - pj_str_t str; - int len; - pj_cli_cmd_spec *cmd; - pj_cli_cmd_spec *next_cmd; - pj_status_t status = PJ_SUCCESS; - pj_cli_parse_mode parse_mode = PARSE_NONE; - - PJ_USE_EXCEPTION; - - PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); - - PJ_UNUSED_ARG(pool); - - str.slen = 0; - pj_cli_exec_info_default(info); - - /* Set the parse mode based on the latest char. */ - len = pj_ansi_strlen(cmdline); - if (len > 0 && ((cmdline[len - 1] == '\r')||(cmdline[len - 1] == '\n'))) { - cmdline[--len] = 0; - parse_mode = PARSE_EXEC; - } else if (len > 0 && - (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) - { - cmdline[--len] = 0; - if (len == 0) { - parse_mode = PARSE_NEXT_AVAIL; - } else { - if (cmdline[len - 1] == ' ') - parse_mode = PARSE_NEXT_AVAIL; - else - parse_mode = PARSE_COMPLETION; - } - } - val->argc = 0; - info->err_pos = 0; - cmd = &sess->fe->cli->root; - if (len > 0) { - pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, - &on_syntax_error); - PJ_TRY { - val->argc = 0; - while (!pj_scan_is_eof(&scanner)) { - info->err_pos = scanner.curptr - scanner.begin; - if (*scanner.curptr == '\'' || *scanner.curptr == '"' || - *scanner.curptr == '[' || *scanner.curptr == '{') - { - pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str); - /* Remove the quotes */ - str.ptr++; - str.slen -= 2; - } else { - pj_scan_get_until_chr(&scanner, " \t\r\n", &str); - } - ++val->argc; - if (val->argc == PJ_CLI_MAX_ARGS) - PJ_THROW(PJ_CLI_ETOOMANYARGS); - - status = get_available_cmds(sess, cmd, &str, val->argc-1, - pool, PJ_TRUE, parse_mode, - &next_cmd, info); - - if (status != PJ_SUCCESS) - PJ_THROW(status); - - if (cmd != next_cmd) { - /* Found new command, set it as the active command */ - cmd = next_cmd; - val->argc = 1; - val->cmd = cmd; - } - if (parse_mode == PARSE_EXEC) - pj_strassign(&val->argv[val->argc-1], &info->hint->name); - else - pj_strassign(&val->argv[val->argc-1], &str); - - } - if (!pj_scan_is_eof(&scanner)) - PJ_THROW(PJ_CLI_EINVARG); - - } - PJ_CATCH_ANY { - pj_scan_fini(&scanner); - return PJ_GET_EXCEPTION(); - } - PJ_END; - } - - if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) { - /* If exec mode, just get the matching argument */ - status = get_available_cmds(sess, cmd, NULL, val->argc, pool, - (parse_mode==PARSE_NEXT_AVAIL), - parse_mode, - NULL, info); - if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) { - pj_str_t data = pj_str(cmdline); - pj_strrtrim(&data); - data.ptr[data.slen] = ' '; - data.ptr[data.slen+1] = 0; - - info->err_pos = pj_ansi_strlen(cmdline); - } - } - - val->sess = sess; - return status; -} - -PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, - char *cmdline, - pj_pool_t *pool, - pj_cli_exec_info *info) -{ - pj_cli_cmd_val val; - pj_status_t status; - pj_cli_exec_info einfo; - pj_str_t cmd; - - PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL); - - PJ_UNUSED_ARG(pool); - - cmd.ptr = cmdline; - cmd.slen = pj_ansi_strlen(cmdline); - - if (pj_strtrim(&cmd)->slen == 0) - return PJ_SUCCESS; - - if (!info) - info = &einfo; - - status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); - if (status != PJ_SUCCESS) - return status; - - if ((val.argc > 0) && (val.cmd->handler)) { - info->cmd_ret = (*val.cmd->handler)(&val); - if (info->cmd_ret == PJ_CLI_EINVARG || - info->cmd_ret == PJ_CLI_EEXIT) - { - return info->cmd_ret; - } - } - - return PJ_SUCCESS; -} - -static pj_status_t insert_new_hint(pj_pool_t *pool, - const pj_str_t *name, - const pj_str_t *desc, - const pj_str_t *type, - pj_cli_exec_info *info) -{ - pj_cli_hint_info *hint; - PJ_ASSERT_RETURN(pool && info, PJ_EINVAL); - PJ_ASSERT_RETURN((info->hint_cnt < PJ_CLI_MAX_HINTS), PJ_EINVAL); - hint = &info->hint[info->hint_cnt]; - - pj_strdup(pool, &hint->name, name); - - if (desc && (desc->slen > 0)) { - pj_strdup(pool, &hint->desc, desc); - } else { - hint->desc.slen = 0; - } - - if (type && (type->slen > 0)) { - pj_strdup(pool, &hint->type, type); - } else { - hint->type.slen = 0; - } - - ++info->hint_cnt; - return PJ_SUCCESS; -} - -static pj_status_t get_match_cmds(pj_cli_cmd_spec *cmd, - const pj_str_t *cmd_val, - pj_pool_t *pool, - pj_cli_cmd_spec **p_cmd, - pj_cli_exec_info *info) -{ - pj_status_t status = PJ_SUCCESS; - PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL); - - if (p_cmd) - *p_cmd = cmd; - - /* Get matching command */ - if (cmd->sub_cmd) { - pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next; - while (child_cmd != cmd->sub_cmd) { - unsigned i; - pj_bool_t found = PJ_FALSE; - if (!pj_strncmp(&child_cmd->name, cmd_val, cmd_val->slen)) { - status = insert_new_hint(pool, &child_cmd->name, - &child_cmd->desc, NULL, info); - if (status != PJ_SUCCESS) - return status; - - found = PJ_TRUE; - } - for (i=0; i < child_cmd->sc_cnt; ++i) { - static const pj_str_t SHORTCUT = {"SC", 2}; - pj_str_t *sc = &child_cmd->sc[i]; - PJ_ASSERT_RETURN(sc, PJ_EINVAL); - - if (!pj_strncmp(sc, cmd_val, cmd_val->slen)) { - status = insert_new_hint(pool, sc, &child_cmd->desc, - &SHORTCUT, info); - if (status != PJ_SUCCESS) - return status; - - found = PJ_TRUE; - } - } - if (found && p_cmd) - *p_cmd = child_cmd; - - child_cmd = child_cmd->next; - } - } - return status; -} - -static pj_status_t get_match_args(pj_cli_sess *sess, - pj_cli_cmd_spec *cmd, - const pj_str_t *cmd_val, - unsigned argc, - pj_pool_t *pool, - pj_cli_parse_mode parse_mode, - pj_cli_exec_info *info) -{ - pj_cli_arg_spec *arg; - pj_status_t status = PJ_SUCCESS; - - PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL); - - if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) { - if (cmd_val->slen > 0) - return PJ_CLI_ETOOMANYARGS; - else - return PJ_SUCCESS; - } - - if (cmd->arg_cnt > 0) { - arg = &cmd->arg[argc-1]; - PJ_ASSERT_RETURN(arg, PJ_EINVAL); - if (arg->type == PJ_CLI_ARG_CHOICE) { - unsigned j; - - if ((parse_mode == PARSE_EXEC) && (!arg->validate)) { - /* If no validation needed, then insert the values */ - status = insert_new_hint(pool, - cmd_val, - NULL, - NULL, - info); - return status; - } - - for (j=0; j < arg->stat_choice_cnt; ++j) { - pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j]; - - PJ_ASSERT_RETURN(choice_val, PJ_EINVAL); - - if (!pj_strncmp(&choice_val->value, cmd_val, cmd_val->slen)) { - status = insert_new_hint(pool, - &choice_val->value, - &choice_val->desc, - &arg_type[PJ_CLI_ARG_CHOICE].msg, - info); - if (status != PJ_SUCCESS) - return status; - } - } - if (arg->get_dyn_choice) { - pj_cli_dyn_choice_param dyn_choice_param; - static pj_str_t choice_str = {"choice", 6}; - - /* Get the dynamic choice values */ - dyn_choice_param.sess = sess; - dyn_choice_param.cmd = cmd; - dyn_choice_param.arg_id = arg->id; - dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL; - dyn_choice_param.pool = pool; - dyn_choice_param.cnt = 0; - - (*arg->get_dyn_choice)(&dyn_choice_param); - for (j=0; j < dyn_choice_param.cnt; ++j) { - pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j]; - if (!pj_strncmp(&choice->value, cmd_val, cmd_val->slen)) { - pj_strassign(&info->hint[info->hint_cnt].name, - &choice->value); - pj_strassign(&info->hint[info->hint_cnt].type, - &choice_str); - pj_strassign(&info->hint[info->hint_cnt].desc, - &choice->desc); - if ((++info->hint_cnt) >= PJ_CLI_MAX_HINTS) - break; - } - } - if ((info->hint_cnt == 0) && (!arg->optional)) - return PJ_CLI_EMISSINGARG; - } - } else { - if (cmd_val->slen == 0) { - if (info->hint_cnt == 0) { - if (!((parse_mode == PARSE_EXEC) && (arg->optional))) { - /* For exec mode,no need to insert hint if optional */ - status = insert_new_hint(pool, - &arg->name, - &arg->desc, - &arg_type[arg->type].msg, - info); - if (status != PJ_SUCCESS) - return status; - } - if (!arg->optional) - return PJ_CLI_EMISSINGARG; - } - } else { - return insert_new_hint(pool, cmd_val, NULL, NULL, info); - } - } - } - return status; -} - -static pj_status_t get_available_cmds(pj_cli_sess *sess, - pj_cli_cmd_spec *cmd, - pj_str_t *cmd_val, - unsigned argc, - pj_pool_t *pool, - pj_bool_t get_cmd, - pj_cli_parse_mode parse_mode, - pj_cli_cmd_spec **p_cmd, - pj_cli_exec_info *info) -{ - pj_status_t status = PJ_SUCCESS; - pj_str_t *prefix; - pj_str_t EMPTY_STR = {NULL, 0}; - - prefix = cmd_val?(pj_strtrim(cmd_val)):(&EMPTY_STR); - - info->hint_cnt = 0; - - if (get_cmd) - status = get_match_cmds(cmd, prefix, pool, p_cmd, info); - if (argc > 0) - status = get_match_args(sess, cmd, prefix, argc, - pool, parse_mode, info); - - if (status == PJ_SUCCESS) { - if (prefix->slen > 0) { - if (info->hint_cnt == 0) { - status = PJ_CLI_EINVARG; - } else if (info->hint_cnt > 1) { - status = PJ_CLI_EAMBIGUOUS; - } - } else { - if (info->hint_cnt > 0) - status = PJ_CLI_EAMBIGUOUS; - } - } - - return status; -} - +/* $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 +#include +#include + +#define CMD_HASH_TABLE_SIZE 63 /* Hash table size */ + +#define CLI_CMD_CHANGE_LOG 30000 +#define CLI_CMD_EXIT 30001 + +#define MAX_CMD_HASH_NAME_LENGTH 64 +#define MAX_CMD_ID_LENGTH 16 + +#if 1 + /* Enable some tracing */ + #define THIS_FILE "cli.c" + #define TRACE_(arg) PJ_LOG(3,arg) +#else + #define TRACE_(arg) +#endif + +/** + * This structure describes the full specification of a CLI command. A CLI + * command mainly consists of the name of the command, zero or more arguments, + * and a callback function to be called to execute the command. + * + * Application can create this specification by forming an XML document and + * calling pj_cli_add_cmd_from_xml() to instantiate the spec. A sample XML + * document containing a command spec is as follows: + * + \verbatim + + + + + + \endverbatim + */ +struct pj_cli_cmd_spec +{ + /** + * To make list of child cmds. + */ + PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); + + /** + * Command ID assigned to this command by the application during command + * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is + * a command group and it can't be executed. + */ + pj_cli_cmd_id id; + + /** + * The command name. + */ + pj_str_t name; + + /** + * The full description of the command. + */ + pj_str_t desc; + + /** + * Number of optional shortcuts + */ + unsigned sc_cnt; + + /** + * Optional array of shortcuts, if any. Shortcut is a short name version + * of the command. If the command doesn't have any shortcuts, this + * will be initialized to NULL. + */ + pj_str_t *sc; + + /** + * The command handler, to be executed when a command matching this command + * specification is invoked by the end user. The value may be NULL if this + * is a command group. + */ + pj_cli_cmd_handler handler; + + /** + * Number of arguments. + */ + unsigned arg_cnt; + + /** + * Array of arguments. + */ + pj_cli_arg_spec *arg; + + /** + * Child commands, if any. A command will only have subcommands if it is + * a group. If the command doesn't have subcommands, this field will be + * initialized with NULL. + */ + pj_cli_cmd_spec *sub_cmd; +}; + +struct pj_cli_t +{ + pj_pool_t *pool; /* Pool to allocate memory from */ + pj_cli_cfg cfg; /* CLI configuration */ + pj_cli_cmd_spec root; /* Root of command tree structure */ + pj_cli_front_end fe_head; /* List of front-ends */ + pj_hash_table_t *cmd_name_hash; /* Command name hash table, this will + include the command name and shortcut + as hash key */ + pj_hash_table_t *cmd_id_hash; /* Command id hash table */ + + pj_bool_t is_quitting; + pj_bool_t is_restarting; +}; + +/** + * Reserved command id constants. + */ +typedef enum pj_cli_std_cmd_id +{ + /** + * Constant to indicate an invalid command id. + */ + PJ_CLI_INVALID_CMD_ID = -1, + + /** + * A special command id to indicate that a command id denotes + * a command group. + */ + PJ_CLI_CMD_ID_GROUP = -2 + +} pj_cli_std_cmd_id; + +/** + * This describes the type of an argument (pj_cli_arg_spec). + */ +typedef enum pj_cli_arg_type +{ + /** + * Unformatted string. + */ + PJ_CLI_ARG_TEXT, + + /** + * An integral number. + */ + PJ_CLI_ARG_INT, + + /** + * Choice type + */ + PJ_CLI_ARG_CHOICE + +} pj_cli_arg_type; + +struct arg_type +{ + const pj_str_t msg; +} arg_type[3] = +{ + {{"Text", 4}}, + {{"Int", 3}}, + {{"Choice", 6}} +}; + +/** + * This structure describe the specification of a command argument. + */ +struct pj_cli_arg_spec +{ + /** + * Argument id + */ + pj_cli_arg_id id; + + /** + * Argument name. + */ + pj_str_t name; + + /** + * Helpful description of the argument. This text will be used when + * displaying help texts for the command/argument. + */ + pj_str_t desc; + + /** + * Argument type, which will be used for rendering the argument and + * to perform basic validation against an input value. + */ + pj_cli_arg_type type; + + /** + * Argument status + */ + pj_bool_t optional; + + /** + * Validate choice values + */ + pj_bool_t validate; + + /** + * Static Choice Values count + */ + unsigned stat_choice_cnt; + + /** + * Static Choice Values + */ + pj_cli_arg_choice_val *stat_choice_val; + + /** + * Argument callback to get the valid values + */ + pj_cli_get_dyn_choice get_dyn_choice; + +}; + +/** + * This describe the parse mode of the command line + */ +typedef enum pj_cli_parse_mode { + PARSE_NONE, + PARSE_COMPLETION, /* Complete the command line */ + PARSE_NEXT_AVAIL, /* Find the next available command line */ + PARSE_EXEC /* Exec the command line */ +} pj_cli_parse_mode; + +/** + * This is used to get the matched command/argument from the + * command/argument structure. + * + * @param sess The session on which the command is execute on. + * @param cmd The active command. + * @param cmd_val The command value to match. + * @param argc The number of argument that the + * current active command have. + * @param pool The memory pool to allocate memory. + * @param get_cmd Set true to search matching command from sub command. + * @param parse_mode The parse mode. + * @param p_cmd The command that mathes the command value. + * @param info The output information containing any hints for + * matching command/arg. + * @return This function return the status of the + * matching process.Please see the return value + * of pj_cli_sess_parse() for possible return values. + */ +static pj_status_t get_available_cmds(pj_cli_sess *sess, + pj_cli_cmd_spec *cmd, + pj_str_t *cmd_val, + unsigned argc, + pj_pool_t *pool, + pj_bool_t get_cmd, + pj_cli_parse_mode parse_mode, + pj_cli_cmd_spec **p_cmd, + pj_cli_exec_info *info); + +PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) +{ + return cmd->id; +} + +PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + pj_strset2(¶m->name, ""); +} + +PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param) +{ + pj_assert(param); + pj_bzero(param, sizeof(*param)); + param->err_pos = -1; + param->cmd_id = PJ_CLI_INVALID_CMD_ID; + param->cmd_ret = PJ_SUCCESS; +} + +PJ_DEF(void) pj_cli_write_log(pj_cli_t *cli, + int level, + const char *buffer, + int len) +{ + struct pj_cli_front_end *fe; + + pj_assert(cli); + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + if (fe->op && fe->op->on_write_log) + (*fe->op->on_write_log)(fe, level, buffer, len); + fe = fe->next; + } +} + +PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess, + const char *buffer, + int len) +{ + struct pj_cli_front_end *fe; + + pj_assert(sess); + + fe = sess->fe; + if (fe->op && fe->op->on_write_log) + (*fe->op->on_write_log)(fe, 0, buffer, len); +} + +/* Command handler */ +static pj_status_t cmd_handler(pj_cli_cmd_val *cval) +{ + unsigned level; + + switch(cval->cmd->id) { + case CLI_CMD_CHANGE_LOG: + level = pj_strtoul(&cval->argv[1]); + if (!level && cval->argv[1].slen > 0 && (cval->argv[1].ptr[0] < '0' || + cval->argv[1].ptr[0] > '9')) + { + return PJ_CLI_EINVARG; + } + cval->sess->log_level = level; + return PJ_SUCCESS; + case CLI_CMD_EXIT: + pj_cli_sess_end_session(cval->sess); + return PJ_CLI_EEXIT; + default: + return PJ_SUCCESS; + } +} + +PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, + pj_cli_t **p_cli) +{ + pj_pool_t *pool; + pj_cli_t *cli; + unsigned i; + + /* This is an example of the command structure */ + char* cmd_xmls[] = { + "" + " " + "", + "" + "", + }; + + PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL); + + pool = pj_pool_create(cfg->pf, "cli", PJ_CLI_POOL_SIZE, + PJ_CLI_POOL_INC, NULL); + if (!pool) + return PJ_ENOMEM; + cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t); + + pj_memcpy(&cli->cfg, cfg, sizeof(*cfg)); + cli->pool = pool; + pj_list_init(&cli->fe_head); + + cli->cmd_name_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); + cli->cmd_id_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE); + + cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec); + pj_list_init(cli->root.sub_cmd); + + /* Register some standard commands. */ + for (i = 0; i < sizeof(cmd_xmls)/sizeof(cmd_xmls[0]); i++) { + pj_str_t xml = pj_str(cmd_xmls[i]); + + if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, + &cmd_handler, NULL, NULL) != PJ_SUCCESS) + { + TRACE_((THIS_FILE, "Failed to add command #%d", i)); + } + } + + *p_cli = cli; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, NULL); + + return &cli->cfg; +} + +PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli, + pj_cli_front_end *fe) +{ + pj_list_push_back(&cli->fe_head, fe); +} + +PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, + pj_bool_t restart) +{ + pj_cli_front_end *fe; + + pj_assert(cli); + if (cli->is_quitting) + return; + + cli->is_quitting = PJ_TRUE; + cli->is_restarting = restart; + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + if (fe->op && fe->op->on_quit) + (*fe->op->on_quit)(fe, req); + fe = fe->next; + } +} + +PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, PJ_FALSE); + + return cli->is_quitting; +} + +PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli) +{ + PJ_ASSERT_RETURN(cli, PJ_FALSE); + + return cli->is_restarting; +} + +PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli) +{ + pj_cli_front_end *fe; + + if (!cli) + return; + + if (!pj_cli_is_quitting(cli)) + pj_cli_quit(cli, NULL, PJ_FALSE); + + fe = cli->fe_head.next; + while (fe != &cli->fe_head) { + pj_list_erase(fe); + if (fe->op && fe->op->on_destroy) + (*fe->op->on_destroy)(fe); + + fe = cli->fe_head.next; + } + cli->is_quitting = PJ_FALSE; + pj_pool_release(cli->pool); +} + +PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) +{ + pj_assert(sess); + + if (sess->op && sess->op->destroy) + (*sess->op->destroy)(sess); +} + +/* Syntax error handler for parser. */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJ_EINVAL); +} + +/* Get the command from the command hash */ +static pj_cli_cmd_spec *get_cmd_name(const pj_cli_t *cli, + const pj_cli_cmd_spec *group, + const pj_str_t *cmd) +{ + pj_str_t cmd_val; + char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH]; + + cmd_val.ptr = cmd_ptr; + cmd_val.slen = 0; + + if (group) { + char cmd_str[MAX_CMD_ID_LENGTH]; + pj_ansi_sprintf(cmd_str, "%d", group->id); + pj_strcat2(&cmd_val, cmd_str); + } + pj_strcat(&cmd_val, cmd); + return (pj_cli_cmd_spec *)pj_hash_get(cli->cmd_name_hash, cmd_val.ptr, + cmd_val.slen, NULL); +} + +/* Add command to the command hash */ +static void add_cmd_name(pj_cli_t *cli, pj_cli_cmd_spec *group, + pj_cli_cmd_spec *cmd, pj_str_t *cmd_name) +{ + pj_str_t cmd_val; + pj_str_t add_cmd; + char cmd_ptr[MAX_CMD_HASH_NAME_LENGTH]; + + cmd_val.ptr = cmd_ptr; + cmd_val.slen = 0; + + if (group) { + char cmd_str[MAX_CMD_ID_LENGTH]; + pj_ansi_sprintf(cmd_str, "%d", group->id); + pj_strcat2(&cmd_val, cmd_str); + } + pj_strcat(&cmd_val, cmd_name); + pj_strdup(cli->pool, &add_cmd, &cmd_val); + + pj_hash_set(cli->pool, cli->cmd_name_hash, cmd_val.ptr, + cmd_val.slen, 0, cmd); +} + +/** + * This method is to parse and add the choice type + * argument values to command structure. + **/ +static pj_status_t add_choice_node(pj_cli_t *cli, + pj_xml_node *xml_node, + pj_cli_arg_spec *arg, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_node *choice_node; + pj_xml_node *sub_node; + pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL]; + pj_status_t status = PJ_SUCCESS; + + sub_node = xml_node; + arg->type = PJ_CLI_ARG_CHOICE; + arg->get_dyn_choice = get_choice; + + choice_node = sub_node->node_head.next; + while (choice_node != (pj_xml_node*)&sub_node->node_head) { + pj_xml_attr *choice_attr; + unsigned *stat_cnt = &arg->stat_choice_cnt; + pj_cli_arg_choice_val *choice_val = &choice_values[*stat_cnt]; + pj_bzero(choice_val, sizeof(*choice_val)); + + choice_attr = choice_node->attr_head.next; + while (choice_attr != &choice_node->attr_head) { + if (!pj_stricmp2(&choice_attr->name, "value")) { + pj_strassign(&choice_val->value, &choice_attr->value); + } else if (!pj_stricmp2(&choice_attr->name, "desc")) { + pj_strassign(&choice_val->desc, &choice_attr->value); + } + choice_attr = choice_attr->next; + } + if (++(*stat_cnt) >= PJ_CLI_MAX_CHOICE_VAL) + break; + choice_node = choice_node->next; + } + if (arg->stat_choice_cnt > 0) { + unsigned i; + + arg->stat_choice_val = (pj_cli_arg_choice_val *) + pj_pool_zalloc(cli->pool, + arg->stat_choice_cnt * + sizeof(pj_cli_arg_choice_val)); + + for (i = 0; i < arg->stat_choice_cnt; i++) { + pj_strdup(cli->pool, &arg->stat_choice_val[i].value, + &choice_values[i].value); + pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, + &choice_values[i].desc); + } + } + return status; +} + +/** + * This method is to parse and add the argument attribute to command structure. + **/ +static pj_status_t add_arg_node(pj_cli_t *cli, + pj_xml_node *xml_node, + pj_cli_cmd_spec *cmd, + pj_cli_arg_spec *arg, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_attr *attr; + pj_status_t status = PJ_SUCCESS; + pj_xml_node *sub_node = xml_node; + + if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) + return PJ_CLI_ETOOMANYARGS; + + pj_bzero(arg, sizeof(*arg)); + attr = sub_node->attr_head.next; + arg->optional = PJ_FALSE; + arg->validate = PJ_TRUE; + while (attr != &sub_node->attr_head) { + if (!pj_stricmp2(&attr->name, "name")) { + pj_strassign(&arg->name, &attr->value); + } else if (!pj_stricmp2(&attr->name, "id")) { + arg->id = pj_strtol(&attr->value); + } else if (!pj_stricmp2(&attr->name, "type")) { + if (!pj_stricmp2(&attr->value, "text")) { + arg->type = PJ_CLI_ARG_TEXT; + } else if (!pj_stricmp2(&attr->value, "int")) { + arg->type = PJ_CLI_ARG_INT; + } else if (!pj_stricmp2(&attr->value, "choice")) { + /* Get choice value */ + add_choice_node(cli, xml_node, arg, get_choice); + } + } else if (!pj_stricmp2(&attr->name, "desc")) { + pj_strassign(&arg->desc, &attr->value); + } else if (!pj_stricmp2(&attr->name, "optional")) { + if (!pj_strcmp2(&attr->value, "1")) { + arg->optional = PJ_TRUE; + } + } else if (!pj_stricmp2(&attr->name, "validate")) { + if (!pj_strcmp2(&attr->value, "1")) { + arg->validate = PJ_TRUE; + } else { + arg->validate = PJ_FALSE; + } + } + attr = attr->next; + } + cmd->arg_cnt++; + return status; +} + +/** + * This method is to parse and add the command attribute to command structure. + **/ +static pj_status_t add_cmd_node(pj_cli_t *cli, + pj_cli_cmd_spec *group, + pj_xml_node *xml_node, + pj_cli_cmd_handler handler, + pj_cli_cmd_spec **p_cmd, + pj_cli_get_dyn_choice get_choice) +{ + pj_xml_node *root = xml_node; + pj_xml_attr *attr; + pj_xml_node *sub_node; + pj_cli_cmd_spec *cmd; + pj_cli_arg_spec args[PJ_CLI_MAX_ARGS]; + pj_str_t sc[PJ_CLI_MAX_SHORTCUTS]; + pj_status_t status = PJ_SUCCESS; + + if (pj_stricmp2(&root->name, "CMD")) + return PJ_EINVAL; + + /* Initialize the command spec */ + cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec); + + /* Get the command attributes */ + attr = root->attr_head.next; + while (attr != &root->attr_head) { + if (!pj_stricmp2(&attr->name, "name")) { + pj_strltrim(&attr->value); + if (!attr->value.slen || + (get_cmd_name(cli, group, &attr->value))) + { + return PJ_CLI_EBADNAME; + } + pj_strdup(cli->pool, &cmd->name, &attr->value); + } else if (!pj_stricmp2(&attr->name, "id")) { + pj_bool_t is_valid = PJ_FALSE; + if (attr->value.slen) { + pj_cli_cmd_id cmd_id = pj_strtol(&attr->value); + if (!pj_hash_get(cli->cmd_id_hash, &cmd_id, + sizeof(pj_cli_cmd_id), NULL)) + is_valid = PJ_TRUE; + } + if (!is_valid) + return PJ_CLI_EBADID; + cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value); + } else if (!pj_stricmp2(&attr->name, "sc")) { + pj_scanner scanner; + pj_str_t str; + + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, attr->value.ptr, attr->value.slen, + PJ_SCAN_AUTOSKIP_WS, &on_syntax_error); + + PJ_TRY { + while (!pj_scan_is_eof(&scanner)) { + pj_scan_get_until_ch(&scanner, ',', &str); + pj_strrtrim(&str); + if (!pj_scan_is_eof(&scanner)) + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + if (!str.slen) + continue; + + if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) { + PJ_THROW(PJ_CLI_ETOOMANYARGS); + } + /* Check whether the shortcuts are already used */ + if (get_cmd_name(cli, &cli->root, &str)) { + PJ_THROW(PJ_CLI_EBADNAME); + } + + pj_strassign(&sc[cmd->sc_cnt++], &str); + } + } + PJ_CATCH_ANY { + pj_scan_fini(&scanner); + return (PJ_GET_EXCEPTION()); + } + PJ_END; + + } else if (!pj_stricmp2(&attr->name, "desc")) { + pj_strdup(cli->pool, &cmd->desc, &attr->value); + } + attr = attr->next; + } + + /* Get the command childs/arguments */ + sub_node = root->node_head.next; + while (sub_node != (pj_xml_node*)&root->node_head) { + if (!pj_stricmp2(&sub_node->name, "CMD")) { + status = add_cmd_node(cli, cmd, sub_node, handler, NULL, + get_choice); + if (status != PJ_SUCCESS) + return status; + } else if (!pj_stricmp2(&sub_node->name, "ARG")) { + /* Get argument attribute */ + status = add_arg_node(cli, sub_node, + cmd, &args[cmd->arg_cnt], + get_choice); + + if (status != PJ_SUCCESS) + return status; + } + sub_node = sub_node->next; + } + + if (!cmd->name.slen) + return PJ_CLI_EBADNAME; + + if (!cmd->id) + return PJ_CLI_EBADID; + + if (cmd->arg_cnt) { + unsigned i; + + cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt * + sizeof(pj_cli_arg_spec)); + + for (i = 0; i < cmd->arg_cnt; i++) { + pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name); + pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); + cmd->arg[i].id = args[i].id; + cmd->arg[i].type = args[i].type; + cmd->arg[i].optional = args[i].optional; + cmd->arg[i].validate = args[i].validate; + cmd->arg[i].get_dyn_choice = args[i].get_dyn_choice; + cmd->arg[i].stat_choice_cnt = args[i].stat_choice_cnt; + cmd->arg[i].stat_choice_val = args[i].stat_choice_val; + } + } + + if (cmd->sc_cnt) { + unsigned i; + + cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt * + sizeof(pj_str_t)); + for (i = 0; i < cmd->sc_cnt; i++) { + pj_strdup(cli->pool, &cmd->sc[i], &sc[i]); + /** Add shortcut to root command **/ + add_cmd_name(cli, &cli->root, cmd, &sc[i]); + } + } + + add_cmd_name(cli, group, cmd, &cmd->name); + pj_hash_set(cli->pool, cli->cmd_id_hash, + &cmd->id, sizeof(pj_cli_cmd_id), 0, cmd); + + cmd->handler = handler; + + if (group) { + if (!group->sub_cmd) { + group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); + pj_list_init(group->sub_cmd); + } + pj_list_push_back(group->sub_cmd, cmd); + } else { + pj_list_push_back(cli->root.sub_cmd, cmd); + } + + if (p_cmd) + *p_cmd = cmd; + + return status; +} + +PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli, + pj_cli_cmd_spec *group, + const pj_str_t *xml, + pj_cli_cmd_handler handler, + pj_cli_cmd_spec **p_cmd, + pj_cli_get_dyn_choice get_choice) +{ + pj_pool_t *pool; + pj_xml_node *root; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); + + /* Parse the xml */ + pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + root = pj_xml_parse(pool, xml->ptr, xml->slen); + if (!root) { + TRACE_((THIS_FILE, "Error: unable to parse XML")); + pj_pool_release(pool); + return PJ_CLI_EBADXML; + } + status = add_cmd_node(cli, group, root, handler, p_cmd, get_choice); + pj_pool_release(pool); + return status; +} + +PJ_DEF(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess, + char *cmdline, + pj_cli_cmd_val *val, + pj_pool_t *pool, + pj_cli_exec_info *info) +{ + pj_scanner scanner; + pj_str_t str; + int len; + pj_cli_cmd_spec *cmd; + pj_cli_cmd_spec *next_cmd; + pj_status_t status = PJ_SUCCESS; + pj_cli_parse_mode parse_mode = PARSE_NONE; + + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); + + PJ_UNUSED_ARG(pool); + + str.slen = 0; + pj_cli_exec_info_default(info); + + /* Set the parse mode based on the latest char. */ + len = pj_ansi_strlen(cmdline); + if (len > 0 && ((cmdline[len - 1] == '\r')||(cmdline[len - 1] == '\n'))) { + cmdline[--len] = 0; + parse_mode = PARSE_EXEC; + } else if (len > 0 && + (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) + { + cmdline[--len] = 0; + if (len == 0) { + parse_mode = PARSE_NEXT_AVAIL; + } else { + if (cmdline[len - 1] == ' ') + parse_mode = PARSE_NEXT_AVAIL; + else + parse_mode = PARSE_COMPLETION; + } + } + val->argc = 0; + info->err_pos = 0; + cmd = &sess->fe->cli->root; + if (len > 0) { + pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, + &on_syntax_error); + PJ_TRY { + val->argc = 0; + while (!pj_scan_is_eof(&scanner)) { + info->err_pos = scanner.curptr - scanner.begin; + if (*scanner.curptr == '\'' || *scanner.curptr == '"' || + *scanner.curptr == '{') + { + pj_scan_get_quotes(&scanner, "'\"{", "'\"}", 3, &str); + /* Remove the quotes */ + str.ptr++; + str.slen -= 2; + } else { + pj_scan_get_until_chr(&scanner, " \t\r\n", &str); + } + ++val->argc; + if (val->argc == PJ_CLI_MAX_ARGS) + PJ_THROW(PJ_CLI_ETOOMANYARGS); + + status = get_available_cmds(sess, cmd, &str, val->argc-1, + pool, PJ_TRUE, parse_mode, + &next_cmd, info); + + if (status != PJ_SUCCESS) + PJ_THROW(status); + + if (cmd != next_cmd) { + /* Found new command, set it as the active command */ + cmd = next_cmd; + val->argc = 1; + val->cmd = cmd; + } + if (parse_mode == PARSE_EXEC) + pj_strassign(&val->argv[val->argc-1], &info->hint->name); + else + pj_strassign(&val->argv[val->argc-1], &str); + + } + if (!pj_scan_is_eof(&scanner)) + PJ_THROW(PJ_CLI_EINVARG); + + } + PJ_CATCH_ANY { + pj_scan_fini(&scanner); + return PJ_GET_EXCEPTION(); + } + PJ_END; + } + + if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) { + /* If exec mode, just get the matching argument */ + status = get_available_cmds(sess, cmd, NULL, val->argc, pool, + (parse_mode==PARSE_NEXT_AVAIL), + parse_mode, + NULL, info); + if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) { + pj_str_t data = pj_str(cmdline); + pj_strrtrim(&data); + data.ptr[data.slen] = ' '; + data.ptr[data.slen+1] = 0; + + info->err_pos = pj_ansi_strlen(cmdline); + } + } + + val->sess = sess; + return status; +} + +PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, + char *cmdline, + pj_pool_t *pool, + pj_cli_exec_info *info) +{ + pj_cli_cmd_val val; + pj_status_t status; + pj_cli_exec_info einfo; + pj_str_t cmd; + + PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL); + + PJ_UNUSED_ARG(pool); + + cmd.ptr = cmdline; + cmd.slen = pj_ansi_strlen(cmdline); + + if (pj_strtrim(&cmd)->slen == 0) + return PJ_SUCCESS; + + if (!info) + info = &einfo; + + status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); + if (status != PJ_SUCCESS) + return status; + + if ((val.argc > 0) && (val.cmd->handler)) { + info->cmd_ret = (*val.cmd->handler)(&val); + if (info->cmd_ret == PJ_CLI_EINVARG || + info->cmd_ret == PJ_CLI_EEXIT) + { + return info->cmd_ret; + } + } + + return PJ_SUCCESS; +} + +static pj_bool_t hint_inserted(const pj_str_t *name, + const pj_str_t *desc, + const pj_str_t *type, + pj_cli_exec_info *info) +{ + unsigned i; + for(i=0; ihint_cnt; ++i) { + pj_cli_hint_info *hint = &info->hint[i]; + if ((!pj_strncmp(&hint->name, name, hint->name.slen)) && + (!pj_strncmp(&hint->desc, desc, hint->desc.slen)) && + (!pj_strncmp(&hint->type, type, hint->type.slen))) + { + return PJ_TRUE; + } + } + return PJ_FALSE; +} + +/** This will insert new hint with the option to check for the same + previous entry **/ +static pj_status_t insert_new_hint2(pj_pool_t *pool, + pj_bool_t unique_insert, + const pj_str_t *name, + const pj_str_t *desc, + const pj_str_t *type, + pj_cli_exec_info *info) +{ + pj_cli_hint_info *hint; + PJ_ASSERT_RETURN(pool && info, PJ_EINVAL); + PJ_ASSERT_RETURN((info->hint_cnt < PJ_CLI_MAX_HINTS), PJ_EINVAL); + + if ((unique_insert) && (hint_inserted(name, desc, type, info))) + return PJ_SUCCESS; + + hint = &info->hint[info->hint_cnt]; + + pj_strdup(pool, &hint->name, name); + + if (desc && (desc->slen > 0)) { + pj_strdup(pool, &hint->desc, desc); + } else { + hint->desc.slen = 0; + } + + if (type && (type->slen > 0)) { + pj_strdup(pool, &hint->type, type); + } else { + hint->type.slen = 0; + } + + ++info->hint_cnt; + return PJ_SUCCESS; +} + +/** This will insert new hint without checking for the same previous entry **/ +static pj_status_t insert_new_hint(pj_pool_t *pool, + const pj_str_t *name, + const pj_str_t *desc, + const pj_str_t *type, + pj_cli_exec_info *info) +{ + return insert_new_hint2(pool, PJ_FALSE, name, desc, type, info); +} + +/** This will get a complete/exact match of a command from the cmd hash **/ +static pj_status_t get_comp_match_cmds(const pj_cli_t *cli, + const pj_cli_cmd_spec *group, + const pj_str_t *cmd_val, + pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, + pj_cli_exec_info *info) +{ + pj_cli_cmd_spec *cmd; + PJ_ASSERT_RETURN(cli && group && cmd_val && pool && info, PJ_EINVAL); + + cmd = get_cmd_name(cli, group, cmd_val); + + if (cmd) { + pj_status_t status; + status = insert_new_hint(pool, cmd_val, &cmd->desc, NULL, info); + + if (status != PJ_SUCCESS) + return status; + + *p_cmd = cmd; + } + + return PJ_SUCCESS; +} + +/** This method will search for any shortcut with pattern match to the input + command. This method should be called from root command, as shortcut could + only be executed from root **/ +static pj_status_t get_pattern_match_shortcut(const pj_cli_t *cli, + const pj_str_t *cmd_val, + pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, + pj_cli_exec_info *info) +{ + pj_hash_iterator_t it_buf, *it; + pj_status_t status; + PJ_ASSERT_RETURN(cli && pool && cmd_val && info, PJ_EINVAL); + + it = pj_hash_first(cli->cmd_name_hash, &it_buf); + while (it) { + unsigned i; + pj_cli_cmd_spec *cmd = (pj_cli_cmd_spec *) + pj_hash_this(cli->cmd_name_hash, it); + + PJ_ASSERT_RETURN(cmd, PJ_EINVAL); + + for (i=0; i < cmd->sc_cnt; ++i) { + static const pj_str_t SHORTCUT = {"SC", 2}; + pj_str_t *sc = &cmd->sc[i]; + PJ_ASSERT_RETURN(sc, PJ_EINVAL); + + if (!pj_strncmp(sc, cmd_val, cmd_val->slen)) { + /** Unique hints needed because cmd hash contain command name + and shortcut referencing to the same command **/ + status = insert_new_hint2(pool, PJ_TRUE, sc, &cmd->desc, + &SHORTCUT, info); + if (status != PJ_SUCCESS) + return status; + + if (p_cmd) + *p_cmd = cmd; + } + } + + it = pj_hash_next(cli->cmd_name_hash, it); + } + + return PJ_SUCCESS; +} + +/** This method will search a pattern match to the input command from the child + command list of the current/active command. **/ +static pj_status_t get_pattern_match_cmds(pj_cli_cmd_spec *cmd, + const pj_str_t *cmd_val, + pj_pool_t *pool, + pj_cli_cmd_spec **p_cmd, + pj_cli_parse_mode parse_mode, + pj_cli_exec_info *info) +{ + pj_status_t status; + PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL); + + if (p_cmd) + *p_cmd = cmd; + + /* Get matching command */ + if (cmd->sub_cmd) { + pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next; + while (child_cmd != cmd->sub_cmd) { + pj_bool_t found = PJ_FALSE; + if (!pj_strncmp(&child_cmd->name, cmd_val, cmd_val->slen)) { + status = insert_new_hint(pool, &child_cmd->name, + &child_cmd->desc, NULL, info); + if (status != PJ_SUCCESS) + return status; + + found = PJ_TRUE; + } + if (found) { + if (parse_mode == PARSE_NEXT_AVAIL) { + /** Only insert shortcut on next available commands mode **/ + unsigned i; + for (i=0; i < child_cmd->sc_cnt; ++i) { + static const pj_str_t SHORTCUT = {"SC", 2}; + pj_str_t *sc = &child_cmd->sc[i]; + PJ_ASSERT_RETURN(sc, PJ_EINVAL); + + status = insert_new_hint(pool, sc, + &child_cmd->desc, &SHORTCUT, + info); + if (status != PJ_SUCCESS) + return status; + } + } + + if (p_cmd) + *p_cmd = child_cmd; + } + child_cmd = child_cmd->next; + } + } + return PJ_SUCCESS; +} + +/** This will match the arguments passed to the command with the argument list + of the specified command list. **/ +static pj_status_t get_match_args(pj_cli_sess *sess, + pj_cli_cmd_spec *cmd, + const pj_str_t *cmd_val, + unsigned argc, + pj_pool_t *pool, + pj_cli_parse_mode parse_mode, + pj_cli_exec_info *info) +{ + pj_cli_arg_spec *arg; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL); + + if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) { + if (cmd_val->slen > 0) + return PJ_CLI_ETOOMANYARGS; + else + return PJ_SUCCESS; + } + + if (cmd->arg_cnt > 0) { + arg = &cmd->arg[argc-1]; + PJ_ASSERT_RETURN(arg, PJ_EINVAL); + if (arg->type == PJ_CLI_ARG_CHOICE) { + unsigned j; + + if ((parse_mode == PARSE_EXEC) && (!arg->validate)) { + /* If no validation needed, then insert the values */ + status = insert_new_hint(pool, cmd_val, NULL, NULL, info); + return status; + } + + for (j=0; j < arg->stat_choice_cnt; ++j) { + pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j]; + + PJ_ASSERT_RETURN(choice_val, PJ_EINVAL); + + if (!pj_strncmp(&choice_val->value, cmd_val, cmd_val->slen)) { + status = insert_new_hint(pool, + &choice_val->value, + &choice_val->desc, + &arg_type[PJ_CLI_ARG_CHOICE].msg, + info); + if (status != PJ_SUCCESS) + return status; + } + } + if (arg->get_dyn_choice) { + pj_cli_dyn_choice_param dyn_choice_param; + static pj_str_t choice_str = {"choice", 6}; + + /* Get the dynamic choice values */ + dyn_choice_param.sess = sess; + dyn_choice_param.cmd = cmd; + dyn_choice_param.arg_id = arg->id; + dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL; + dyn_choice_param.pool = pool; + dyn_choice_param.cnt = 0; + + (*arg->get_dyn_choice)(&dyn_choice_param); + for (j=0; j < dyn_choice_param.cnt; ++j) { + pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j]; + if (!pj_strncmp(&choice->value, cmd_val, cmd_val->slen)) { + pj_strassign(&info->hint[info->hint_cnt].name, + &choice->value); + pj_strassign(&info->hint[info->hint_cnt].type, + &choice_str); + pj_strassign(&info->hint[info->hint_cnt].desc, + &choice->desc); + if ((++info->hint_cnt) >= PJ_CLI_MAX_HINTS) + break; + } + } + if ((info->hint_cnt == 0) && (!arg->optional)) + return PJ_CLI_EMISSINGARG; + } + } else { + if (cmd_val->slen == 0) { + if (info->hint_cnt == 0) { + if (!((parse_mode == PARSE_EXEC) && (arg->optional))) { + /* For exec mode,no need to insert hint if optional */ + status = insert_new_hint(pool, + &arg->name, + &arg->desc, + &arg_type[arg->type].msg, + info); + if (status != PJ_SUCCESS) + return status; + } + if (!arg->optional) + return PJ_CLI_EMISSINGARG; + } + } else { + return insert_new_hint(pool, cmd_val, NULL, NULL, info); + } + } + } + return status; +} + +/** This will check for a match of the commands/arguments input. Any match + will be inserted to the hint data. **/ +static pj_status_t get_available_cmds(pj_cli_sess *sess, + pj_cli_cmd_spec *cmd, + pj_str_t *cmd_val, + unsigned argc, + pj_pool_t *pool, + pj_bool_t get_cmd, + pj_cli_parse_mode parse_mode, + pj_cli_cmd_spec **p_cmd, + pj_cli_exec_info *info) +{ + pj_status_t status = PJ_SUCCESS; + pj_str_t *prefix; + pj_str_t EMPTY_STR = {NULL, 0}; + + prefix = cmd_val?(pj_strtrim(cmd_val)):(&EMPTY_STR); + + info->hint_cnt = 0; + + if (get_cmd) { + status = get_comp_match_cmds(sess->fe->cli, cmd, prefix, pool, p_cmd, + info); + if (status != PJ_SUCCESS) + return status; + + /** If exact match found, then no need to search for pattern match **/ + if (info->hint_cnt == 0) { + if ((parse_mode != PARSE_NEXT_AVAIL) && + (cmd == &sess->fe->cli->root)) + { + /** Pattern match for shortcut needed on root command only **/ + status = get_pattern_match_shortcut(sess->fe->cli, prefix, pool, + p_cmd, info); + + if (status != PJ_SUCCESS) + return status; + } + + status = get_pattern_match_cmds(cmd, prefix, pool, p_cmd, + parse_mode, info); + } + + if (status != PJ_SUCCESS) + return status; + } + + if (argc > 0) + status = get_match_args(sess, cmd, prefix, argc, + pool, parse_mode, info); + + if (status == PJ_SUCCESS) { + if (prefix->slen > 0) { + /** If a command entered is not a an empty command, and have a + single match in the command list then it is a valid command **/ + if (info->hint_cnt == 0) { + status = PJ_CLI_EINVARG; + } else if (info->hint_cnt > 1) { + status = PJ_CLI_EAMBIGUOUS; + } + } else { + if (info->hint_cnt > 0) + status = PJ_CLI_EAMBIGUOUS; + } + } + + return status; +} + diff --git a/pjlib-util/src/pjlib-util/cli_console.c b/pjlib-util/src/pjlib-util/cli_console.c index 934654a2..9c7dbc63 100644 --- a/pjlib-util/src/pjlib-util/cli_console.c +++ b/pjlib-util/src/pjlib-util/cli_console.c @@ -181,7 +181,7 @@ static void send_prompt_str(pj_cli_sess *sess) char data_str[128]; struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; - send_data.ptr = &data_str[0]; + send_data.ptr = data_str; send_data.slen = 0; pj_strcat(&send_data, &fe->cfg.prompt_str); @@ -201,7 +201,7 @@ static void send_err_arg(pj_cli_sess *sess, unsigned i; struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; - send_data.ptr = &data_str[0]; + send_data.ptr = data_str; send_data.slen = 0; if (with_return) @@ -272,7 +272,7 @@ static void send_ambi_arg(pj_cli_sess *sess, 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.ptr = data; send_data.slen = 0; if (with_return) @@ -287,9 +287,12 @@ static void send_ambi_arg(pj_cli_sess *sess, /* Get the max length of the command name */ for (i=0;ihint_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); + 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; } @@ -303,7 +306,7 @@ static void send_ambi_arg(pj_cli_sess *sess, } cmd_length = 0; - for (i=0;ihint_cnt;++i) { + for (i=0;ihint_cnt;++i) { if ((&hint[i].type) && (hint[i].type.slen > 0)) { if (pj_stricmp(&hint[i].type, &sc_type) == 0) { parse_state = OP_SHORTCUT; @@ -316,12 +319,7 @@ static void send_ambi_arg(pj_cli_sess *sess, 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; - } + if (parse_state != OP_SHORTCUT) { pj_strcat2(&send_data, "\r\n "); cmd_length = hint[i].name.slen; } @@ -337,10 +335,18 @@ static void send_ambi_arg(pj_cli_sess *sess, 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); + 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); @@ -349,10 +355,13 @@ static void send_ambi_arg(pj_cli_sess *sess, } if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || - ((i+1) >= info->hint_cnt)) + ((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"); diff --git a/pjlib-util/src/pjlib-util/cli_telnet.c b/pjlib-util/src/pjlib-util/cli_telnet.c index 22ef0155..631ede51 100644 --- a/pjlib-util/src/pjlib-util/cli_telnet.c +++ b/pjlib-util/src/pjlib-util/cli_telnet.c @@ -667,7 +667,7 @@ static void send_prompt_str(cli_telnet_sess *sess) char data_str[128]; cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; - send_data.ptr = &data_str[0]; + send_data.ptr = data_str; send_data.slen = 0; pj_strcat(&send_data, &fe->cfg.prompt_str); @@ -691,7 +691,7 @@ static void send_err_arg(cli_telnet_sess *sess, unsigned i; cli_telnet_fe *fe = (cli_telnet_fe *)sess->base.fe; - send_data.ptr = &data_str[0]; + send_data.ptr = data_str; send_data.slen = 0; if (with_return) @@ -708,7 +708,7 @@ static void send_err_arg(cli_telnet_sess *sess, pj_strcat(&send_data, msg); pj_strcat(&send_data, &fe->cfg.prompt_str); if (with_last_cmd) - pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]); + pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); telnet_sess_send(sess, &send_data); } @@ -771,7 +771,7 @@ static void send_ambi_arg(cli_telnet_sess *sess, 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.ptr = data; send_data.slen = 0; if (with_return) @@ -786,9 +786,12 @@ static void send_ambi_arg(cli_telnet_sess *sess, /* Get the max length of the command name */ for (i=0;ihint_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); + 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; } @@ -816,18 +819,11 @@ static void send_ambi_arg(cli_telnet_sess *sess, parse_state = OP_NORMAL; } - if ((parse_state != OP_SHORTCUT)) { - if (cmd_desc) { - /* Print data if previous hint is shortcut */ - send_hint_arg(sess, &send_data, - cmd_desc, cmd_length, - max_length); - cmd_desc = 0; - } + if (parse_state != OP_SHORTCUT) { pj_strcat2(&send_data, "\r\n "); cmd_length = hint[i].name.slen; - } - + } + switch (parse_state) { case OP_CHOICE: /* Format : "[Choice Value] description" */ @@ -843,9 +839,16 @@ static void send_ambi_arg(cli_telnet_sess *sess, break; case OP_SHORTCUT: /* Format : "Command | sc | description" */ - pj_strcat2(&send_data, " | "); - pj_strcat(&send_data, &hint[i].name); - cmd_length += (hint[i].name.slen + 3); + { + 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: /* Command */ @@ -855,18 +858,21 @@ static void send_ambi_arg(cli_telnet_sess *sess, } if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || - ((i+1) >= info->hint_cnt)) + ((i+1) >= info->hint_cnt) || + (pj_strncmp(&hint[i].desc, &hint[i+1].desc, hint[i].desc.slen))) { /* Add description info */ send_hint_arg(sess, &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); if (with_last_cmd) - pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]); + pj_strcat2(&send_data, (char *)sess->rcmd->rbuf); telnet_sess_send(sess, &send_data); } @@ -882,7 +888,7 @@ static void send_comp_arg(cli_telnet_sess *sess, pj_strcat2(&info->hint[0].name, " "); - send_data.ptr = &data[0]; + send_data.ptr = data; send_data.slen = 0; pj_strcat(&send_data, &info->hint[0].name); @@ -900,7 +906,7 @@ static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data) /* Cursor is not at EOL, insert character */ unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00}; echo[4] = *data; - telnet_sess_send2(sess, &echo[0], 5); + telnet_sess_send2(sess, echo, 5); } else { /* Append character */ telnet_sess_send2(sess, data, 1); @@ -924,10 +930,10 @@ static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data) */ unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50}; echo[0] = *data; - telnet_sess_send2(sess, &echo[0], 5); + telnet_sess_send2(sess, echo, 5); } else { const static unsigned char echo[3] = {0x08, 0x20, 0x08}; - telnet_sess_send2(sess, &echo[0], 3); + telnet_sess_send2(sess, echo, 3); } return PJ_TRUE; } @@ -986,7 +992,7 @@ static pj_bool_t handle_tab(cli_telnet_sess *sess) status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val, pool, &info); - len = pj_ansi_strlen((char *)&sess->rcmd->rbuf[0]); + len = pj_ansi_strlen((char *)sess->rcmd->rbuf); switch (status) { case PJ_CLI_EINVARG: @@ -1009,7 +1015,7 @@ static pj_bool_t handle_tab(cli_telnet_sess *sess) } if (info.hint_cnt > 0) { /* Complete command */ - pj_str_t cmd = pj_str((char *)&sess->rcmd->rbuf[0]); + pj_str_t cmd = pj_str((char *)sess->rcmd->rbuf); pj_str_t last_token; if (get_last_token(&cmd, &last_token) == PJ_SUCCESS) { @@ -1018,13 +1024,13 @@ static pj_bool_t handle_tab(cli_telnet_sess *sess) pj_strtrim(&last_token); if (hint_info->slen >= last_token.slen) { hint_info->slen -= last_token.slen; - pj_memmove(&hint_info->ptr[0], + pj_memmove(hint_info->ptr, &hint_info->ptr[last_token.slen], hint_info->slen); } send_comp_arg(sess, &info); - pj_memcpy(&sess->rcmd->rbuf[len], &info.hint[0].name.ptr[0], + pj_memcpy(&sess->rcmd->rbuf[len], info.hint[0].name.ptr, info.hint[0].name.slen); len += info.hint[0].name.slen; @@ -1135,7 +1141,7 @@ static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up) MOVE_CURSOR_LEFT = 0x08, CLEAR_CHAR = 0x20 }; - send_data.ptr = &str[0]; + send_data.ptr = str; send_data.slen = 0; /* Move cursor position to the beginning of line */ -- cgit v1.2.3