- Timestamp:
- Nov 23, 2012 10:30:55 AM (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli.c
r3231 r4299 43 43 #endif 44 44 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 */ 54 struct 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 45 115 struct pj_cli_t 46 116 { … … 55 125 }; 56 126 127 /** 128 * Reserved command id constants. 129 */ 130 typedef 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 */ 148 typedef 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 167 static 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 */ 180 struct 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 */ 229 typedef 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 */ 254 static 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 264 PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) 265 { 266 return cmd->id; 267 } 268 57 269 PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) 58 270 { … … 86 298 fe = fe->next; 87 299 } 300 } 301 302 PJ_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); 88 313 } 89 314 … … 102 327 return PJ_SUCCESS; 103 328 case CLI_CMD_EXIT: 104 pj_cli_ end_session(cval->sess);329 pj_cli_sess_end_session(cval->sess); 105 330 return PJ_CLI_EEXIT; 106 331 default: … … 109 334 } 110 335 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 **/ 347 PJ_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 111 404 PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, 112 405 pj_cli_t **p_cli) 113 406 { 114 407 pj_pool_t *pool; 115 pj_cli_t *cli; 408 pj_cli_t *cli; 116 409 unsigned i; 410 /* This is an example of the command structure */ 117 411 char* cmd_xmls[] = { 118 412 "<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'>" 124 416 "</CMD>", 125 417 }; … … 146 438 pj_str_t xml = pj_str(cmd_xmls[i]); 147 439 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) != 149 441 PJ_SUCCESS) 150 442 TRACE_((THIS_FILE, "Failed to add command #%d", i)); 151 443 } 152 444 153 *p_cli = cli; 445 *p_cli = cli; 154 446 155 447 return PJ_SUCCESS; … … 224 516 } 225 517 226 PJ_DEF(void) pj_cli_ end_session(pj_cli_sess *sess)518 PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) 227 519 { 228 520 pj_assert(sess); … … 239 531 } 240 532 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 **/ 537 static 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 **/ 585 static 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 **/ 631 static 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; 251 639 pj_xml_attr *attr; 252 640 pj_xml_node *sub_node; … … 256 644 pj_status_t status = PJ_SUCCESS; 257 645 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 270 646 if (pj_stricmp2(&root->name, "CMD")) 271 ERROR_(PJ_EINVAL);647 return PJ_EINVAL; 272 648 273 649 /* Initialize the command spec */ … … 279 655 if (!pj_stricmp2(&attr->name, "name")) { 280 656 pj_strltrim(&attr->value); 281 pj_strrtrim(&attr->value);282 /* Check whether command with the specified name already exists */283 657 if (!attr->value.slen || 284 658 pj_hash_get(cli->hash, attr->value.ptr, 285 659 attr->value.slen, NULL)) 286 660 { 287 ERROR_(PJ_CLI_EBADNAME);661 return PJ_CLI_EBADNAME; 288 662 } 289 290 663 pj_strdup(cli->pool, &cmd->name, &attr->value); 291 664 } else if (!pj_stricmp2(&attr->name, "id")) { … … 324 697 PJ_CATCH_ANY { 325 698 pj_scan_fini(&scanner); 326 ERROR_(PJ_GET_EXCEPTION());699 return (PJ_GET_EXCEPTION()); 327 700 } 328 701 PJ_END; … … 331 704 pj_strdup(cli->pool, &cmd->desc, &attr->value); 332 705 } 333 334 706 attr = attr->next; 335 707 } 336 708 337 /* Get the command arguments */709 /* Get the command childs/arguments */ 338 710 sub_node = root->node_head.next; 339 711 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; 373 723 } 374 724 sub_node = sub_node->next; 375 725 } 376 726 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 385 727 if (!cmd->name.slen) 386 ERROR_(PJ_CLI_EBADNAME);728 return PJ_CLI_EBADNAME; 387 729 388 730 if (cmd->arg_cnt) { … … 395 737 pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); 396 738 cmd->arg[i].type = args[i].type; 739 cmd->arg[i].optional = args[i].optional; 397 740 } 398 741 } … … 414 757 cmd->handler = handler; 415 758 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 } 417 764 pj_list_push_back(group->sub_cmd, cmd); 418 else765 } else { 419 766 pj_list_push_back(cli->root.sub_cmd, cmd); 767 } 420 768 421 769 if (p_cmd) 422 p_cmd = cmd; 423 424 on_exit: 770 *p_cmd = cmd; 771 772 return status; 773 } 774 775 PJ_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); 425 800 pj_pool_release(pool); 426 427 801 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 804 PJ_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 { 436 810 pj_scanner scanner; 437 811 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; 440 817 441 818 PJ_USE_EXCEPTION; … … 443 820 PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); 444 821 445 if (!info) 446 info = &einfo; 822 PJ_UNUSED_ARG(pool); 823 824 str.slen = 0; 447 825 pj_cli_exec_info_default(info); 448 826 449 /* Parse the command line. */827 /* Set the parse mode based on the latest char. */ 450 828 len = pj_ansi_strlen(cmdline); 451 if (len > 0 && cmdline[len - 1] == '\ n') {829 if (len > 0 && cmdline[len - 1] == '\r') { 452 830 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 } 508 925 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 926 return status; 927 } 928 929 PJ_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) 516 933 { 517 934 pj_cli_cmd_val val; 518 935 pj_status_t status; 519 936 pj_cli_exec_info einfo; 937 pj_str_t cmd; 520 938 521 939 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; 522 948 523 949 if (!info) 524 950 info = &einfo; 525 status = pj_cli_ parse(sess, cmdline, &val, info);951 status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); 526 952 if (status != PJ_SUCCESS) 527 953 return status; 528 954 529 if ( val.cmd->handler) {955 if ((val.argc > 0) && (val.cmd->handler)) { 530 956 info->cmd_ret = (*val.cmd->handler)(&val); 531 957 if (info->cmd_ret == PJ_CLI_EINVARG || … … 536 962 return PJ_SUCCESS; 537 963 } 964 965 static 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 992 static 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 1042 static 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 1120 static 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.