Index: include/asterisk/cli.h =================================================================== --- include/asterisk/cli.h (revision 47402) +++ include/asterisk/cli.h (working copy) @@ -44,7 +44,23 @@ #define AST_CLI_COMPLETE_EOF "_EOF_" -/*! \brief A command line entry */ +/*! + * New-style CLI handler use a single function to implement all the + * functionality of the cli handler. Called with the default arguments, + * (argc >0), the handler simply implements the command as before. + * A negative argc indicates one of the other functions, namely + * generate the usage string, the full command, or implement the generator. + * As a trick to extend the interface while being backward compatible, + * argv[-1] points to a struct ast_cli_args, and for the generator, + * argv[0] is really a pointer to a struct ast_cli_args. + * The return string is obtained by casting the result to char * + */ +enum ast_cli_fn { + CLI_USAGE = -1, /* return the usage string */ + CLI_CMD_STRING = -2, /* return the command string */ + CLI_GENERATE = -3, /* behave as 'generator', remap argv to struct ast_cli_args */ +}; + struct ast_cli_entry { char * const cmda[AST_MAX_CMD_LEN]; /*! Handler for the command (fd for output, # of args, argument list). @@ -78,12 +94,24 @@ It then gets set to something different when the deprecated command is run for the first time (ie; after we warn the user that it's deprecated) */ + char *new_style_buf; /* new style handler */ + int args; /* number of non-null entries in cmda */ int deprecated; char *_deprecated_by; /* copied from the "parent" _full_cmd, on deprecated commands */ /*! For linking */ AST_LIST_ENTRY(ast_cli_entry) list; }; +/* argument for new-style CLI handler */ +struct ast_cli_args { +/*! \brief A command line entry */ + char fake[4]; /* a fake string, in the first position, for safety */ + const char *line; /* the current input line */ + const char *word; /* the word we want to complete */ + int pos; /* position of the word to complete */ + int n; /* the iteration count (n-th entry we generate) */ +}; + /*! * \brief Helper function to generate cli entries from a NULL-terminated array. * Returns the n-th matching entry from the array, or NULL if not found. Index: main/cli.c =================================================================== --- main/cli.c (revision 47402) +++ main/cli.c (working copy) @@ -1166,6 +1166,14 @@ AST_LIST_LOCK(&helpers); AST_LIST_REMOVE(&helpers, e, list); AST_LIST_UNLOCK(&helpers); + free(e->_full_cmd); + e->_full_cmd = NULL; + if (e->new_style_buf) { + ((char **)e->cmda)[0] = NULL; + free(e->new_style_buf); + e->new_style_buf = NULL; + e->usage = NULL; + } } return 0; } @@ -1174,8 +1182,31 @@ { struct ast_cli_entry *cur; char fulle[80] =""; - int lf, ret = -1; - + int i, lf, ret = -1; + + if (e->cmda[0] == NULL) { /* new style entry, run the handler to init fields */ + char *args[2] = { (char *)e, NULL }; + char *s = (char *)(e->handler(-1, CLI_CMD_STRING, args+1)); + char **dst = (char **)e->cmda; /* need to cast as the entry is readonly */ + + s = ast_skip_blanks(s); + s = e->new_style_buf = ast_strdup(s); + for (i=0; !ast_strlen_zero(s) && i < AST_MAX_CMD_LEN-1; i++) { + *dst++ = s; /* store string */ + s = ast_skip_nonblanks(s); + if (*s == '\0') /* we are done */ + break; + *s++ = '\0'; + s = ast_skip_blanks(s); + } + *dst++ = NULL; + e->usage = (char *)(e->handler(-1, CLI_USAGE, args+1)); + ast_verbose("running new-style CLI entry for %s\n", e->new_style_buf); + sleep(1); + } + for (i = 0; e->cmda[i]; i++) + ; + e->args = i; ast_join(fulle, sizeof(fulle), e->cmda); AST_LIST_LOCK(&helpers); @@ -1479,17 +1510,38 @@ } if (lock) AST_LIST_LOCK(&helpers); - while( !ret && (e = cli_next(&i)) ) { + while ( (e = cli_next(&i)) ) { int lc = strlen(e->_full_cmd); if (e->_full_cmd[0] != '_' && lc > 0 && matchlen <= lc && !strncasecmp(matchstr, e->_full_cmd, matchlen)) { /* Found initial part, return a copy of the next word... */ - if (e->cmda[argindex] && ++matchnum > state) + if (e->cmda[argindex] && ++matchnum > state) { ret = strdup(e->cmda[argindex]); /* we need a malloced string */ - } else if (e->generator && !strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) { - /* We have a command in its entirity within us -- theoretically only one - command can have this occur */ - ret = e->generator(matchstr, word, argindex, state); + break; + } + } else if (!strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) { + /* This entry is a prefix of the command string entered + * (only one entry in the list should have this property). + * Run the generator if one is available. In any case we are done. + */ + if (e->generator) + ret = e->generator(matchstr, word, argindex, state); + else if (e->new_style_buf) { /* new style command */ + /* prepare fake arguments for the generator. + * argv[-1] is the cli entry we use, + * argv[0] is a pointer to the generator arguments, + * with a fake string '-' at the beginning so we can + * dereference it as a string with no trouble, + * and then the usual NULL terminator. + */ + struct ast_cli_args a = { + .fake = "-", + .line = matchstr, .word = word, + .pos = argindex, .n = state }; + char *args[] = { (char *)e, (char *)&a, NULL }; + ret = (char *)e->handler(-1, CLI_GENERATE, args + 1); + } + break; } } if (lock) @@ -1505,24 +1557,28 @@ int ast_cli_command(int fd, const char *s) { - char *argv[AST_MAX_ARGS]; + char *args[AST_MAX_ARGS + 1]; struct ast_cli_entry *e; int x; char *dup; int tws; - if (!(dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws))) + if (!(dup = parse_args(s, &x, args + 1, AST_MAX_ARGS, &tws))) return -1; /* We need at least one entry, or ignore */ if (x > 0) { AST_LIST_LOCK(&helpers); - e = find_cli(argv, 0); + e = find_cli(args + 1, 0); if (e) e->inuse++; AST_LIST_UNLOCK(&helpers); if (e) { - switch(e->handler(fd, x, argv)) { + /* within calling the handler, argv[-1] contains a pointer + * to the cli entry, and the array is null-terminated + */ + args[0] = (char *)e; + switch(e->handler(fd, x, args + 1)) { case RESULT_SHOWUSAGE: if (e->usage) ast_cli(fd, "%s", e->usage); @@ -1539,7 +1595,7 @@ break; } } else - ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv)); + ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(args + 1)); if (e) ast_atomic_fetchadd_int(&e->inuse, -1); } Index: main/astobj2.c =================================================================== --- main/astobj2.c (revision 47500) +++ main/astobj2.c (working copy) @@ -664,6 +665,36 @@ return 0; } +static int test_new_cli(int fd, int argc, char *argv[]) +{ + struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1]; + struct ast_cli_args *a; + + switch(argc) { + case CLI_USAGE: + return (int)"this is the usage string for astobj2 foo bar \n"; + + case CLI_CMD_STRING: + return (int)"astobj2 foo bar"; + + case CLI_GENERATE: + a = (struct ast_cli_args *)argv[0]; + if (a->pos > e->args) /* only extend one word */ + return NULL; + if (a->n < 10) { + char *buf; + asprintf(&buf, "%d", a->n); + return (int)buf; + } + return NULL; + default: /* regular handler */ + /* we are guaranteed to be called with argc >= e->args; */ + if (argc > e->args + 1) /* we only accept one extra field */ + return RESULT_SHOWUSAGE; + ast_cli(fd, "this is test_new_cli %d for [%d] %s\n", argc, e->args, e->_full_cmd); + return RESULT_SUCCESS; + } +} static struct ast_cli_entry cli_astobj2[] = { { { "astobj2", "stats", NULL }, handle_astobj2_stats, "Print astobj2 statistics", @@ -671,6 +702,7 @@ { { "astobj2", "test", NULL }, handle_astobj2_test, "Test astobj2", NULL }, + { { NULL }, test_new_cli }, }; int astobj2_init(void);