Esempio n. 1
0
/* 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;
}
Esempio n. 2
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);
}