mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-01-18 10:46:23 +00:00
Improve command-line parsing (issue #59)
Emit HINT log message about using "help" command Support alternatives syntax "word|word|..." in CLI schema Better return values from cli_parse()
This commit is contained in:
parent
b826ac1f1b
commit
5bbdef5587
192
cli.c
192
cli.c
@ -1,5 +1,25 @@
|
||||
/*
|
||||
Serval DNA command-line functions
|
||||
Copyright (C) 2010-2013 Serval Project, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
#include "cli.h"
|
||||
#include "log.h"
|
||||
#include "serval.h"
|
||||
@ -49,6 +69,20 @@ int cli_usage_args(const int argc, const char *const *args, const struct cli_sch
|
||||
return 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 <andrew@servalproject.com>
|
||||
*/
|
||||
int cli_parse(const int argc, const char *const *args, const struct cli_schema *commands, struct cli_parsed *parsed)
|
||||
{
|
||||
int ambiguous = 0;
|
||||
@ -63,24 +97,27 @@ int cli_parse(const int argc, const char *const *args, const struct cli_schema *
|
||||
cmdpa.argc = argc;
|
||||
cmdpa.labelc = 0;
|
||||
cmdpa.varargi = -1;
|
||||
const char *word = NULL;
|
||||
const char *pattern = NULL;
|
||||
unsigned arg = 0;
|
||||
unsigned opt = 0;
|
||||
while ((word = commands[cmd].words[opt])) {
|
||||
//DEBUGF("cmd=%d opt=%d word='%s' args[arg=%d]='%s'", cmd, opt, word, arg, arg < argc ? args[arg] : "");
|
||||
unsigned wordlen = strlen(word);
|
||||
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, word);
|
||||
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
|
||||
* "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)
|
||||
*
|
||||
* "\word" consumes one argument that exactly matches "word" and labels it "word"
|
||||
* "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)
|
||||
*
|
||||
* "[word]" consumes one argument if it exactly matches "word", records it with label
|
||||
* "word"
|
||||
* (as a result of the above rule, "|word" or "word|" or "|word|" consumes one argument that
|
||||
* exactly matches "word" and labels it "word")
|
||||
*
|
||||
* "<label>" consumes exactly one argument "ANY", records it with label "label"
|
||||
*
|
||||
@ -93,6 +130,14 @@ int cli_parse(const int argc, const char *const *args, const struct cli_schema *
|
||||
* "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
|
||||
@ -104,72 +149,85 @@ int cli_parse(const int argc, const char *const *args, const struct cli_schema *
|
||||
* "[prefix<any>]" consumes one argument "prefixANY" if available, records the text matching
|
||||
* ANY with label "prefix"
|
||||
*/
|
||||
if (wordlen == 3 && word[0] == '.' && word[1] == '.' && word[2] == '.') {
|
||||
if (patlen == 3 && pattern[0] == '.' && pattern[1] == '.' && pattern[2] == '.') {
|
||||
cmdpa.varargi = arg;
|
||||
arg = argc;
|
||||
++opt;
|
||||
} else {
|
||||
int optional = 0;
|
||||
int repeating = 0;
|
||||
if (wordlen > 5 && word[0] == '[' && word[wordlen-4] == ']' && word[wordlen-3] == '.' && word[wordlen-2] == '.' && word[wordlen-1] == '.') {
|
||||
if (patlen > 5 && pattern[0] == '[' && pattern[patlen-4] == ']' && pattern[patlen-3] == '.' && pattern[patlen-2] == '.' && pattern[patlen-1] == '.') {
|
||||
optional = repeating = 1;
|
||||
word += 1;
|
||||
wordlen -= 5;
|
||||
pattern += 1;
|
||||
patlen -= 5;
|
||||
}
|
||||
else if (wordlen > 2 && word[0] == '[' && word[wordlen-1] == ']') {
|
||||
else if (patlen > 2 && pattern[0] == '[' && pattern[patlen-1] == ']') {
|
||||
optional = 1;
|
||||
word += 1;
|
||||
wordlen -= 2;
|
||||
pattern += 1;
|
||||
patlen -= 2;
|
||||
}
|
||||
const char *prefix = NULL;
|
||||
unsigned prefixlen = 0;
|
||||
char prefixarglen = 0;
|
||||
unsigned oarg = arg;
|
||||
const char *text = NULL;
|
||||
const char *label = NULL;
|
||||
unsigned labellen = 0;
|
||||
const char *text = NULL;
|
||||
const char *caret = strchr(word, '<');
|
||||
unsigned oarg = arg;
|
||||
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++];
|
||||
const char *word;
|
||||
unsigned wordlen = 0;
|
||||
char simple = 0;
|
||||
unsigned alt = 0;
|
||||
for (word = pattern; word < &pattern[patlen]; word += wordlen + 1, ++alt) {
|
||||
// Skip over empty "||word" alternative (but still count it).
|
||||
if (*word == '|') {
|
||||
wordlen = 0;
|
||||
continue;
|
||||
}
|
||||
// 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 {
|
||||
label = &word[1];
|
||||
labellen = wordlen - 2;
|
||||
if (arg < argc)
|
||||
text = args[arg++];
|
||||
}
|
||||
} else if (wordlen > 1 && word[0] == '\\') {
|
||||
if (strlen(args[arg]) == wordlen - 1 && strncmp(args[arg], &word[1], wordlen - 1) == 0) {
|
||||
label = &word[1];
|
||||
labellen = wordlen - 1;
|
||||
text = args[arg++];
|
||||
}
|
||||
} else if (arg < argc && strlen(args[arg]) == wordlen && strncmp(args[arg], word, wordlen) == 0) {
|
||||
if (optional) {
|
||||
} else if (arg < argc && strlen(args[arg]) == wordlen && strncmp(args[arg], word, wordlen) == 0) {
|
||||
simple = 1;
|
||||
text = args[arg];
|
||||
label = word;
|
||||
labellen = wordlen;
|
||||
++arg;
|
||||
}
|
||||
++arg;
|
||||
}
|
||||
assert(alt > 0);
|
||||
if (arg == oarg && !optional)
|
||||
break;
|
||||
if (labellen && text) {
|
||||
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, word);
|
||||
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;
|
||||
@ -181,37 +239,33 @@ int cli_parse(const int argc, const char *const *args, const struct cli_schema *
|
||||
}
|
||||
}
|
||||
//DEBUGF("cmd=%d opt=%d args[arg=%d]='%s'", cmd, opt, arg, arg < argc ? args[arg] : "");
|
||||
if (!word && arg == argc) {
|
||||
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. */
|
||||
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) {
|
||||
WHY("Ambiguous command line call:");
|
||||
WHY_argv(" ", argc, args);
|
||||
WHY("Matches the following known command line calls:");
|
||||
WHY_argv(" ", argc, commands[matched_cmd].words);
|
||||
NOWHENCE(WHY_argv("Ambiguous command:", argc, args));
|
||||
NOWHENCE(HINT("Matches the following:"));
|
||||
NOWHENCE(HINT_argv(" ", argc, commands[matched_cmd].words));
|
||||
}
|
||||
if (ambiguous)
|
||||
WHY_argv(" ", argc, commands[cmd].words);
|
||||
NOWHENCE(HINT_argv(" ", argc, commands[cmd].words));
|
||||
matched_cmd = cmd;
|
||||
*parsed = cmdpa;
|
||||
}
|
||||
}
|
||||
/* Don't process ambiguous calls */
|
||||
if (ambiguous)
|
||||
return -1;
|
||||
return 2;
|
||||
/* Complain if we found no matching calls */
|
||||
if (matched_cmd < 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;
|
||||
if (argc)
|
||||
NOWHENCE(WHY_argv("Unknown command:", argc, args));
|
||||
return 1;
|
||||
}
|
||||
return matched_cmd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _debug_cli_parsed(struct __sourceloc __whence, const struct cli_parsed *parsed)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
Serval Distributed Numbering Architecture (DNA)
|
||||
Copyright (C) 2010-2012 Paul Gardner-Stephen
|
||||
Serval DNA command-line functions
|
||||
Copyright (C) 2010-2013 Serval Project, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@ -224,8 +224,9 @@ int parseCommandLine(const char *argv0, int argc, const char *const *args)
|
||||
|
||||
struct cli_parsed parsed;
|
||||
int result = cli_parse(argc, args, command_line_options, &parsed);
|
||||
if (result != -1) {
|
||||
// Do not run the command if the configuration does not load ok
|
||||
switch (result) {
|
||||
case 0:
|
||||
// Do not run the command if the configuration does not load ok.
|
||||
if (((parsed.commands[parsed.cmdi].flags & CLIFLAG_PERMISSIVE_CONFIG) ? cf_reload_permissive() : cf_reload()) != -1)
|
||||
result = cli_invoke(&parsed, NULL);
|
||||
else {
|
||||
@ -233,9 +234,17 @@ int parseCommandLine(const char *argv0, int argc, const char *const *args)
|
||||
strbuf_append_argv(b, argc, args);
|
||||
result = WHYF("configuration defective, not running command: %s", strbuf_str(b));
|
||||
}
|
||||
} else {
|
||||
// Load configuration so that "unsupported command" log message can get out
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
// Load configuration so that log messages can get out.
|
||||
cf_reload_permissive();
|
||||
NOWHENCE(HINTF("Run \"%s help\" for more information.", argv0 ? argv0 : "servald"));
|
||||
break;
|
||||
default:
|
||||
// Load configuration so that log error messages can get out.
|
||||
cf_reload_permissive();
|
||||
break;
|
||||
}
|
||||
|
||||
/* clean up after ourselves */
|
||||
@ -1899,7 +1908,7 @@ int app_id_self(const struct cli_parsed *parsed, void *context)
|
||||
int count=0;
|
||||
|
||||
a.packetTypeAndFlags=MDP_GETADDRS;
|
||||
const char *arg = parsed->argc >= 2 ? parsed->args[1] : "";
|
||||
const char *arg = parsed->labelc ? parsed->labelv[0].text : "";
|
||||
if (!strcasecmp(arg,"self"))
|
||||
a.addrlist.mode = MDP_ADDRLIST_MODE_SELF; /* get own identities */
|
||||
else if (!strcasecmp(arg,"allpeers"))
|
||||
@ -2359,14 +2368,8 @@ struct cli_schema command_line_options[]={
|
||||
"Display command usage."},
|
||||
{app_echo,{"echo","[-e]","[--]","...",NULL},CLIFLAG_PERMISSIVE_CONFIG,
|
||||
"Output the supplied string."},
|
||||
{app_log,{"log","debug","<message>",NULL},CLIFLAG_PERMISSIVE_CONFIG,
|
||||
"Log the supplied message at DEBUG level."},
|
||||
{app_log,{"log","info","<message>",NULL},CLIFLAG_PERMISSIVE_CONFIG,
|
||||
"Log the supplied message at INFO level."},
|
||||
{app_log,{"log","warn","<message>",NULL},CLIFLAG_PERMISSIVE_CONFIG,
|
||||
"Log the supplied message at WARN level."},
|
||||
{app_log,{"log","error","<message>",NULL},CLIFLAG_PERMISSIVE_CONFIG,
|
||||
"Log the supplied message at ERROR level."},
|
||||
{app_log,{"log","error|warn|hint|info|debug","<message>",NULL},CLIFLAG_PERMISSIVE_CONFIG,
|
||||
"Log the supplied message at given level."},
|
||||
{app_server_start,{"start",NULL}, 0,
|
||||
"Start Serval Mesh node process with instance path taken from SERVALINSTANCE_PATH environment variable."},
|
||||
{app_server_start,{"start","in","<instance path>",NULL}, 0,
|
||||
@ -2426,17 +2429,9 @@ struct cli_schema command_line_options[]={
|
||||
{app_rhizome_extract,{"rhizome","extract","file" KEYRING_PIN_OPTIONS,
|
||||
"<manifestid>","[<filepath>]","[<bsk>]",NULL}, 0,
|
||||
"Extract and decrypt a file from Rhizome and write it to the given path"},
|
||||
{app_rhizome_delete,{"rhizome","delete","\\manifest",
|
||||
"<manifestid>",NULL}, 0,
|
||||
"Remove the manifest for the given bundle from the Rhizome store"},
|
||||
{app_rhizome_delete,{"rhizome","delete","\\payload",
|
||||
"<manifestid>",NULL}, 0,
|
||||
"Remove the payload for the given bundle from the Rhizome store"},
|
||||
{app_rhizome_delete,{"rhizome","delete","\\bundle",
|
||||
"<manifestid>",NULL}, 0,
|
||||
"Remove the manifest and payload for the given bundle from the Rhizome store"},
|
||||
{app_rhizome_delete,{"rhizome","delete","\\file",
|
||||
"<fileid>",NULL}, 0,
|
||||
{app_rhizome_delete,{"rhizome","delete","manifest|payload|bundle","<manifestid>",NULL}, 0,
|
||||
"Remove the manifest for the given manifest, payload or bundle from the Rhizome store"},
|
||||
{app_rhizome_delete,{"rhizome","delete","file|","<fileid>",NULL}, 0,
|
||||
"Remove the file with the given hash from the Rhizome store"},
|
||||
{app_rhizome_direct_sync,{"rhizome","direct","sync","[<url>]",NULL}, 0,
|
||||
"Synchronise with the specified Rhizome Direct server. Return when done."},
|
||||
@ -2454,12 +2449,8 @@ struct cli_schema command_line_options[]={
|
||||
"Create a new identity in the keyring protected by the provided PIN"},
|
||||
{app_keyring_set_did,{"keyring", "set","did" KEYRING_PIN_OPTIONS,"<sid>","<did>","<name>",NULL}, 0,
|
||||
"Set the DID for the specified SID. Optionally supply PIN to unlock the SID record in the keyring."},
|
||||
{app_id_self,{"id","self",NULL}, 0,
|
||||
"Return my own identity(s) as URIs"},
|
||||
{app_id_self,{"id","peers",NULL}, 0,
|
||||
"Return identity of known routable peers as URIs"},
|
||||
{app_id_self,{"id","allpeers",NULL}, 0,
|
||||
"Return identity of all known peers as URIs"},
|
||||
{app_id_self,{"id","self|peers|allpeers",NULL}, 0,
|
||||
"Return identity(s) as URIs of own node, or of known routable peers, or all known peers"},
|
||||
{app_route_print, {"route","print",NULL}, 0,
|
||||
"Print the routing table"},
|
||||
{app_network_scan, {"scan","[<address>]",NULL}, 0,
|
||||
|
@ -565,8 +565,7 @@ int monitor_process_command(struct monitor_context *c)
|
||||
int argc = parse_argv(c->line, ' ', argv, 16);
|
||||
|
||||
struct cli_parsed parsed;
|
||||
int res = cli_parse(argc, (const char *const*)argv, monitor_commands, &parsed);
|
||||
if (res == -1 || cli_invoke(&parsed, c))
|
||||
if (cli_parse(argc, (const char *const*)argv, monitor_commands, &parsed) || cli_invoke(&parsed, c))
|
||||
return monitor_write_error(c, "Invalid command");
|
||||
return 0;
|
||||
}
|
||||
|
@ -266,12 +266,22 @@ static void console_command(char *line){
|
||||
int argc = parse_argv(line, ' ', argv, 16);
|
||||
|
||||
struct cli_parsed parsed;
|
||||
int ret = cli_parse(argc, (const char *const*)argv, console_commands, &parsed);
|
||||
if (ret == -1) {
|
||||
switch (cli_parse(argc, (const char *const*)argv, console_commands, &parsed)) {
|
||||
case 0:
|
||||
cli_invoke(&parsed, NULL);
|
||||
break;
|
||||
case 1:
|
||||
printf("Unknown command, try help\n");
|
||||
fflush(stdout);
|
||||
} else {
|
||||
cli_invoke(&parsed, NULL);
|
||||
break;
|
||||
case 2:
|
||||
printf("Ambiguous command, try help\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
default:
|
||||
printf("Error\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user