X-Git-Url: http://git.droids-corp.org/?a=blobdiff_plain;f=src%2Flib%2Fcmdline_parse.c;h=ceb22d620c1e1722634041b713c95ced797de946;hb=0c04fc6ac5c90753ff582486e2602b7ed429623d;hp=85958d0e4a9e9917b83c607e5e3f0574f2f13e7b;hpb=6f32a68393e01b4179592b9f48255179e8ad55f7;p=libcmdline.git diff --git a/src/lib/cmdline_parse.c b/src/lib/cmdline_parse.c index 85958d0..ceb22d6 100644 --- a/src/lib/cmdline_parse.c +++ b/src/lib/cmdline_parse.c @@ -1,3 +1,37 @@ +/*- + * Copyright (c) <2010>, Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * - Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* * Copyright (c) 2009, Olivier MATZ * All rights reserved. @@ -29,22 +63,33 @@ #include #include #include +#include +#include +#include #include #include "cmdline_parse.h" #include "cmdline.h" -//#define CMDLINE_DEBUG //#define debug_printf printf #define debug_printf(args...) do {} while(0) +/* used internally for cmdline_help() and cmdline_complete() */ +struct cmdline_preparse { + int nb_valid_tok; /* number of valid tokens in the buffer */ + void *opaque; /* pointer to opaque data */ + char comp_tok_buf[CMDLINE_MAX_TOKEN_SIZE]; /* token to complete */ + int comp_tok_len; /* length of the token to complete */ + int comp_tok_offset; /* offset of token to complete in the line buf */ +}; + /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our * own. */ static int isblank2(char c) { - if (c == ' ' || + if (c == ' ' || c == '\t' ) return 1; return 0; @@ -53,7 +98,7 @@ isblank2(char c) static int isendofline(char c) { - if (c == '\n' || + if (c == '\n' || c == '\r' ) return 1; return 0; @@ -88,367 +133,574 @@ nb_common_chars(const char * s1, const char * s2) return i; } -/** +/* quote a string and escape original quotes */ +int cmdline_quote_token(char *dst, unsigned dstlen, const char *src) +{ + unsigned s = 0, d = 0; + + /* the 2 quotes + '\0' */ + if (dstlen < 3) + return -EMSGSIZE; + + dst[d++] = '"'; + while (src[s] != '\0') { + if (d >= (dstlen-2)) + return -EMSGSIZE; + + if (src[s] == '"') + dst[d++] = '\\'; + if (src[s] == '\\' && src[s+1] == '"') + dst[d++] = '\\'; + + dst[d++] = src[s++]; + } + + if (d >= (dstlen-2)) + return -EMSGSIZE; + dst[d++] = '"'; + dst[d++] = '\0'; + return s; +} + +/* Remove quote and stop when we reach the end of token. Return the + * number of "eaten" bytes from the source buffer, or a negative value + * on error */ +int cmdline_get_token(char *dst, unsigned dstlen, const char *src) +{ + unsigned s = 0, d = 0; + int quoted = 0; + + /* skip spaces */ + while (isblank2(src[s])) + s++; + + /* empty token */ + if (cmdline_isendoftoken(src[s])) + return -EINVAL; + + /* copy token and remove quotes */ + while (src[s] != '\0') { + if (d >= dstlen) + return -EMSGSIZE; + + if (cmdline_isendoftoken(src[s]) && quoted == 0) + break; + + if (src[s] == '\\' && src[s+1] == '"') { + dst[d++] = '"'; + s += 2; + continue; + } + if (src[s] == '\\' && src[s+1] == '\\') { + dst[d++] = '\\'; + s += 2; + continue; + } + if (src[s] == '"') { + s++; + quoted = !quoted; + continue; + } + dst[d++] = src[s++]; + } + + /* not enough room in dst buffer */ + if (d >= (dstlen-1)) + return -EMSGSIZE; + + /* end of string during quote */ + if (quoted) + return -EINVAL; + + dst[d++] = '\0'; + return s; +} + +/* return the nth token from src and copy it in dst. Return the offset + * of the token in src, or a negative value on error. Note: the index + * of the first token is 0. */ +static int cmdline_get_nth_token(char *dst, unsigned dstlen, int n, + const char *src) +{ + int ret = 0, offset = 0; + + do { + offset += ret; + + /* skip spaces */ + while (isblank2(src[offset])) + offset++; + + /* get the token starting at offset */ + ret = cmdline_get_token(dst, dstlen, src + offset); + if (ret < 0) + return ret; + + } while (n--); + + return offset; +} + +/* * try to match the buffer with an instruction (only the first * nb_match_token tokens if != 0). Return 0 if we match all the * tokens, else the number of matched tokens, else -1. + +retval à vérifier. + */ static int -match_inst(cmdline_parse_inst_t *inst, const char *buf, unsigned int nb_match_token, - void * result_buf) +match_inst(cmdline_parse_inst_t *inst, const char *linebuf, + unsigned int nb_match_token, void *resbuf, unsigned resbuf_size) { - unsigned int token_num=0; - cmdline_parse_token_hdr_t * token_p; - unsigned int i=0; - int n = 0; - struct cmdline_token_hdr token_hdr; + unsigned int token_num = 0; + cmdline_parse_token_hdr_t *token; + int n = 0, res; + char token_str[CMDLINE_MAX_TOKEN_SIZE]; + + token = inst->tokens[token_num]; - token_p = inst->tokens[token_num]; - if (token_p) - memcpy(&token_hdr, token_p, sizeof(token_hdr)); - /* check if we match all tokens of inst */ - while (token_p && (!nb_match_token || i= nb_match_token) + return 0; + debug_printf("TK\n"); - /* skip spaces */ - while (isblank2(*buf)) { - buf++; - } - - /* end of buf */ - if ( isendofline(*buf) || iscomment(*buf) ) + + /* copy token and remove quotes */ + n = cmdline_get_token(token_str, sizeof(token_str), linebuf); + if (n < 0) break; - - n = token_hdr.ops->parse(token_p, buf, (result_buf ? result_buf+token_hdr.offset : NULL)); - if ( n < 0 ) + + /* parse this token */ + if (resbuf == NULL) + res = token->ops->parse(token, token_str, NULL, 0); + else { + unsigned rb_sz; + void *rb = (char *)resbuf + token->offset; + + /* not enough room to store result */ + if (token->offset > resbuf_size) + return -ENOBUFS; + + rb_sz = resbuf_size - token->offset; + res = token->ops->parse(token, token_str, rb, rb_sz); + } + + /* does not match this token */ + if (res < 0) break; + debug_printf("TK parsed (len=%d)\n", n); - i++; - buf += n; - + linebuf += n; token_num ++; - token_p = inst->tokens[token_num]; - if (token_p) - memcpy(&token_hdr, token_p, sizeof(token_hdr)); + token = inst->tokens[token_num]; } - + /* does not match */ - if (i==0) - return -1; - - /* in case we want to match a specific num of token */ - if (nb_match_token) { - if (i == nb_match_token) { - return 0; - } - return i; - } + if (token_num == 0) + return -ENOENT; /* we don't match all the tokens */ - if (token_p) { - return i; - } + if (token) + return token_num; /* are there are some tokens more */ - while (isblank2(*buf)) { - buf++; - } - - /* end of buf */ - if ( isendofline(*buf) || iscomment(*buf) ) + while (isblank2(*linebuf)) + linebuf++; + + /* end of buf, we match all inst */ + if (*linebuf == '\0' || isendofline(*linebuf) || iscomment(*linebuf)) return 0; /* garbage after inst */ - return i; + return token_num; } -int -cmdline_parse(struct cmdline *cl, const char * buf) +/* Check if a line buffer is valid and can be parsed or completed. The + * parsing stops when \n or \0 is reached. The also function checks + * that tokens are correctly quoted. The number of tokens in the + * buffer is returned. */ +static int cmdline_validate_linebuf(const char *linebuf) { - unsigned int inst_num=0; - cmdline_parse_inst_t *inst; - const char *curbuf; - char result_buf[BUFSIZ]; /* XXX align, size zé in broblém */ - void (*f)(void *, struct cmdline *, void *) = NULL; - void *data = NULL; - int comment = 0; - int linelen = 0; - int parse_it = 0; - int err = CMDLINE_PARSE_NOMATCH; - int tok; - cmdline_parse_ctx_t *ctx = cl->ctx; -#ifdef CMDLINE_DEBUG - char debug_buf[BUFSIZ]; -#endif - - /* - * - look if the buffer contains at least one line - * - look if line contains only spaces or comments - * - count line length - */ - curbuf = buf; - while (! isendofline(*curbuf)) { - if ( *curbuf == '\0' ) { - debug_printf("Incomplete buf (len=%d)\n", linelen); - return 0; + int quoted = 0, comment = 0; + int i = 0, nbtok = 0, token = 0; + + while (linebuf[i] != '\0') { + if (isendofline(linebuf[i]) && quoted == 0) + break; + if (comment == 1) { + i++; + continue; } - if ( iscomment(*curbuf) ) { + if (iscomment(linebuf[i]) && quoted == 0) { comment = 1; + i ++; + continue; } - if ( ! isblank2(*curbuf) && ! comment) { - parse_it = 1; + + /* end of token */ + if (isblank2(linebuf[i]) && quoted == 0) + token = 0; + /* new token */ + if (!isblank2(linebuf[i]) && token == 0) { + token = 1; + nbtok++; } - curbuf++; - linelen++; - } - /* skip all endofline chars */ - while (isendofline(buf[linelen])) { - linelen++; + if (linebuf[i] == '\\' && linebuf[i+1] == '"') { + i += 2; + continue; + } + if (linebuf[i] == '\\' && linebuf[i+1] == '\\') { + i += 2; + continue; + } + if (linebuf[i] == '"') { + i++; + quoted = !quoted; + continue; + } + i++; } + if (quoted) + return CMDLINE_PARSE_UNTERMINATED_QUOTE; + return nbtok; +} - /* empty line */ - if ( parse_it == 0 ) { - debug_printf("Empty line (len=%d)\n", linelen); - return linelen; - } +/* Try to parse a buffer according to the specified context. The + * argument linebuf must end with \n or \0. */ +int +cmdline_parse(cmdline_parse_ctx_t *ctx, const char *linebuf, void *opaque) +{ + cmdline_parse_inst_t **pinst; + cmdline_parse_inst_t *inst; + char result_buf[CMDLINE_MAX_DSTBUF_SIZE]; + void (*f)(void *, struct cmdline *, void *) = NULL; + void *data = NULL; + int ret; + + ret = cmdline_validate_linebuf(linebuf); + if (ret < 0) + return ret; + if (ret == 0) + return CMDLINE_PARSE_EMPTY; -#ifdef CMDLINE_DEBUG - snprintf(debug_buf, (linelen>64 ? 64 : linelen), "%s", buf); - debug_printf("Parse line : len=%d, <%s>\n", linelen, debug_buf); -#endif /* parse it !! */ - inst = ctx[inst_num]; - while (inst) { - debug_printf("INST %d\n", inst_num); + for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) { + inst = *pinst; + debug_printf("INST\n"); /* fully parsed */ - tok = match_inst(inst, buf, 0, result_buf); + ret = match_inst(inst, linebuf, 0, result_buf, sizeof(result_buf)); - if (tok > 0) /* we matched at least one token */ - err = CMDLINE_PARSE_BAD_ARGS; + if (ret != 0) + continue; - else if (!tok) { - debug_printf("INST fully parsed\n"); - /* skip spaces */ - while (isblank2(*curbuf)) { - curbuf++; - } - - /* if end of buf -> there is no garbage after inst */ - if (isendofline(*curbuf) || iscomment(*curbuf)) { - if (!f) { - memcpy(&f, &inst->f, sizeof(f)); - memcpy(&data, &inst->data, sizeof(data)); - } - else { - /* more than 1 inst matches */ - err = CMDLINE_PARSE_AMBIGUOUS; - f=NULL; - debug_printf("Ambiguous cmd\n"); - break; - } - } + debug_printf("INST fully parsed\n"); + + /* if end of buf -> there is no garbage after inst */ + if (f != NULL) { + /* more than 1 inst matches */ + debug_printf("Ambiguous cmd\n"); + return CMDLINE_PARSE_AMBIGUOUS; } - - inst_num ++; - inst = ctx[inst_num]; + f = inst->f; + data = inst->data; } - + /* call func */ - if (f) { - f(result_buf, cl, data); - } + if (f == NULL) + return CMDLINE_PARSE_NOMATCH; - /* no match */ + f(result_buf, opaque, data); + return CMDLINE_PARSE_SUCCESS; +} + +/* called by cmdline_help() and cmdline_complete() to preparse the + * line buffer (the operations done are common to these functions) */ +static int cmdline_preparse(struct cmdline_preparse *preparse, const char *buf) +{ + int ret, len, nb_tok; + + /* count the number of tokens in the line buffer */ + ret = cmdline_validate_linebuf(buf); + if (ret < 0) + return ret; + nb_tok = ret; + + /* if last token is not complete, decrement nb_valid_tok */ + len = strlen(buf); + if (nb_tok == 0 || isblank2(buf[len - 1])) { + preparse->nb_valid_tok = nb_tok; + preparse->comp_tok_offset = len; + preparse->comp_tok_buf[0] = '\0'; + preparse->comp_tok_len = 0; + } else { - debug_printf("No match err=%d\n", err); - return err; + preparse->nb_valid_tok = nb_tok - 1; + + /* get the incomplete token (can be empty) and return its + * offset in the buffer */ + preparse->comp_tok_offset = + cmdline_get_nth_token(preparse->comp_tok_buf, + sizeof(preparse->comp_tok_buf), + preparse->nb_valid_tok, + buf); + preparse->comp_tok_len = strlen(preparse->comp_tok_buf); } - - return linelen; + + return 0; } -int -cmdline_complete(struct cmdline *cl, const char *buf, int *state, - char *dst, unsigned int size) +/* Display a contextual help using the write_buf() function pointer + * given as parameter (called with its opaque pointer). The contextual + * help depends on the buffer given. */ +int cmdline_help(cmdline_parse_ctx_t *ctx, const char *buf, + cmdline_write_t *write_buf, void *opaque) { - const char *incomplete_token = buf; - unsigned int inst_num = 0; + cmdline_parse_token_hdr_t *token; + cmdline_parse_inst_t **pinst; cmdline_parse_inst_t *inst; - cmdline_parse_token_hdr_t *token_p; - struct cmdline_token_hdr token_hdr; - char tmpbuf[64], completion_buf[64]; - unsigned int incomplete_token_len; - int completion_len = -1; - int nb_token = 0; - unsigned int i, n; - int l; - unsigned int nb_completable; - unsigned int nb_non_completable; - unsigned int local_state=0; + struct cmdline_preparse preparse; + char helpbuf[CMDLINE_MAX_DSTBUF_SIZE]; + char tmpbuf[CMDLINE_MAX_DSTBUF_SIZE]; char *help_str; - cmdline_parse_ctx_t *ctx = cl->ctx; + int n, iterate; - debug_printf("%s called\n", __FUNCTION__); - /* count the number of complete token to parse */ - for (i=0 ; buf[i] ; i++) { - if (!isblank2(buf[i]) && isblank2(buf[i+1])) - nb_token++; - if (isblank2(buf[i]) && !isblank2(buf[i+1])) - incomplete_token = buf+i+1; - } - incomplete_token_len = strlen(incomplete_token); - - /* first call -> do a first pass */ - if (*state <= 0) { - debug_printf("try complete <%s>\n", buf); - debug_printf("there is %d complete tokens, <%s> is incomplete\n", nb_token, incomplete_token); - - nb_completable = 0; - nb_non_completable = 0; - - inst = ctx[inst_num]; - while (inst) { - /* parse the first tokens of the inst */ - if (nb_token && match_inst(inst, buf, nb_token, NULL)) - goto next; - - debug_printf("instruction match \n"); - token_p = inst->tokens[nb_token]; - if (token_p) - memcpy(&token_hdr, token_p, sizeof(token_hdr)); - - /* non completable */ - if (!token_p || - !token_hdr.ops->complete_get_nb || - !token_hdr.ops->complete_get_elt || - (n = token_hdr.ops->complete_get_nb(token_p)) == 0) { - nb_non_completable++; - goto next; - } + cmdline_preparse(&preparse, buf); + + debug_printf("display contextual help\n"); + + for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) { + inst = *pinst; + + /* match the beginning of the command */ + if (preparse.nb_valid_tok != 0 && + match_inst(inst, buf, preparse.nb_valid_tok, + NULL, 0) != 0) + continue; + + token = inst->tokens[preparse.nb_valid_tok]; - debug_printf("%d choices for this token\n", n); - for (i=0 ; icomplete_get_elt(token_p, i, tmpbuf, sizeof(tmpbuf)) < 0) - continue; - strcat(tmpbuf, " "); /* we have at least room for one char */ - debug_printf(" choice <%s>\n", tmpbuf); - /* does the completion match the beginning of the word ? */ - if (!strncmp(incomplete_token, tmpbuf, incomplete_token_len)) { - if (completion_len == -1) { - strcpy(completion_buf, tmpbuf+incomplete_token_len); - completion_len = strlen(tmpbuf+incomplete_token_len); - - } - else { - completion_len = nb_common_chars(completion_buf, - tmpbuf+incomplete_token_len); - completion_buf[completion_len] = 0; - } - nb_completable++; - } - } - next: - inst_num ++; - inst = ctx[inst_num]; + /* end of inst */ + if (token == NULL) { + n = snprintf(helpbuf, sizeof(helpbuf), "[Return]\n"); + if (n > 0) + write_buf(opaque, helpbuf, n); + continue; } - debug_printf("total choices %d for this completion\n", nb_completable); + /* token matches, but no completion */ + if (token->ops->complete_start == NULL || + token->ops->complete_iterate == NULL) + iterate = 0; + else + iterate = 1; + + /* store the incomplete token in tmpbuf */ + n = preparse.comp_tok_len + 1; + if (n > sizeof(tmpbuf)) + n = sizeof(tmpbuf); + snprintf(tmpbuf, n, "%s", preparse.comp_tok_buf); + + if (iterate == 1 && + token->ops->complete_start(token, tmpbuf, + &preparse.opaque) < 0) { + /* cancel iteration, complete_start() returned + * a negative value, meaning no completion */ + iterate = 0; + if (token->ops->complete_end != NULL) + token->ops->complete_end(token, + &preparse.opaque); + } - /* no possible completion */ - if (nb_completable == 0 && nb_non_completable == 0) - return 0; - - /* if multichoice is not required */ - if (*state == 0 && incomplete_token_len > 0) { - /* one or several choices starting with the - same chars */ - if (completion_len > 0) { - if (completion_len + 1 > size) - return 0; - - strcpy(dst, completion_buf); - return 2; - } + /* get token dynamic help string */ + if ((token->ops->help == NULL) || + (token->ops->help(token, tmpbuf, sizeof(tmpbuf)) < 0)) + snprintf(tmpbuf, sizeof(tmpbuf), "unknown"); + + /* get instruction static help string */ + help_str = inst->help_str; + if (help_str == NULL) + help_str = "No help"; + + /* send it to callback function */ + n = snprintf(helpbuf, sizeof(helpbuf), + "[%s]: %s\n", tmpbuf, help_str); + if (n >= 0 && n < sizeof(helpbuf)) + write_buf(opaque, helpbuf, n); + + if (iterate == 0) + continue; + + /* iterate over all possible completion for this inst */ + while (token->ops->complete_iterate(token, + &preparse.opaque, + tmpbuf, + sizeof(tmpbuf)) >= 0) { + + + debug_printf(" choice <%s>\n", tmpbuf); + + /* does the completion match the beginning of + * the word ? */ + if (strncmp(preparse.comp_tok_buf, tmpbuf, + preparse.comp_tok_len)) + continue; + + /* get the token and add it in help buffer */ + n = snprintf(helpbuf, sizeof(helpbuf), " %s\n", tmpbuf); + if (n >= 0 && n < sizeof(helpbuf)) + write_buf(opaque, helpbuf, n); } + + /* no more completion, go to next inst */ + if ( token->ops->complete_end != NULL) + token->ops->complete_end(token, &preparse.opaque); } - /* init state correctly */ - if (*state == -1) - *state = 0; - - debug_printf("Multiple choice STATE=%d\n", *state); - - inst_num = 0; - inst = ctx[inst_num]; - while (inst) { - /* we need to redo it */ - inst = ctx[inst_num]; - - if (nb_token && match_inst(inst, buf, nb_token, NULL)) - goto next2; - - token_p = inst->tokens[nb_token]; - if (token_p) - memcpy(&token_hdr, token_p, sizeof(token_hdr)); - - /* one choice for this token */ - if (!token_p || - !token_hdr.ops->complete_get_nb || - !token_hdr.ops->complete_get_elt || - (n = token_hdr.ops->complete_get_nb(token_p)) == 0) { - if (local_state < *state) { - local_state++; - goto next2; - } - (*state)++; - if (token_p && token_hdr.ops->get_help) { - token_hdr.ops->get_help(token_p, tmpbuf, sizeof(tmpbuf)); - help_str = inst->help_str; - if (help_str) - snprintf(dst, size, "[%s]: %s", tmpbuf, help_str); - else - snprintf(dst, size, "[%s]: No help", tmpbuf); - } - else { - snprintf(dst, size, "[RETURN]"); - } - return 1; + return 0; +} + +/* try to complete the buffer given as a parameter */ +int +cmdline_complete(cmdline_parse_ctx_t *ctx, const char *buf, + char *dst, unsigned int dstsize) +{ + cmdline_parse_token_hdr_t *token; + cmdline_parse_inst_t **pinst; + cmdline_parse_inst_t *inst; + struct cmdline_preparse preparse; + int nb_match = 0; + int nb_completion = 0; + int completion_len = CMDLINE_MAX_TOKEN_SIZE; + char completion_buf[CMDLINE_MAX_TOKEN_SIZE]; + char tmpbuf[CMDLINE_MAX_TOKEN_SIZE]; + int ret, n; + + debug_printf("%s called\n", __FUNCTION__); + + /* fill the preparse structure that contains infos that will + * help us to complete the buffer */ + ret = cmdline_preparse(&preparse, buf); + if (ret < 0) + return CMDLINE_COMPLETE_NONE; + + /* try to complete !! */ + for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) { + inst = *pinst; + + debug_printf("INST\n"); + + /* try to match the first tokens */ + if (preparse.nb_valid_tok != 0 && + match_inst(inst, buf, preparse.nb_valid_tok, + NULL, 0) != 0) + continue; + + nb_match ++; + token = inst->tokens[preparse.nb_valid_tok]; + + /* non completable */ + if (token == NULL || + token->ops->complete_start == NULL || + token->ops->complete_iterate == NULL) + continue; + + /* store the incomplete token in tmpbuf */ + n = preparse.comp_tok_len + 1; + if (n > sizeof(tmpbuf)) + n = sizeof(tmpbuf); + snprintf(tmpbuf, n, "%s", preparse.comp_tok_buf); + + /* non completable */ + if (token->ops->complete_start(token, tmpbuf, + &preparse.opaque) < 0) { + if (token->ops->complete_end != NULL) + token->ops->complete_end(token, + &preparse.opaque); + continue; } - /* several choices */ - for (i=0 ; icomplete_get_elt(token_p, i, tmpbuf, sizeof(tmpbuf)) < 0) - continue; - strcat(tmpbuf, " "); /* we have at least room for one char */ + /* all possible completion for this token */ + while (1) { + + ret = token->ops->complete_iterate(token, + &preparse.opaque, + tmpbuf, + sizeof(tmpbuf)-1); + + if (ret < 0) + break; + + debug_printf("Completion %s\n", tmpbuf); + + /* we kept at least the room for one char */ + if (ret == 0) + strcat(tmpbuf, " "); + debug_printf(" choice <%s>\n", tmpbuf); - /* does the completion match the beginning of the word ? */ - if (!strncmp(incomplete_token, tmpbuf, incomplete_token_len)) { - if (local_state < *state) { - local_state++; - continue; - } - (*state)++; - l=snprintf(dst, size, "%s", tmpbuf); - if (l>=0 && token_hdr.ops->get_help) { - token_hdr.ops->get_help(token_p, tmpbuf, sizeof(tmpbuf)); - help_str = inst->help_str; - if (help_str) - snprintf(dst+l, size-l, "[%s]: %s", tmpbuf, help_str); - else - snprintf(dst+l, size-l, "[%s]: No help", tmpbuf); - } - - return 1; + + /* does the completion match the beginning of + * the word ? */ + if (strncmp(preparse.comp_tok_buf, tmpbuf, + preparse.comp_tok_len)) + continue; + + /* first one, save the buffer */ + if (nb_completion == 0) { + completion_len = snprintf(completion_buf, + sizeof(completion_buf), + "%s", tmpbuf); + } + else { + n = nb_common_chars(completion_buf, tmpbuf); + if (n < completion_len) + completion_len = n; } + nb_completion ++; + + /* we cannot add any char, just display help */ + if (completion_len == preparse.comp_tok_len) + break; } - next2: - inst_num ++; - inst = ctx[inst_num]; + if (token->ops->complete_end != NULL) + token->ops->complete_end(token, &preparse.opaque); + + if (completion_len == preparse.comp_tok_len) + break; } - return 0; + + debug_printf("nb_completion=%d, completion_len=%d\n", + nb_completion, completion_len); + + /* one choice, append chars and return */ + if (nb_completion == 1) { + snprintf(dst, dstsize, "%s", + completion_buf + preparse.comp_tok_len); + return CMDLINE_COMPLETE_APPEND; + } + + /* many choices, but starting with same chars: append chars + * and return */ + if (nb_completion != 0 && completion_len > preparse.comp_tok_len) { + if (completion_len >= dstsize) + completion_len = dstsize - 1; + strncpy(dst, completion_buf + preparse.comp_tok_len, + completion_len - preparse.comp_tok_len); + dst[completion_len - preparse.comp_tok_len] = '\0'; + return CMDLINE_COMPLETE_APPEND; + } + + /* no match, nothing to do */ + if (nb_match == 0 || nb_completion == 0) + return CMDLINE_COMPLETE_NONE; + + return CMDLINE_COMPLETE_MANY; }