diff options
Diffstat (limited to 'lib/args.c')
-rw-r--r-- | lib/args.c | 221 |
1 files changed, 123 insertions, 98 deletions
@@ -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; |