/* $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 PJ_CLI_MAX_CMDBUF #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_DEF(void) pj_cli_sess_write_msg(pj_cli_sess *sess, const char *buffer, pj_size_t 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, (unsigned)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, (unsigned)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; /* The buffer passed to the scanner is not NULL terminated, * but should be safe. See ticket #2063. */ 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; pj_scan_fini(&scanner); } 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; pj_size_t 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. * And NULL terminate the buffer for the scanner. */ 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 = (int)(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; pj_scan_fini(&scanner); } 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 = (int)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); /* 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; }