/* Returns 0 if a command is matched and parsed, with the results of the parsing in the '*parsed' * structure. * * Returns 1 and logs an error if no command matches the argument list, contents of '*parsed' are * undefined. * * Returns 2 if the argument list is ambiguous, ie, matches more than one command, contents of * '*parsed' are undefined. * * Returns -1 and logs an error if the parsing fails due to an internal error (eg, malformed command * schema), contents of '*parsed' are undefined. * * @author Andrew Bettison <*****@*****.**> */ int cli_parse(const int argc, const char *const *args, const struct cli_schema *commands, struct cli_parsed *parsed) { int ambiguous = 0; int matched_cmd = -1; int cmd; for (cmd = 0; commands[cmd].function; ++cmd) { struct cli_parsed cmdpa; memset(&cmdpa, 0, sizeof cmdpa); cmdpa.commands = commands; cmdpa.cmdi = cmd; cmdpa.args = args; cmdpa.argc = argc; cmdpa.labelc = 0; cmdpa.varargi = -1; const char *pattern = NULL; unsigned arg = 0; unsigned opt = 0; while ((pattern = commands[cmd].words[opt])) { //DEBUGF("cmd=%d opt=%d pattern='%s' args[arg=%d]='%s'", cmd, opt, pattern, arg, arg < argc ? args[arg] : ""); unsigned patlen = strlen(pattern); if (cmdpa.varargi != -1) return WHYF("Internal error: commands[%d].word[%d]=\"%s\" - more words not allowed after \"...\"", cmd, opt, commands[cmd].words[opt]); /* These are the argument matching rules: * * "..." consumes all remaining arguments * * "word" consumes one argument that exactly matches "word", does not label it (this is the * "simple" case in the code below; all other rules label something that matched) * * "word1|word2|...|wordN" consumes one argument that exactly matches "word1" or "word2" etc. * or "wordN", labels it with the matched word (an empty alternative, eg "|word" does not * match an empty argument) * * (as a special case of the above rule, "|word" consumes one argument that exactly matches * "word" and labels it "word", but it appears in the help description as "word") * * "<label>" consumes exactly one argument "ANY", records it with label "label" * * "prefix=<any>" consumes one argument "prefix=ANY" or two arguments "prefix" "ANY", * and records the text matching ANY with label "prefix" * * "prefix <any>" consumes one argyment "prefix ANY" if available or two arguments "prefix" * "ANY", and records the text matching ANY with label "prefix" * * "prefix<any>" consumes one argument "prefixANY", and records the text matching ANY with * label "prefix" * * "[ANY]..." consumes all remaining arguments which match ANY, as defined below * * "[word]" consumes one argument if it exactly matches "word", records it with label * "word" * * "[word1|word2|...|wordN]" consumes one argument if it exactly matches "word1" or "word2" * etc. or "wordN", labels it with the matched word * * "[<label>]" consumes one argument "ANY" if available, records it with label "label" * * "[prefix=<any>]" consumes one argument "prefix=ANY" if available or two arguments * "prefix" "ANY" if available, records the text matching ANY with label "prefix" * * "[prefix <any>]" consumes one argument "prefix ANY" if available or two arguments * "prefix" "ANY" if available, records the text matching ANY with label "prefix" * * "[prefix<any>]" consumes one argument "prefixANY" if available, records the text matching * ANY with label "prefix" */ if (patlen == 3 && pattern[0] == '.' && pattern[1] == '.' && pattern[2] == '.') { cmdpa.varargi = arg; arg = argc; ++opt; } else { int optional = 0; int repeating = 0; if (patlen > 5 && pattern[0] == '[' && pattern[patlen-4] == ']' && pattern[patlen-3] == '.' && pattern[patlen-2] == '.' && pattern[patlen-1] == '.') { optional = repeating = 1; pattern += 1; patlen -= 5; } else if (patlen > 2 && pattern[0] == '[' && pattern[patlen-1] == ']') { optional = 1; pattern += 1; patlen -= 2; } unsigned oarg = arg; const char *text = NULL; const char *label = NULL; unsigned labellen = 0; const char *word = pattern; unsigned wordlen = 0; char simple = 0; unsigned alt = 0; if (patlen && *word == '|') { ++alt; ++word; } if (patlen == 0) return WHYF("Internal error: commands[%d].word[%d]=\"%s\" - empty words not allowed", cmd, opt, commands[cmd].words[opt]); for (; word < &pattern[patlen]; word += wordlen + 1, ++alt) { // Skip over empty "||word" alternative (but still count it). if (*word == '|') return WHYF("Internal error: commands[%d].word[%d]=\"%s\" - empty alternatives not allowed", cmd, opt, commands[cmd].words[opt]); // Find end of "word|" alternative. wordlen = 1; while (&word[wordlen] < &pattern[patlen] && word[wordlen] != '|') ++wordlen; // Skip remaining alternatives if we already got a match. if (text) continue; // Look for a match. const char *prefix = NULL; unsigned prefixlen = 0; char prefixarglen = 0; const char *caret = strchr(word, '<'); if (wordlen > 2 && caret && word[wordlen-1] == '>') { if ((prefixarglen = prefixlen = caret - word)) { prefix = word; if (prefixlen > 1 && (prefix[prefixlen-1] == '=' || prefix[prefixlen-1] == ' ')) --prefixarglen; label = prefix; labellen = prefixarglen; if (arg < argc) { unsigned arglen = strlen(args[arg]); if (arglen >= prefixlen && strncmp(args[arg], prefix, prefixlen) == 0) { text = args[arg++] + prefixlen; } else if (arg + 1 < argc && arglen == prefixarglen && strncmp(args[arg], prefix, prefixarglen) == 0) { ++arg; text = args[arg++]; } } } else { label = &word[1]; labellen = wordlen - 2; if (arg < argc) text = args[arg++]; } } else if (arg < argc && strlen(args[arg]) == wordlen && strncmp(args[arg], word, wordlen) == 0) { simple = 1; text = args[arg]; label = word; labellen = wordlen; ++arg; } } assert(alt > 0); if (arg == oarg && !optional) break; if (labellen && text && (optional || !simple || alt > 1)) { if (cmdpa.labelc >= NELS(cmdpa.labelv)) return WHYF("Internal error: commands[%d].word[%d]=\"%s\" - label limit exceeded", cmd, opt, commands[cmd].words[opt]); cmdpa.labelv[cmdpa.labelc].label = label; cmdpa.labelv[cmdpa.labelc].len = labellen; cmdpa.labelv[cmdpa.labelc].text = text; ++cmdpa.labelc; if (!repeating) ++opt; } else ++opt; } } //DEBUGF("cmd=%d opt=%d args[arg=%d]='%s'", cmd, opt, arg, arg < argc ? args[arg] : ""); if (!pattern && arg == argc) { /* A match! We got through the command definition with no internal errors and all literal args matched and we have a proper number of args. If we have multiple matches, then note that the call is ambiguous. */ if (matched_cmd >= 0) ++ambiguous; if (ambiguous == 1) { NOWHENCE(WHY_argv("Ambiguous command:", argc, args)); NOWHENCE(HINT("Matches the following:")); NOWHENCE(HINT_argv(" ", argc, commands[matched_cmd].words)); } if (ambiguous) NOWHENCE(HINT_argv(" ", argc, commands[cmd].words)); matched_cmd = cmd; *parsed = cmdpa; } } /* Don't process ambiguous calls */ if (ambiguous) return 2; /* Complain if we found no matching calls */ if (matched_cmd < 0) { if (argc) NOWHENCE(WHY_argv("Unknown command:", argc, args)); return 1; } return 0; }
int cli_execute(const char *argv0, int argc, const char *const *args, struct command_line_option *options, void *context){ int ambiguous=0; int cli_call=-1; int i; for(i=0;options[i].function;i++) { int j; const char *word = NULL; int optional = 0; int mandatory = 0; for (j = 0; (word = options[i].words[j]); ++j) { int wordlen = strlen(word); if (optional < 0) { WHYF("Internal error: command_line_options[%d].word[%d]=\"%s\" not allowed after \"...\"", i, j, word); break; } else if (!( (wordlen > 2 && word[0] == '<' && word[wordlen-1] == '>') || (wordlen > 4 && word[0] == '[' && word[1] == '<' && word[wordlen-2] == '>' && word[wordlen-1] == ']') || (wordlen > 0) )) { WHYF("Internal error: command_line_options[%d].word[%d]=\"%s\" is malformed", i, j, word); break; } else if (word[0] == '<') { ++mandatory; if (optional) { WHYF("Internal error: command_line_options[%d].word[%d]=\"%s\" should be optional", i, j, word); break; } } else if (word[0] == '[') { ++optional; } else if (wordlen == 3 && word[0] == '.' && word[1] == '.' && word[2] == '.') { optional = -1; } else { ++mandatory; if (j < argc && strcasecmp(word, args[j])) // literal words don't match break; } } if (!word && argc >= mandatory && (optional < 0 || argc <= mandatory + optional)) { /* A match! We got through the command definition with no internal errors and all literal args matched and we have a proper number of args. If we have multiple matches, then note that the call is ambiguous. */ if (cli_call>=0) ambiguous++; if (ambiguous==1) { WHY("Ambiguous command line call:"); WHY_argv(" ", argc, args); WHY("Matches the following known command line calls:"); WHY_argv(" ", argc, options[cli_call].words); } if (ambiguous) WHY_argv(" ", argc, options[i].words); cli_call=i; } } /* Don't process ambiguous calls */ if (ambiguous) return -1; /* Complain if we found no matching calls */ if (cli_call<0) { if (argc) { WHY("Unknown command line call:"); WHY_argv(" ", argc, args); } INFO("Use \"help\" command to see a list of valid commands"); return -1; } /* Otherwise, make call */ return options[cli_call].function(argc, args, &options[cli_call], context); }