Ignore:
Timestamp:
Nov 23, 2012 10:30:55 AM (11 years ago)
Author:
riza
Message:

Re #1098: Additional implementation to command parsing, telnet front end, console front end

File:
1 edited

Legend:

Unmodified
Added
Removed
  • pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli.c

    r3231 r4299  
    4343#endif 
    4444 
     45/** 
     46 * This structure describes the full specification of a CLI command. A CLI 
     47 * command mainly consists of the name of the command, zero or more arguments, 
     48 * and a callback function to be called to execute the command. 
     49 * 
     50 * Application can create this specification by forming an XML document and 
     51 * calling pj_cli_create_cmd_from_xml() to instantiate the spec. A sample XML 
     52 * document containing a command spec is as follows: 
     53 */ 
     54struct pj_cli_cmd_spec 
     55{ 
     56    /** 
     57     * To make list of child cmds. 
     58     */ 
     59    PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); 
     60 
     61    /** 
     62     * Command ID assigned to this command by the application during command 
     63     * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is 
     64     * a command group and it can't be executed. 
     65     */ 
     66    pj_cli_cmd_id id; 
     67 
     68    /** 
     69     * The command name. 
     70     */ 
     71    pj_str_t name; 
     72 
     73    /** 
     74     * The full description of the command. 
     75     */ 
     76    pj_str_t desc; 
     77 
     78    /** 
     79     * Number of optional shortcuts 
     80     */ 
     81    unsigned sc_cnt; 
     82 
     83    /** 
     84     * Optional array of shortcuts, if any. Shortcut is a short name version 
     85     * of the command. If the command doesn't have any shortcuts, this 
     86     * will be initialized to NULL. 
     87     */ 
     88    pj_str_t *sc; 
     89 
     90    /** 
     91     * The command handler, to be executed when a command matching this command 
     92     * specification is invoked by the end user. The value may be NULL if this 
     93     * is a command group. 
     94     */ 
     95    pj_cli_cmd_handler handler; 
     96 
     97    /** 
     98     * Number of arguments. 
     99     */ 
     100    unsigned arg_cnt; 
     101 
     102    /** 
     103     * Array of arguments. 
     104     */ 
     105    pj_cli_arg_spec *arg; 
     106 
     107    /** 
     108     * Child commands, if any. A command will only have subcommands if it is 
     109     * a group. If the command doesn't have subcommands, this field will be 
     110     * initialized with NULL. 
     111     */ 
     112    pj_cli_cmd_spec *sub_cmd; 
     113}; 
     114 
    45115struct pj_cli_t 
    46116{ 
     
    55125}; 
    56126 
     127/** 
     128 * Reserved command id constants. 
     129 */ 
     130typedef enum pj_cli_std_cmd_id 
     131{ 
     132    /** 
     133     * Constant to indicate an invalid command id. 
     134     */ 
     135    PJ_CLI_INVALID_CMD_ID = -1, 
     136 
     137    /** 
     138     * A special command id to indicate that a command id denotes 
     139     * a command group. 
     140     */ 
     141    PJ_CLI_CMD_ID_GROUP = -2 
     142 
     143} pj_cli_std_cmd_id; 
     144 
     145/** 
     146 * This describes the type of an argument (pj_cli_arg_spec). 
     147 */ 
     148typedef enum pj_cli_arg_type 
     149{ 
     150    /** 
     151     * Unformatted string. 
     152     */ 
     153    PJ_CLI_ARG_TEXT, 
     154 
     155    /** 
     156     * An integral number. 
     157     */ 
     158    PJ_CLI_ARG_INT, 
     159 
     160    /** 
     161     * Choice type 
     162    */ 
     163    PJ_CLI_ARG_CHOICE 
     164 
     165} pj_cli_arg_type; 
     166 
     167static const struct 
     168{ 
     169    const pj_str_t msg; 
     170} arg_type[] =  
     171{ 
     172    {"Text", 4}, 
     173    {"Int", 3}, 
     174    {"Choice", 6} 
     175}; 
     176 
     177/** 
     178 * This structure describe the specification of a command argument. 
     179 */ 
     180struct pj_cli_arg_spec 
     181{ 
     182    /** 
     183     * Argument id 
     184     */ 
     185    pj_cli_arg_id id; 
     186 
     187    /** 
     188     * Argument name. 
     189     */ 
     190    pj_str_t name; 
     191 
     192    /** 
     193     * Helpful description of the argument. This text will be used when 
     194     * displaying help texts for the command/argument. 
     195     */ 
     196    pj_str_t desc; 
     197 
     198    /** 
     199     * Argument type, which will be used for rendering the argument and 
     200     * to perform basic validation against an input value. 
     201     */ 
     202    pj_cli_arg_type type; 
     203 
     204    /** 
     205     * Argument status 
     206     */ 
     207    pj_bool_t optional; 
     208 
     209    /** 
     210     * Static Choice Values count 
     211     */ 
     212    unsigned stat_choice_cnt;  
     213 
     214    /** 
     215     * Static Choice Values 
     216     */ 
     217    pj_cli_arg_choice_val *stat_choice_val;  
     218 
     219    /** 
     220     * Argument callback to get the valid values 
     221     */ 
     222    pj_cli_arg_get_dyn_choice_val get_dyn_choice; 
     223 
     224}; 
     225 
     226/** 
     227 * This describe the parse mode of the command line 
     228 */ 
     229typedef enum pj_cli_parse_mode { 
     230    PARSE_NONE, 
     231    PARSE_COMPLETION,   /* Complete the command line */ 
     232    PARSE_NEXT_AVAIL,   /* Find the next available command line */ 
     233    PARSE_EXEC          /* Exec the command line */ 
     234} pj_cli_parse_mode; 
     235 
     236/**  
     237 * This is used to get the matched command/argument from the  
     238 * command/argument structure. 
     239 *  
     240 * @param sess          The session on which the command is execute on. 
     241 * @param cmd           The active command. 
     242 * @param cmd_val       The command value to match. 
     243 * @param argc          The number of argument that the  
     244 *                      current active command have. 
     245 * @param pool          The memory pool to allocate memory. 
     246 * @param get_cmd       Set true to search matching command from sub command. 
     247 * @param parse_mode    The parse mode. 
     248 * @param info          The output information containing any hints for  
     249 *                      matching command/arg. 
     250 * @return              This function return the status of the  
     251 *                      matching process.Please see the return value 
     252 *                      of pj_cli_sess_parse() for possible return values. 
     253 */ 
     254static pj_status_t get_available_cmds(pj_cli_sess *sess, 
     255                                      pj_cli_cmd_spec *cmd,  
     256                                      pj_str_t *cmd_val, 
     257                                      unsigned argc, 
     258                                      pj_pool_t *pool, 
     259                                      pj_bool_t get_cmd, 
     260                                      pj_cli_parse_mode parse_mode, 
     261                                      pj_cli_cmd_spec **p_cmd, 
     262                                      pj_cli_exec_info *info); 
     263 
     264PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) 
     265{ 
     266    return cmd->id; 
     267} 
     268 
    57269PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) 
    58270{ 
     
    86298        fe = fe->next; 
    87299    } 
     300} 
     301 
     302PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess,                                
     303                                    const char *buffer, 
     304                                    int len) 
     305{ 
     306    struct pj_cli_front_end *fe; 
     307 
     308    pj_assert(sess); 
     309 
     310    fe = sess->fe; 
     311    if (fe->op && fe->op->on_write_log) 
     312        (*fe->op->on_write_log)(fe, 0, buffer, len); 
    88313} 
    89314 
     
    102327        return PJ_SUCCESS; 
    103328    case CLI_CMD_EXIT: 
    104         pj_cli_end_session(cval->sess); 
     329        pj_cli_sess_end_session(cval->sess); 
    105330        return PJ_CLI_EEXIT; 
    106331    default: 
     
    109334} 
    110335 
     336#define print_(arg) \ 
     337    do { \ 
     338        unsigned d = pj_log_get_decor(); \ 
     339        pj_log_set_decor(0); \ 
     340        PJ_LOG(1, arg); \ 
     341        pj_log_set_decor(d); \ 
     342    } while (0) 
     343 
     344/**  
     345 *  Example to send the command structure to all cli session  
     346 **/   
     347PJ_DEF(void) pj_cli_log_command_struct(pj_cli_t *cli, pj_str_t *indent, pj_cli_cmd_spec *in_cmd) 
     348{ 
     349    static const pj_str_t CMD_SIGN = {"+", 1};         
     350    static const pj_str_t ARG_SIGN = {"-", 1};     
     351 
     352    if (cli) { 
     353        pj_cli_cmd_spec *cmd; 
     354        pj_pool_t *pool = pj_pool_create(cli->cfg.pf, "log_cmd", 64, 64, NULL); 
     355        cmd = (in_cmd)?in_cmd:&cli->root; 
     356        if (pool) { 
     357            pj_str_t modif_indent;           
     358            pj_cli_cmd_spec *child_cmd; 
     359            unsigned i; 
     360            char *indent_data; 
     361            pj_str_t *print_indent; 
     362 
     363            if (!indent) { 
     364                print_indent = PJ_POOL_ALLOC_T(pool, pj_str_t); 
     365                pj_strdup2(pool, print_indent, ""); 
     366            } else { 
     367                print_indent = indent;   
     368            } 
     369            indent_data = (char *)pj_pool_alloc(pool, print_indent->slen + 2); 
     370            modif_indent.ptr = indent_data; 
     371            modif_indent.slen = 0; 
     372 
     373            if (&cli->root != cmd) { 
     374                print_(("", "%.*s%.*s%.*s\r\n",  
     375                       (int)print_indent->slen, print_indent->ptr, 
     376                       (int)CMD_SIGN.slen, CMD_SIGN.ptr, 
     377                       (int)cmd->name.slen, cmd->name.ptr)); 
     378            } 
     379            pj_strcpy(&modif_indent, print_indent); 
     380            pj_strcat2(&modif_indent, "  ");         
     381 
     382            //print child commands 
     383            if (cmd->sub_cmd) { 
     384                child_cmd = cmd->sub_cmd->next; 
     385                while(child_cmd != cmd->sub_cmd) { 
     386                    pj_cli_log_command_struct(cli, &modif_indent, child_cmd); 
     387                    child_cmd = child_cmd->next; 
     388                } 
     389            } 
     390 
     391            //print argumen 
     392            for (i=0; i<cmd->arg_cnt;++i) { 
     393                pj_cli_arg_spec *arg = &cmd->arg[i]; 
     394                print_(("", "%.*s%.*s%.*s\r\n",  
     395                       (int)modif_indent.slen, modif_indent.ptr, 
     396                       (int)ARG_SIGN.slen, ARG_SIGN.ptr, 
     397                       (int)arg->name.slen, arg->name.ptr)); 
     398            } 
     399            pj_pool_release(pool); 
     400        } 
     401    }      
     402} 
     403 
    111404PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, 
    112405                                  pj_cli_t **p_cli) 
    113406{ 
    114407    pj_pool_t *pool; 
    115     pj_cli_t *cli; 
     408    pj_cli_t *cli;     
    116409    unsigned i; 
     410    /* This is an example of the command structure */ 
    117411    char* cmd_xmls[] = { 
    118412     "<CMD name='log' id='30000' sc='' desc='Change log level'>" 
    119      "  <ARGS>" 
    120      "    <ARG name='level' type='int' desc='Log level'/>" 
    121      "  </ARGS>" 
    122      "</CMD>", 
    123      "<CMD name='exit' id='30001' sc='' desc='Exit session'>" 
     413     "    <ARG name='level' type='int' optional='0' desc='Log level'/>" 
     414     "</CMD>",      
     415     "<CMD name='exit' id='30001' sc='' desc='Exit session'>"      
    124416     "</CMD>", 
    125417    }; 
     
    146438        pj_str_t xml = pj_str(cmd_xmls[i]); 
    147439 
    148         if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, &cmd_handler, NULL) != 
     440        if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, &cmd_handler, NULL, NULL) != 
    149441            PJ_SUCCESS) 
    150442            TRACE_((THIS_FILE, "Failed to add command #%d", i)); 
    151443    } 
    152444 
    153     *p_cli = cli; 
     445    *p_cli = cli;     
    154446 
    155447    return PJ_SUCCESS; 
     
    224516} 
    225517 
    226 PJ_DEF(void) pj_cli_end_session(pj_cli_sess *sess) 
     518PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) 
    227519{ 
    228520    pj_assert(sess); 
     
    239531} 
    240532 
    241 PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli, 
    242                                             pj_cli_cmd_spec *group, 
    243                                             const pj_str_t *xml, 
    244                                             pj_cli_cmd_handler handler, 
    245                                             pj_cli_cmd_spec *p_cmd) 
    246 { 
    247     #define ERROR_(STATUS) \ 
    248         do {status = STATUS; goto on_exit;} while(0) 
    249     pj_pool_t *pool; 
    250     pj_xml_node *root; 
     533/** 
     534 * This method is to parse and add the choice type  
     535 * argument values to command structure. 
     536 **/ 
     537static pj_status_t pj_cli_add_choice_node(pj_cli_t *cli, 
     538                                          pj_xml_node *xml_node, 
     539                                          pj_cli_arg_spec *arg, 
     540                                          pj_cli_arg_get_dyn_choice_val get_choice) 
     541{ 
     542    pj_xml_node *choice_node; 
     543    pj_xml_node *sub_node; 
     544    pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL]; 
     545    pj_status_t status = PJ_SUCCESS; 
     546 
     547    sub_node = xml_node; 
     548    arg->type = PJ_CLI_ARG_CHOICE; 
     549    arg->get_dyn_choice = get_choice;                                            
     550 
     551    choice_node = sub_node->node_head.next; 
     552    while (choice_node != (pj_xml_node*)&sub_node->node_head) { 
     553        pj_xml_attr *choice_attr; 
     554        pj_cli_arg_choice_val *choice_val = &choice_values[arg->stat_choice_cnt];                     
     555        pj_bzero(choice_val, sizeof(*choice_val)); 
     556 
     557        choice_attr = choice_node->attr_head.next; 
     558        while (choice_attr != &choice_node->attr_head) { 
     559            if (!pj_stricmp2(&choice_attr->name, "value")) { 
     560                pj_strassign(&choice_val->value, &choice_attr->value); 
     561            } else if (!pj_stricmp2(&choice_attr->name, "desc")) { 
     562                pj_strassign(&choice_val->desc, &choice_attr->value); 
     563            }                            
     564        }                            
     565        ++(arg->stat_choice_cnt); 
     566        choice_node = choice_node->next; 
     567    }     
     568    if (arg->stat_choice_cnt > 0) { 
     569        unsigned i; 
     570 
     571        arg->stat_choice_val = (pj_cli_arg_choice_val *)pj_pool_zalloc(cli->pool,  
     572                                                                       arg->stat_choice_cnt * 
     573                                                                       sizeof(pj_cli_arg_choice_val)); 
     574        for (i = 0; i < arg->stat_choice_cnt; i++) { 
     575            pj_strdup(cli->pool, &arg->stat_choice_val[i].value, &choice_values[i].value); 
     576            pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, &choice_values[i].desc);             
     577        } 
     578    } 
     579    return status; 
     580} 
     581 
     582/** 
     583 * This method is to parse and add the argument attribute to command structure. 
     584 **/ 
     585static pj_status_t pj_cli_add_arg_node(pj_cli_t *cli, 
     586                                       pj_xml_node *xml_node, 
     587                                       pj_cli_cmd_spec *cmd, 
     588                                       pj_cli_arg_spec *arg, 
     589                                       pj_cli_arg_get_dyn_choice_val get_choice) 
     590{     
     591    pj_xml_attr *attr; 
     592    pj_status_t status = PJ_SUCCESS; 
     593    pj_xml_node *sub_node = xml_node; 
     594 
     595    if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) 
     596        return PJ_CLI_ETOOMANYARGS; 
     597     
     598    pj_bzero(arg, sizeof(*arg)); 
     599    attr = sub_node->attr_head.next; 
     600    arg->optional = PJ_FALSE; 
     601    while (attr != &sub_node->attr_head) {       
     602        if (!pj_stricmp2(&attr->name, "name")) { 
     603            pj_strassign(&arg->name, &attr->value); 
     604        } else if (!pj_stricmp2(&attr->name, "id")) { 
     605            arg->id = pj_strtol(&attr->value); 
     606        } else if (!pj_stricmp2(&attr->name, "type")) { 
     607            if (!pj_stricmp2(&attr->value, "text")) { 
     608                arg->type = PJ_CLI_ARG_TEXT; 
     609            } else if (!pj_stricmp2(&attr->value, "int")) { 
     610                arg->type = PJ_CLI_ARG_INT; 
     611            } else if (!pj_stricmp2(&attr->value, "CHOICE")) { 
     612                /* Get choice value */ 
     613                pj_cli_add_choice_node(cli, xml_node, arg, get_choice); 
     614            }  
     615        } else if (!pj_stricmp2(&attr->name, "desc")) { 
     616            pj_strassign(&arg->desc, &attr->value); 
     617        } else if (!pj_stricmp2(&attr->name, "optional")) { 
     618            if (!pj_strcmp2(&attr->value, "1")) { 
     619                arg->optional = PJ_TRUE; 
     620            } 
     621        } 
     622        attr = attr->next; 
     623    } 
     624    cmd->arg_cnt++; 
     625    return status; 
     626} 
     627 
     628/** 
     629 * This method is to parse and add the command attribute to command structure. 
     630 **/ 
     631static pj_status_t pj_cli_add_cmd_node(pj_cli_t *cli,                              
     632                                       pj_cli_cmd_spec *group,                                    
     633                                       pj_xml_node *xml_node, 
     634                                       pj_cli_cmd_handler handler, 
     635                                       pj_cli_cmd_spec **p_cmd, 
     636                                       pj_cli_arg_get_dyn_choice_val get_choice) 
     637{ 
     638    pj_xml_node *root = xml_node; 
    251639    pj_xml_attr *attr; 
    252640    pj_xml_node *sub_node; 
     
    256644    pj_status_t status = PJ_SUCCESS; 
    257645 
    258     PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); 
    259  
    260     /* Parse the xml */ 
    261     pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); 
    262     if (!pool) 
    263         return PJ_ENOMEM; 
    264     root = pj_xml_parse(pool, xml->ptr, xml->slen); 
    265     if (!root) { 
    266         TRACE_((THIS_FILE, "Error: unable to parse XML")); 
    267         ERROR_(PJ_CLI_EBADXML); 
    268     } 
    269  
    270646    if (pj_stricmp2(&root->name, "CMD")) 
    271         ERROR_(PJ_EINVAL); 
     647        return PJ_EINVAL; 
    272648 
    273649    /* Initialize the command spec */ 
     
    279655        if (!pj_stricmp2(&attr->name, "name")) { 
    280656            pj_strltrim(&attr->value); 
    281             pj_strrtrim(&attr->value); 
    282             /* Check whether command with the specified name already exists */ 
    283657            if (!attr->value.slen || 
    284658                pj_hash_get(cli->hash, attr->value.ptr,  
    285659                            attr->value.slen, NULL)) 
    286660            { 
    287                 ERROR_(PJ_CLI_EBADNAME); 
     661                return PJ_CLI_EBADNAME; 
    288662            } 
    289  
    290663            pj_strdup(cli->pool, &cmd->name, &attr->value); 
    291664        } else if (!pj_stricmp2(&attr->name, "id")) { 
     
    324697            PJ_CATCH_ANY { 
    325698                pj_scan_fini(&scanner); 
    326                 ERROR_(PJ_GET_EXCEPTION()); 
     699                return (PJ_GET_EXCEPTION()); 
    327700            } 
    328701            PJ_END; 
     
    331704            pj_strdup(cli->pool, &cmd->desc, &attr->value); 
    332705        } 
    333  
    334706        attr = attr->next; 
    335707    } 
    336708 
    337     /* Get the command arguments */ 
     709    /* Get the command childs/arguments */ 
    338710    sub_node = root->node_head.next; 
    339711    while (sub_node != (pj_xml_node*)&root->node_head) { 
    340         if (!pj_stricmp2(&sub_node->name, "ARGS")) { 
    341             pj_xml_node *arg_node; 
    342  
    343             arg_node = sub_node->node_head.next; 
    344             while (arg_node != (pj_xml_node*)&sub_node->node_head) { 
    345                 if (!pj_stricmp2(&arg_node->name, "ARG")) { 
    346                     pj_cli_arg_spec *arg; 
    347  
    348                     if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) 
    349                         ERROR_(PJ_CLI_ETOOMANYARGS); 
    350                     arg = &args[cmd->arg_cnt]; 
    351                     pj_bzero(arg, sizeof(*arg)); 
    352                     attr = arg_node->attr_head.next; 
    353                     while (attr != &arg_node->attr_head) { 
    354                         if (!pj_stricmp2(&attr->name, "name")) { 
    355                             pj_strassign(&arg->name, &attr->value); 
    356                         } else if (!pj_stricmp2(&attr->name, "type")) { 
    357                             if (!pj_stricmp2(&attr->value, "text")) { 
    358                                 arg->type = PJ_CLI_ARG_TEXT; 
    359                             } else if (!pj_stricmp2(&attr->value, "int")) { 
    360                                 arg->type = PJ_CLI_ARG_INT; 
    361                             } 
    362                         } else if (!pj_stricmp2(&attr->name, "desc")) { 
    363                             pj_strassign(&arg->desc, &attr->value); 
    364                         } 
    365  
    366                         attr = attr->next; 
    367                     } 
    368                     cmd->arg_cnt++; 
    369                 } 
    370  
    371                 arg_node = arg_node->next; 
    372             } 
     712        if (!pj_stricmp2(&sub_node->name, "CMD")) { 
     713            status = pj_cli_add_cmd_node(cli, cmd, sub_node, handler, NULL,  
     714                                         get_choice); 
     715            if (status != PJ_SUCCESS) 
     716                return status; 
     717        } else if (!pj_stricmp2(&sub_node->name, "ARG")) { 
     718            /* Get argument attribute */ 
     719            status = pj_cli_add_arg_node(cli, sub_node, cmd, &args[cmd->arg_cnt],  
     720                                         get_choice); 
     721            if (status != PJ_SUCCESS) 
     722                return status; 
    373723        } 
    374724        sub_node = sub_node->next; 
    375725    } 
    376726 
    377     if (cmd->id == PJ_CLI_CMD_ID_GROUP) { 
    378         /* Command group shouldn't have any shortcuts nor arguments */ 
    379         if (!cmd->sc_cnt || !cmd->arg_cnt) 
    380             ERROR_(PJ_EINVAL); 
    381         cmd->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); 
    382         pj_list_init(cmd->sub_cmd); 
    383     } 
    384  
    385727    if (!cmd->name.slen) 
    386         ERROR_(PJ_CLI_EBADNAME); 
     728        return PJ_CLI_EBADNAME; 
    387729 
    388730    if (cmd->arg_cnt) { 
     
    395737            pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); 
    396738            cmd->arg[i].type = args[i].type; 
     739            cmd->arg[i].optional = args[i].optional; 
    397740        } 
    398741    } 
     
    414757    cmd->handler = handler; 
    415758 
    416     if (group) 
     759    if (group) { 
     760        if (!group->sub_cmd) { 
     761            group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); 
     762            pj_list_init(group->sub_cmd); 
     763        } 
    417764        pj_list_push_back(group->sub_cmd, cmd); 
    418     else 
     765    } else { 
    419766        pj_list_push_back(cli->root.sub_cmd, cmd); 
     767    } 
    420768 
    421769    if (p_cmd) 
    422         p_cmd = cmd; 
    423  
    424 on_exit: 
     770        *p_cmd = cmd; 
     771 
     772    return status; 
     773} 
     774 
     775PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli, 
     776                                            pj_cli_cmd_spec *group, 
     777                                            const pj_str_t *xml, 
     778                                            pj_cli_cmd_handler handler, 
     779                                            pj_cli_cmd_spec **p_cmd,  
     780                                            pj_cli_arg_get_dyn_choice_val get_choice) 
     781{  
     782    pj_pool_t *pool; 
     783    pj_xml_node *root; 
     784    pj_status_t status = PJ_SUCCESS; 
     785     
     786    PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); 
     787 
     788    /* Parse the xml */ 
     789    pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); 
     790    if (!pool) 
     791        return PJ_ENOMEM; 
     792 
     793    root = pj_xml_parse(pool, xml->ptr, xml->slen); 
     794    if (!root) { 
     795        TRACE_((THIS_FILE, "Error: unable to parse XML")); 
     796        pj_pool_release(pool); 
     797        return PJ_CLI_EBADXML; 
     798    }     
     799    status = pj_cli_add_cmd_node(cli, group, root, handler, p_cmd, get_choice); 
    425800    pj_pool_release(pool); 
    426  
    427801    return status; 
    428 #undef ERROR_ 
    429 } 
    430  
    431 PJ_DEF(pj_status_t) pj_cli_parse(pj_cli_sess *sess, 
    432                                  char *cmdline, 
    433                                  pj_cli_cmd_val *val, 
    434                                  pj_cli_exec_info *info) 
    435 { 
     802} 
     803 
     804PJ_DEF(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess, 
     805                                      char *cmdline, 
     806                                      pj_cli_cmd_val *val, 
     807                                      pj_pool_t *pool, 
     808                                      pj_cli_exec_info *info) 
     809{     
    436810    pj_scanner scanner; 
    437811    pj_str_t str; 
    438     int len; 
    439     pj_cli_exec_info einfo; 
     812    int len;     
     813    pj_cli_cmd_spec *cmd; 
     814    pj_cli_cmd_spec *next_cmd; 
     815    pj_status_t status = PJ_SUCCESS; 
     816    pj_cli_parse_mode parse_mode = PARSE_NONE;     
    440817 
    441818    PJ_USE_EXCEPTION; 
     
    443820    PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); 
    444821 
    445     if (!info) 
    446         info = &einfo; 
     822    PJ_UNUSED_ARG(pool); 
     823 
     824    str.slen = 0; 
    447825    pj_cli_exec_info_default(info); 
    448826 
    449     /* Parse the command line. */ 
     827    /* Set the parse mode based on the latest char. */ 
    450828    len = pj_ansi_strlen(cmdline); 
    451     if (len > 0 && cmdline[len - 1] == '\n') { 
     829    if (len > 0 && cmdline[len - 1] == '\r') { 
    452830        cmdline[--len] = 0; 
    453         if (len > 0 && cmdline[len - 1] == '\r') 
    454             cmdline[--len] = 0; 
    455     } 
    456     pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS,  
    457                  &on_syntax_error); 
    458     PJ_TRY { 
    459         pj_scan_get_until_chr(&scanner, " \t\r\n", &str); 
    460  
    461         /* Find the command from the hash table */ 
    462         val->cmd = pj_hash_get(sess->fe->cli->hash, str.ptr,  
    463                                str.slen, NULL); 
    464  
    465         /* Error, command not found */ 
    466         if (!val->cmd) 
    467             PJ_THROW(PJ_ENOTFOUND); 
    468  
    469         info->cmd_id = val->cmd->id; 
    470  
    471         /* Parse the command arguments */ 
    472         val->argc = 1; 
    473         pj_strassign(&val->argv[0], &str); 
    474         while (!pj_scan_is_eof(&scanner)) { 
    475             if (*scanner.curptr == '\'' || *scanner.curptr == '"' || 
    476                 *scanner.curptr == '[' || *scanner.curptr == '{') 
    477             { 
    478                 pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str); 
    479                 /* Remove the quotes */ 
    480                 str.ptr++; 
    481                 str.slen -= 2; 
    482             } else { 
    483                 pj_scan_get_until_chr(&scanner, " \t\r\n", &str); 
    484             } 
    485             if (val->argc == PJ_CLI_MAX_ARGS) 
    486                 PJ_THROW(PJ_CLI_ETOOMANYARGS); 
    487             pj_strassign(&val->argv[val->argc++], &str); 
    488         } 
    489          
    490         if (!pj_scan_is_eof(&scanner)) { 
    491             PJ_THROW(PJ_CLI_EINVARG); 
    492         } 
    493     } 
    494     PJ_CATCH_ANY { 
    495         pj_scan_fini(&scanner); 
    496         return PJ_GET_EXCEPTION(); 
    497     } 
    498     PJ_END; 
    499  
    500     if ((val->argc - 1) < (int)val->cmd->arg_cnt) { 
    501         info->arg_idx = val->argc; 
    502         return PJ_CLI_EMISSINGARG; 
    503     } else if ((val->argc - 1) > (int)val->cmd->arg_cnt) { 
    504         info->arg_idx = val->cmd->arg_cnt + 1; 
    505         return PJ_CLI_ETOOMANYARGS; 
    506     } 
    507  
     831        parse_mode = PARSE_EXEC; 
     832    } else if (len > 0 &&  
     833               (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) { 
     834 
     835        cmdline[--len] = 0; 
     836        if (len == 0) { 
     837            parse_mode = PARSE_NEXT_AVAIL; 
     838        } else { 
     839            if (cmdline[len - 1] == ' ')  
     840                parse_mode = PARSE_NEXT_AVAIL; 
     841            else  
     842                parse_mode = PARSE_COMPLETION; 
     843        } 
     844    } 
     845    val->argc = 0; 
     846    info->err_pos = 0; 
     847    cmd = &sess->fe->cli->root; 
     848    if (len > 0) { 
     849        pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS,  
     850                     &on_syntax_error); 
     851        PJ_TRY { 
     852            val->argc = 0;           
     853            while (!pj_scan_is_eof(&scanner)) { 
     854                info->err_pos = scanner.curptr - scanner.begin; 
     855                if (*scanner.curptr == '\'' || *scanner.curptr == '"' || 
     856                    *scanner.curptr == '[' || *scanner.curptr == '{') 
     857                { 
     858                    pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str); 
     859                    /* Remove the quotes */ 
     860                    str.ptr++; 
     861                    str.slen -= 2; 
     862                } else { 
     863                    pj_scan_get_until_chr(&scanner, " \t\r\n", &str); 
     864                } 
     865                ++val->argc; 
     866                if (val->argc == PJ_CLI_MAX_ARGS) 
     867                    PJ_THROW(PJ_CLI_ETOOMANYARGS);               
     868                 
     869                status = get_available_cmds(sess, cmd, &str, val->argc-1,  
     870                                            pool, PJ_TRUE, parse_mode,  
     871                                            &next_cmd, info); 
     872 
     873                if (status != PJ_SUCCESS) 
     874                    PJ_THROW(status); 
     875                 
     876                if (cmd != next_cmd) { 
     877                    /* Found new command, set it as the active command */ 
     878                    cmd = next_cmd; 
     879                    val->argc = 1; 
     880                    val->cmd = cmd; 
     881                } 
     882                if (parse_mode == PARSE_EXEC)  
     883                    pj_strassign(&val->argv[val->argc-1], &info->hint->name); 
     884                else  
     885                    pj_strassign(&val->argv[val->argc-1], &str); 
     886 
     887            } 
     888             
     889            if (!pj_scan_is_eof(&scanner)) { 
     890                PJ_THROW(PJ_CLI_EINVARG); 
     891            } 
     892        } 
     893        PJ_CATCH_ANY { 
     894            pj_scan_fini(&scanner); 
     895            return PJ_GET_EXCEPTION(); 
     896        } 
     897        PJ_END; 
     898    }  
     899     
     900    if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) { 
     901        /* If exec mode, just get the matching argument */ 
     902        status = get_available_cmds(sess, cmd, NULL, val->argc, pool,  
     903                                    (parse_mode==PARSE_NEXT_AVAIL),  
     904                                    parse_mode, 
     905                                    NULL, info); 
     906        if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) { 
     907            pj_str_t data = pj_str(cmdline); 
     908            pj_strrtrim(&data); 
     909            data.ptr[data.slen] = ' '; 
     910            data.ptr[data.slen+1] = 0; 
     911 
     912            info->err_pos = pj_ansi_strlen(cmdline); 
     913        } 
     914 
     915    } else if (parse_mode == PARSE_COMPLETION) { 
     916        if (info->hint[0].name.slen > str.slen) { 
     917            pj_str_t *hint_info = &info->hint[0].name; 
     918            pj_memmove(&hint_info->ptr[0], &hint_info->ptr[str.slen],  
     919                       info->hint[0].name.slen-str.slen); 
     920            hint_info->slen = info->hint[0].name.slen-str.slen;          
     921        } else { 
     922            info->hint[0].name.slen = 0; 
     923        } 
     924    }     
    508925    val->sess = sess; 
    509  
    510     return PJ_SUCCESS; 
    511 } 
    512  
    513 pj_status_t pj_cli_exec(pj_cli_sess *sess, 
    514                         char *cmdline, 
    515                         pj_cli_exec_info *info) 
     926    return status; 
     927} 
     928 
     929PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, 
     930                                      char *cmdline, 
     931                                      pj_pool_t *pool, 
     932                                      pj_cli_exec_info *info) 
    516933{ 
    517934    pj_cli_cmd_val val; 
    518935    pj_status_t status; 
    519936    pj_cli_exec_info einfo; 
     937    pj_str_t cmd; 
    520938 
    521939    PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL); 
     940 
     941    PJ_UNUSED_ARG(pool); 
     942 
     943    cmd.ptr = cmdline; 
     944    cmd.slen = pj_ansi_strlen(cmdline); 
     945 
     946    if (pj_strtrim(&cmd)->slen == 0) 
     947        return PJ_SUCCESS; 
    522948 
    523949    if (!info) 
    524950        info = &einfo; 
    525     status = pj_cli_parse(sess, cmdline, &val, info); 
     951    status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); 
    526952    if (status != PJ_SUCCESS) 
    527953        return status; 
    528954 
    529     if (val.cmd->handler) { 
     955    if ((val.argc > 0) && (val.cmd->handler)) { 
    530956        info->cmd_ret = (*val.cmd->handler)(&val); 
    531957        if (info->cmd_ret == PJ_CLI_EINVARG || 
     
    536962    return PJ_SUCCESS; 
    537963} 
     964 
     965static pj_status_t pj_cli_insert_new_hint(pj_pool_t *pool,  
     966                                          const pj_str_t *name,  
     967                                          const pj_str_t *desc,  
     968                                          const pj_str_t *type,  
     969                                          pj_cli_exec_info *info) 
     970{     
     971    pj_cli_hint_info *hint = &info->hint[info->hint_cnt]; 
     972    PJ_ASSERT_RETURN(pool && info, PJ_EINVAL);     
     973 
     974    pj_strdup(pool, &hint->name, name); 
     975 
     976    if (desc && (desc->slen > 0))  { 
     977        pj_strdup(pool, &hint->desc, desc); 
     978    } else { 
     979        hint->desc.slen = 0; 
     980    } 
     981     
     982    if (type && (type->slen > 0)) { 
     983        pj_strdup(pool, &hint->type, type); 
     984    } else { 
     985        hint->type.slen = 0; 
     986    } 
     987 
     988    ++info->hint_cnt;     
     989    return PJ_SUCCESS; 
     990} 
     991 
     992static pj_status_t get_match_cmds(pj_cli_cmd_spec *cmd,  
     993                                  const pj_str_t *cmd_val,                                     
     994                                  pj_pool_t *pool,  
     995                                  pj_cli_cmd_spec **p_cmd,                              
     996                                  pj_cli_exec_info *info) 
     997{ 
     998    pj_status_t status = PJ_SUCCESS; 
     999    PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL);     
     1000 
     1001    if (p_cmd) 
     1002        *p_cmd = cmd; 
     1003 
     1004    /* Get matching command */ 
     1005    if (cmd->sub_cmd) { 
     1006        pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next; 
     1007        while (child_cmd != cmd->sub_cmd) { 
     1008            unsigned i;     
     1009            pj_bool_t found = PJ_FALSE; 
     1010            if (!pj_strnicmp(&child_cmd->name, cmd_val, cmd_val->slen)) {                
     1011                status = pj_cli_insert_new_hint(pool, &child_cmd->name,  
     1012                                                &child_cmd->desc, NULL, info); 
     1013                if (status != PJ_SUCCESS) 
     1014                    return status; 
     1015 
     1016                found = PJ_TRUE; 
     1017            } 
     1018            for (i=0; i < child_cmd->sc_cnt; ++i) { 
     1019                static const pj_str_t SHORTCUT = {"SC", 2}; 
     1020                pj_str_t *sc = &child_cmd->sc[i]; 
     1021                PJ_ASSERT_RETURN(sc, PJ_EINVAL); 
     1022 
     1023                if (!pj_strnicmp(sc, cmd_val, cmd_val->slen)) {          
     1024                    status = pj_cli_insert_new_hint(pool, sc, &child_cmd->desc,  
     1025                                                    &SHORTCUT, info); 
     1026                    if (status != PJ_SUCCESS) 
     1027                        return status; 
     1028 
     1029                    found = PJ_TRUE; 
     1030                }                
     1031            } 
     1032            if (found && p_cmd) { 
     1033                *p_cmd = child_cmd;              
     1034            } 
     1035 
     1036            child_cmd = child_cmd->next; 
     1037        } 
     1038    } 
     1039    return status; 
     1040} 
     1041 
     1042static pj_status_t get_match_args(pj_cli_sess *sess, 
     1043                                  pj_cli_cmd_spec *cmd,  
     1044                                  const pj_str_t *cmd_val, 
     1045                                  unsigned argc, 
     1046                                  pj_pool_t *pool,  
     1047                                  pj_cli_parse_mode parse_mode, 
     1048                                  pj_cli_exec_info *info) 
     1049{ 
     1050    pj_cli_arg_spec *arg; 
     1051    pj_status_t status = PJ_SUCCESS; 
     1052 
     1053    PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL); 
     1054 
     1055    if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) { 
     1056        if (cmd_val->slen > 0) 
     1057            return PJ_CLI_ETOOMANYARGS; 
     1058        else 
     1059            return PJ_SUCCESS; 
     1060    } 
     1061 
     1062    if (cmd->arg_cnt > 0) { 
     1063        arg = &cmd->arg[argc-1]; 
     1064        PJ_ASSERT_RETURN(arg, PJ_EINVAL); 
     1065        if (arg->type == PJ_CLI_ARG_CHOICE) {        
     1066            unsigned j;      
     1067            pj_cli_dyn_choice_param dyn_choice_param;        
     1068 
     1069            for (j=0; j < arg->stat_choice_cnt; ++j) { 
     1070                pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j]; 
     1071             
     1072                PJ_ASSERT_RETURN(choice_val, PJ_EINVAL);                 
     1073             
     1074                if (!pj_strnicmp(&choice_val->value, cmd_val, cmd_val->slen)) {              
     1075                    status = pj_cli_insert_new_hint(pool, &choice_val->value,  
     1076                                                    &choice_val->desc,  
     1077                                                    &arg_type[PJ_CLI_ARG_CHOICE].msg,  
     1078                                                    info); 
     1079                    if (status != PJ_SUCCESS) 
     1080                        return status;               
     1081                } 
     1082            } 
     1083            /* Get the dynamic choice values */      
     1084            dyn_choice_param.sess = sess; 
     1085            dyn_choice_param.cmd = cmd; 
     1086            dyn_choice_param.arg_id = arg->id; 
     1087            dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL; 
     1088            dyn_choice_param.pool = pool; 
     1089            dyn_choice_param.cnt = 0;        
     1090 
     1091            (*arg->get_dyn_choice)(&dyn_choice_param); 
     1092            for (j=0; j < dyn_choice_param.cnt; ++j) { 
     1093                pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j]; 
     1094                pj_strassign(&info->hint[info->hint_cnt].name, &choice->value); 
     1095                pj_strassign(&info->hint[info->hint_cnt].desc, &choice->desc); 
     1096                ++info->hint_cnt; 
     1097            } 
     1098        } else { 
     1099            if (cmd_val->slen == 0) { 
     1100                if (info->hint_cnt == 0) { 
     1101                    if (!((parse_mode == PARSE_EXEC) && (arg->optional))) { 
     1102                        /* If exec mode, don't need to insert the hint if optional */ 
     1103                        status = pj_cli_insert_new_hint(pool, &arg->name, &arg->desc,  
     1104                                                        &arg_type[arg->type].msg, info); 
     1105                        if (status != PJ_SUCCESS) 
     1106                            return status; 
     1107                    } 
     1108                    if (!arg->optional) 
     1109                        return PJ_CLI_EMISSINGARG; 
     1110                }  
     1111            } else { 
     1112                return pj_cli_insert_new_hint(pool, cmd_val,  
     1113                                              NULL, NULL, info); 
     1114            } 
     1115        } 
     1116    }  
     1117    return status; 
     1118} 
     1119 
     1120static pj_status_t get_available_cmds(pj_cli_sess *sess, 
     1121                                      pj_cli_cmd_spec *cmd,  
     1122                                      pj_str_t *cmd_val, 
     1123                                      unsigned argc, 
     1124                                      pj_pool_t *pool, 
     1125                                      pj_bool_t get_cmd, 
     1126                                      pj_cli_parse_mode parse_mode, 
     1127                                      pj_cli_cmd_spec **p_cmd, 
     1128                                      pj_cli_exec_info *info) 
     1129{ 
     1130    pj_status_t status = PJ_SUCCESS; 
     1131    pj_str_t *prefix; 
     1132    pj_str_t EMPTY_STR = {NULL, 0}; 
     1133 
     1134    prefix = cmd_val?(pj_strtrim(cmd_val)):(&EMPTY_STR); 
     1135 
     1136    info->hint_cnt = 0;     
     1137 
     1138    if (get_cmd) 
     1139        status = get_match_cmds(cmd, prefix, pool, p_cmd, info); 
     1140    if (argc > 0) 
     1141        status = get_match_args(sess, cmd, prefix, argc, pool, parse_mode, info); 
     1142 
     1143    if (status == PJ_SUCCESS) {  
     1144        if (prefix->slen > 0) { 
     1145            if (info->hint_cnt == 0) { 
     1146                status = PJ_CLI_EINVARG; 
     1147            } else if (info->hint_cnt > 1) { 
     1148                status = PJ_CLI_EAMBIGUOUS; 
     1149            } 
     1150        } else { 
     1151            if (info->hint_cnt > 0) 
     1152                status = PJ_CLI_EAMBIGUOUS; 
     1153        } 
     1154    }  
     1155 
     1156    return status; 
     1157} 
     1158 
Note: See TracChangeset for help on using the changeset viewer.