aboutsummaryrefslogtreecommitdiff
path: root/lib/args.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/args.c')
-rw-r--r--lib/args.c221
1 files changed, 123 insertions, 98 deletions
diff --git a/lib/args.c b/lib/args.c
index 2b8da7c7..53258279 100644
--- a/lib/args.c
+++ b/lib/args.c
@@ -65,19 +65,19 @@
// % time offset in milliseconds with optional s/m/h/d suffix
// (longopt)
// | this is required. If more than one marked, only one required.
-// ; long option's argument is optional (can only be supplied with --opt=)
+// ; Option's argument is optional, and must be collated: -aARG or --a=ARG
// ^ Stop parsing after encountering this argument
// " " (space char) the "plus an argument" must be separate
// I.E. "-j 3" not "-j3". So "kill -stop" != "kill -s top"
//
// At the beginning of the get_opt string (before any options):
-// ^ stop at first nonoption argument
// <0 die if less than # leftover arguments (default 0)
// >9 die if > # leftover arguments (default MAX_INT)
-// ? Allow unknown arguments (pass them through to command).
-// & first arg has imaginary dash (ala tar/ps/ar) which sets FLAGS_NODASH
// 0 Include argv[0] in optargs
-// note: ^ and ? implied when no options
+// ^ stop at first nonoption argument
+// ? Pass unknown arguments through to command (implied when no flags).
+// & first arg has imaginary dash (ala tar/ps/ar) which sets FLAGS_NODASH
+// ~ Collate following bare longopts (as if under short opt, repeatable)
//
// At the end: [groups] of previously seen options
// - Only one in group (switch off) [-abc] means -ab=-b, -ba=-a, -abc=-c
@@ -85,7 +85,7 @@
// ! More than one in group is error [!abc] means -ab calls error_exit()
// primarily useful if you can switch things back off again.
//
-// You may use octal escapes with the high bit (127) set to use a control
+// You may use octal escapes with the high bit (128) set to use a control
// character as an option flag. For example, \300 would be the option -@
// Notes from getopt man page
@@ -131,25 +131,31 @@ struct getoptflagstate
unsigned excludes, requires;
};
+static void forget_arg(struct opts *opt)
+{
+ if (opt->arg) {
+ if (opt->type=='*') llist_traverse((void *)*opt->arg, free);
+ *opt->arg = 0;
+ }
+}
+
// Use getoptflagstate to parse one command line option from argv
-static int gotflag(struct getoptflagstate *gof, struct opts *opt, int shrt)
+// Sets flags, saves/clears opt->arg, advances gof->arg/gof->argc as necessary
+static void gotflag(struct getoptflagstate *gof, struct opts *opt, int longopt)
{
unsigned long long i;
+ struct opts *and;
+ char *arg;
int type;
// Did we recognize this option?
- if (!opt) {
- if (gof->noerror) return 1;
- help_exit("Unknown option '%s'", gof->arg);
- }
+ if (!opt) help_exit("Unknown option '%s'", gof->arg);
// Might enabling this switch off something else?
if (toys.optflags & opt->dex[0]) {
- struct opts *clr;
-
// Forget saved argument for flag we switch back off
- for (clr=gof->opts, i=1; clr; clr = clr->next, i<<=1)
- if (clr->arg && (i & toys.optflags & opt->dex[0])) *clr->arg = 0;
+ for (and = gof->opts, i = 1; and; and = and->next, i<<=1)
+ if (i & toys.optflags & opt->dex[0]) forget_arg(and);
toys.optflags &= ~opt->dex[0];
}
@@ -159,72 +165,63 @@ static int gotflag(struct getoptflagstate *gof, struct opts *opt, int shrt)
if (opt->flags&2) gof->stopearly=2;
if (toys.optflags & gof->excludes) {
- struct opts *bad;
-
- for (bad=gof->opts, i=1; bad ;bad = bad->next, i<<=1) {
- if (opt == bad || !(i & toys.optflags)) continue;
- if (toys.optflags & bad->dex[2]) break;
+ for (and = gof->opts, i = 1; and; and = and->next, i<<=1) {
+ if (opt == and || !(i & toys.optflags)) continue;
+ if (toys.optflags & and->dex[2]) break;
}
- if (bad) help_exit("No '%c' with '%c'", opt->c, bad->c);
+ if (and) help_exit("No '%c' with '%c'", opt->c, and->c);
}
- // Does this option take an argument?
- if (!gof->arg || (shrt && !gof->arg[1])) {
- gof->arg = 0;
- if (opt->flags & 8) return 0;
- gof->arg = "";
- } else gof->arg++;
- type = opt->type;
-
- if (type == '@') ++*(opt->arg);
- else if (type) {
- char *arg = gof->arg;
-
- // Handle "-xblah" and "-x blah", but also a third case: "abxc blah"
- // to make "tar xCjfv blah1 blah2 thingy" work like
- // "tar -x -C blah1 -j -f blah2 -v thingy"
-
- if (gof->nodash_now || (!arg[0] && !(opt->flags & 8)))
- arg = toys.argv[++gof->argc];
- if (!arg) {
- char *s = "Missing argument to ";
- struct longopts *lo;
-
- if (opt->c != -1) help_exit("%s-%c", s, opt->c);
-
- for (lo = gof->longopts; lo->opt != opt; lo = lo->next);
- help_exit("%s--%.*s", s, lo->len, lo->str);
- }
-
- if (type == ':') *(opt->arg) = (long)arg;
- else if (type == '*') {
- struct arg_list **list;
-
- list = (struct arg_list **)opt->arg;
- while (*list) list=&((*list)->next);
- *list = xzalloc(sizeof(struct arg_list));
- (*list)->arg = arg;
- } else if (type == '#' || type == '-') {
- long l = atolx(arg);
- if (type == '-' && !ispunct(*arg)) l*=-1;
- if (l < opt->val[0].l) help_exit("-%c < %ld", opt->c, opt->val[0].l);
- if (l > opt->val[1].l) help_exit("-%c > %ld", opt->c, opt->val[1].l);
-
- *(opt->arg) = l;
- } else if (CFG_TOYBOX_FLOAT && type == '.') {
- FLOAT *f = (FLOAT *)(opt->arg);
-
- *f = strtod(arg, &arg);
- if (opt->val[0].l != LONG_MIN && *f < opt->val[0].f)
- help_exit("-%c < %lf", opt->c, (double)opt->val[0].f);
- if (opt->val[1].l != LONG_MAX && *f > opt->val[1].f)
- help_exit("-%c > %lf", opt->c, (double)opt->val[1].f);
- } else if (type=='%') *(opt->arg) = xparsemillitime(arg);
-
- if (!gof->nodash_now) gof->arg = "";
+ // Are we NOT saving an argument? (Type 0, '@', unattached ';', short ' ')
+ if (*(arg = gof->arg)) gof->arg++;
+ if ((type = opt->type) == '@') {
+ ++*opt->arg;
+ return;
+ }
+ if (!longopt && *gof->arg && (opt->flags & 4)) return forget_arg(opt);
+ if (!type || (!arg[!longopt] && (opt->flags & 8))) return forget_arg(opt);
+
+ // Handle "-xblah" and "-x blah", but also a third case: "abxc blah"
+ // to make "tar xCjfv blah1 blah2 thingy" work like
+ // "tar -x -C blah1 -j -f blah2 -v thingy"
+
+ if (longopt && *arg) arg++;
+ else arg = (gof->nodash_now||!*gof->arg) ? toys.argv[++gof->argc] : gof->arg;
+ if (!gof->nodash_now) gof->arg = "";
+ if (!arg) {
+ struct longopts *lo;
+
+ arg = "Missing argument to ";
+ if (opt->c != -1) help_exit("%s-%c", arg, opt->c);
+ for (lo = gof->longopts; lo->opt != opt; lo = lo->next);
+ help_exit("%s--%.*s", arg, lo->len, lo->str);
}
- return 0;
+ // Parse argument by type
+ if (type == ':') *(opt->arg) = (long)arg;
+ else if (type == '*') {
+ struct arg_list **list;
+
+ list = (struct arg_list **)opt->arg;
+ while (*list) list=&((*list)->next);
+ *list = xzalloc(sizeof(struct arg_list));
+ (*list)->arg = arg;
+ } else if (type == '#' || type == '-') {
+ long l = atolx(arg);
+ if (type == '-' && !ispunct(*arg)) l*=-1;
+ if (l < opt->val[0].l) help_exit("-%c < %ld", opt->c, opt->val[0].l);
+ if (l > opt->val[1].l) help_exit("-%c > %ld", opt->c, opt->val[1].l);
+
+ *(opt->arg) = l;
+ } else if (CFG_TOYBOX_FLOAT && type == '.') {
+ FLOAT *f = (FLOAT *)(opt->arg);
+
+ *f = strtod(arg, &arg);
+ if (opt->val[0].l != LONG_MIN && *f < opt->val[0].f)
+ help_exit("-%c < %lf", opt->c, (double)opt->val[0].f);
+ if (opt->val[1].l != LONG_MAX && *f > opt->val[1].f)
+ help_exit("-%c > %lf", opt->c, (double)opt->val[1].f);
+ } else if (type=='%') *(opt->arg) = xparsemillitime(arg);
}
// Parse this command's options string into struct getoptflagstate, which
@@ -255,7 +252,7 @@ static int parse_optflaglist(struct getoptflagstate *gof)
// Parse option string into a linked list of options with attributes.
- if (!*options) gof->stopearly++, gof->noerror++;
+ if (!*options) gof->noerror++;
while (*options) {
char *temp;
@@ -270,7 +267,7 @@ static int parse_optflaglist(struct getoptflagstate *gof)
new->val[0].l = LONG_MIN;
new->val[1].l = LONG_MAX;
}
- // Each option must start with "(" or an option character. (Bare
+ // Each option must start with "(" or an option character. (Bare
// longopts only come at the start of the string.)
if (*options == '(' && new->c != -1) {
char *end;
@@ -312,8 +309,8 @@ static int parse_optflaglist(struct getoptflagstate *gof)
} else error_exit("<>= only after .#%%");
options = --temp;
- // At this point, we've hit the end of the previous option. The
- // current character is the start of a new option. If we've already
+ // At this point, we've hit the end of the previous option. The
+ // current character is the start of a new option. If we've already
// assigned an option to this struct, loop to allocate a new one.
// (It'll get back here afterwards and fall through to next else.)
} else if (new->c) {
@@ -321,7 +318,7 @@ static int parse_optflaglist(struct getoptflagstate *gof)
continue;
// Claim this option, loop to see what's after it.
- } else new->c = 127&*options;
+ } else new->c = *options;
options++;
}
@@ -332,7 +329,8 @@ static int parse_optflaglist(struct getoptflagstate *gof)
for (new = gof->opts; new; new = new->next) {
unsigned long long u = 1LL<<idx++;
- if (new->c == 1) new->c = 0;
+ if (new->c == 1 || new->c=='~') new->c = 0;
+ else new->c &= 127;
new->dex[1] = u;
if (new->flags & 1) gof->requires |= u;
if (new->type) {
@@ -386,7 +384,7 @@ void get_optflags(void)
struct getoptflagstate gof;
struct opts *catch;
unsigned long long saveflags;
- char *letters[]={"s",""};
+ char *letters[]={"s",""}, *ss;
// Option parsing is a two stage process: parse the option string into
// a struct opts list, then use that list to process argv[];
@@ -419,6 +417,8 @@ void get_optflags(void)
gof.arg++;
if (*gof.arg=='-') {
struct longopts *lo;
+ struct arg_list *al = 0, *al2;
+ int ii;
gof.arg++;
// Handle --
@@ -427,28 +427,51 @@ void get_optflags(void)
continue;
}
- // do we match a known --longopt?
+ // unambiguously match the start of a known --longopt?
check_help(toys.argv+gof.argc);
for (lo = gof.longopts; lo; lo = lo->next) {
- if (!strncmp(gof.arg, lo->str, lo->len)) {
- if (!gof.arg[lo->len]) gof.arg = 0;
- else if (gof.arg[lo->len] == '=' && lo->opt->type)
- gof.arg += lo->len;
- else continue;
- // It's a match.
- catch = lo->opt;
- break;
+ for (ii = 0; ii<lo->len; ii++) if (gof.arg[ii] != lo->str[ii]) break;
+
+ // = only terminates when we can take an argument, not type 0 or '@'
+ if (!gof.arg[ii] || (gof.arg[ii]=='=' && !strchr("@", lo->opt->type)))
+ {
+ al2 = xmalloc(sizeof(struct arg_list));
+ al2->next = al;
+ al2->arg = (void *)lo;
+ al = al2;
+
+ // Exact match is unambigous even when longer options available
+ if (ii==lo->len) {
+ llist_traverse(al, free);
+ al = 0;
+
+ break;
+ }
}
}
+ // How many matches?
+ if (al) {
+ *libbuf = 0;
+ if (al->next) for (ss = libbuf, al2 = al; al2; al2 = al2->next) {
+ lo = (void *)al2->arg;
+ ss += sprintf(ss, " %.*s"+(al2==al), lo->len, lo->str);
+ } else lo = (void *)al->arg;
+ llist_traverse(al, free);
+ if (*libbuf) error_exit("bad --%s (%s)", gof.arg, libbuf);
+ }
+ // One unambiguous match?
+ if (lo) {
+ catch = lo->opt;
+ while (!strchr("=", *gof.arg)) gof.arg++;
// Should we handle this --longopt as a non-option argument?
- if (!lo && gof.noerror) {
+ } else if (gof.noerror) {
gof.arg -= 2;
goto notflag;
}
// Long option parsed, handle option.
- gotflag(&gof, catch, 0);
+ gotflag(&gof, catch, 1);
continue;
}
@@ -458,7 +481,7 @@ void get_optflags(void)
else goto notflag;
}
- // At this point, we have the args part of -args. Loop through
+ // At this point, we have the args part of -args. Loop through
// each entry (could be -abc meaning -a -b -c)
saveflags = toys.optflags;
while (gof.arg && *gof.arg) {
@@ -466,14 +489,16 @@ void get_optflags(void)
// Identify next option char.
for (catch = gof.opts; catch; catch = catch->next)
if (*gof.arg == catch->c)
- if (!((catch->flags&4) && gof.arg[1])) break;
+ if (!gof.arg[1] || (catch->flags&(4|8))!=4) break;
- // Handle option char (advancing past what was used)
- if (gotflag(&gof, catch, 1) ) {
+ if (!catch && gof.noerror) {
toys.optflags = saveflags;
gof.arg = toys.argv[gof.argc];
goto notflag;
}
+
+ // Handle option char (advancing past what was used)
+ gotflag(&gof, catch, 0);
}
continue;