From 61190a3637a73c73eb6d936f446b2a14f3c9562c Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Mon, 18 Feb 2008 03:32:17 -0600 Subject: Add enable/disable/exclude logic, update docs. --- lib/args.c | 199 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 102 insertions(+), 97 deletions(-) (limited to 'lib/args.c') diff --git a/lib/args.c b/lib/args.c index 2391daa3..057097aa 100644 --- a/lib/args.c +++ b/lib/args.c @@ -16,13 +16,14 @@ // * plus a string argument, appended to a list // # plus a signed long argument (TODO: Bounds checking?) // @ plus an occurrence counter (which is a long) -// | this is required. If more than one marked, only one required. // (longopt) -// +X enabling this enables X (switch on) -// ~X enabling this disables X (switch off) -// x~x means toggle x, I.E. specifying it again switches it off. -// !X die with error if X already set (x!x die if x supplied twice) -// [yz] needs at least one of y or z. +// | this is required. If more than one marked, only one required. +// +// These modify other option letters (previously seen in string): +// +X enabling this enables X (switch on) +// ~X enabling this disables X (switch off) +// !X die with error if X already set (x!x die if x supplied twice) +// [yz] needs at least one of y or z. // at the beginning: // + stop at first nonoption argument // <0 at least # leftover arguments needed (default 0) @@ -37,88 +38,108 @@ // - is a synonym for stdin in file arguments // -abc means -a -b -c -/* This uses a getopt-like option string, but not getopt() itself. +/* This uses a getopt-like option string, but not getopt() itself. We call + * it the get_opt string. * - * Each option in options corresponds to a bit position in the return - * value (last argument is (1<<0), the next to last is (1<<1) and so on). - * If the option isn't seen in argv[] its bit is 0. + * Each option in the get_opt string corresponds to a bit position in the + * return value. The rightmost argument is (1<<0), the next to last is (1<<1) + * and so on. If the option isn't seen in argv[], its bit remains 0. * * Options which have an argument fill in the corresponding slot in the global - * toys.command struct, which it treats as an array of longs (note that - * sizeof(long)==sizeof(pointer) is guaranteed by LP64). + * union "this" (see generated/globals.h), which it treats as an array of longs + * (note that sizeof(long)==sizeof(pointer) is guaranteed by LP64). * * You don't have to free the option strings, which point into the environment - * space. List list objects should be freed by main() when command_main() - * returns. + * space. List objects should be freed by main() when command_main() returns. * * Example: - * get_optflags() when toys.which->options="ab:c:d" - * argv = ["command", "-b", "fruit", "-d"] - * flags = 5, toy[0]=NULL, toy[1]="fruit"; + * Calling get_optflags() when toys.which->options="ab:c:d" and + * argv = ["command", "-b", "fruit", "-d", "walrus"] results in: + * + * Changes to struct toys: + * toys.optflags = 5 (-b=4 | -d=1) + * toys.optargs[0]="walrus" (leftover argument) + * toys.optargs[1]=NULL (end of list) + * toys.optc=1 (there was 1 leftover argument) + * + * Changes to union this: + * this[0]=NULL (because -c didn't get an argument this time) + * this[1]="fruit" (argument to -b) */ -// +// Linked list of all known options (get_opt string is parsed into this). struct opts { struct opts *next; - char c; - int type; - int shift; - long *arg; + uint32_t edx[3]; // Flag mask to enable/disable/exclude. + long *arg; // Pointer into union this to store arguments at. + int c; // Short argument character + char type; // Type of arguments to store }; -static struct getoptflagstate +// State during argument parsing. +struct getoptflagstate { int argc; char *arg; struct opts *opts, *this; int noerror, nodash_now; -} gof; + uint32_t excludes; +}; -static void gotflag(void) +// Parse one command line option. + +static void gotflag(struct getoptflagstate *gof) { int type; + struct opts *opt = gof->this; // Did we recognize this option? - if (!gof.this && !gof.noerror) error_exit("Unknown option %s", gof.arg); - else toys.optflags |= 1 << gof.this->shift; + if (!opt) { + if (gof->noerror) return; + error_exit("Unknown option %s", gof->arg); + } + toys.optflags |= opt->edx[0]; + toys.optflags &= ~opt->edx[1]; + gof->excludes = opt->edx[2]; // Does this option take an argument? - gof.arg++; - type = gof.this->type & 255; + gof->arg++; + type = opt->type; if (type) { // 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 && !*gof.arg) { - gof.arg = toys.argv[++gof.argc]; - if (!gof.arg) error_exit("Missing argument"); + if (!gof->nodash_now && !gof->arg[0]) { + gof->arg = toys.argv[++gof->argc]; + // TODO: The following line doesn't display --longopt correctly + if (!gof->arg) error_exit("Missing argument to -%c",opt->c); } // Grab argument. - if (!gof.arg && !(gof.arg = toys.argv[++gof.argc])) + if (!gof->arg && !(gof->arg = toys.argv[++(gof->argc)])) error_exit("Missing argument"); - if (type == ':') *(gof.this->arg) = (long)gof.arg; + if (type == ':') *(opt->arg) = (long)gof->arg; else if (type == '*') { struct arg_list *temp, **list; - list = (struct arg_list **)gof.this->arg; + list = (struct arg_list **)opt->arg; temp = xmalloc(sizeof(struct arg_list)); - temp->arg = gof.arg; + temp->arg = gof->arg; temp->next = *list; *list = temp; - } else if (type == '#') *(gof.this->arg) = atolx((char *)gof.arg); + } else if (type == '#') *(opt->arg) = atolx((char *)gof->arg); else if (type == '@') { } - gof.arg = ""; + gof->arg = ""; } - gof.this = NULL; + gof->this = NULL; } -// Fill out toys.optflags and toys.optargs. This isn't reentrant because -// we don't bzero(&gof, sizeof(gof)); +// Fill out toys.optflags and toys.optargs. +static char *plustildenot = "+~!"; void get_optflags(void) { int stopearly = 0, nodash = 0, minargs = 0, maxargs; @@ -128,6 +149,7 @@ void get_optflags(void) char *str; int len; } *longopts = NULL; + struct getoptflagstate gof; long *nextarg = (long *)&this; char *options = toys.which->options; @@ -137,9 +159,11 @@ void get_optflags(void) while (toys.argv[maxargs++]); toys.optargs = xzalloc(sizeof(char *)*maxargs); maxargs = INT_MAX; + bzero(&gof, sizeof(struct getoptflagstate)); // Parse option format if (options) { + // Parse leading special behavior indicators for (;;) { if (*options == '+') stopearly++; @@ -153,14 +177,16 @@ void get_optflags(void) // Parse rest of opts into array while (*options) { + char *temp = "+~!"; // Allocate a new option entry when necessary if (!gof.this) { gof.this = xzalloc(sizeof(struct opts)); gof.this->next = gof.opts; gof.opts = gof.this; + ++*(gof.this->edx); } - // 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 == '(') { char *end; @@ -169,7 +195,7 @@ void get_optflags(void) // Find the end of the longopt for (end = ++options; *end && *end != ')'; end++); if (CFG_TOYBOX_DEBUG && !*end) - error_exit("Unterminated optstring"); + error_exit("Bug1 in get_opt"); // Allocate and init a new struct longopts lo = xmalloc(sizeof(struct longopts)); @@ -180,25 +206,35 @@ void get_optflags(void) longopts = lo; options = end; - // For leading longopts (with no corresponding short opt), note - // that this option struct has been used. - gof.this->shift++; + // Mark this as struct opt as used, even when no short opt. + if (!gof.this->c) gof.this->c = -1; // If this is the start of a new option that wasn't a longopt, - } else if (index(":*#@", *options)) { - gof.this->type |= *options; - } else if (*options == '|') { - } else if (*options == '+') { - } else if (*options == '~') { - } else if (*options == '!') { + } else if (strchr(":*#@", *options)) { + gof.this->type = *options; + } else if (0 != (temp = strchr(plustildenot, *options))) { + int i=0, idx = temp - plustildenot; + struct opts *opt; + + if (CFG_TOYBOX_DEBUG && !*++options) + error_exit("Bug2 in get_opt"); + // Find this option flag (in previously parsed struct opt) + for (opt = gof.this; ; opt = opt->next) { + if (CFG_TOYBOX_DEBUG && !opt) error_exit("Bug3 in get_opt"); + if (opt->c == *options) break; + i++; + } + gof.this->edx[idx] |= 1<shift || gof.this->c) { + // (It'll get back here afterwards and fall through to next else.) + } else if(gof.this->c) { gof.this = NULL; continue; @@ -209,13 +245,16 @@ void get_optflags(void) } } - // Initialize shift bits and pointers to store arguments. (We have to - // calculate this ahead of time because longopts jump into the middle of - // the list.) + // Initialize enable/disable/exclude masks and pointers to store arguments. + // (We have to calculate all this ahead of time because longopts jump into + // the middle of the list.) gof.argc = 0; for (gof.this = gof.opts; gof.this; gof.this = gof.this->next) { - gof.this->shift = gof.argc++; - if (gof.this->type & 255) { + int i; + + for (i=0;i<3;i++) gof.this->edx[i] <<= gof.argc; + gof.argc++; + if (gof.this->type) { gof.this->arg = (void *)nextarg; *(nextarg++) = 0; } @@ -251,11 +290,9 @@ void get_optflags(void) for (lo = longopts; lo; lo = lo->next) { if (!strncmp(gof.arg, lo->str, lo->len)) { if (gof.arg[lo->len]) { - if (gof.arg[lo->len]=='=' - && (lo->opt->type & 255)) - { + if (gof.arg[lo->len]=='=' && lo->opt->type) gof.arg += lo->len; - } else continue; + else continue; } // It's a match. gof.arg = ""; @@ -265,7 +302,7 @@ void get_optflags(void) } // Long option parsed, handle option. - gotflag(); + gotflag(&gof); continue; } @@ -284,7 +321,7 @@ void get_optflags(void) if (*gof.arg == gof.this->c) break; // Handle option char (advancing past what was used) - gotflag(); + gotflag(&gof); } continue; @@ -301,35 +338,3 @@ notflag: error_exit("Max %d argument%s", maxargs, maxargs ? "s" : ""); if (CFG_HELP) toys.exithelp = 0; } - -// Loop through files listed on the command line - -static int dofileargs(char ***files, int fd, int iswrite) -{ - char *filename = *((*files)++); - static int flags[] = {O_RDONLY, O_CREAT|O_TRUNC, O_RDWR}; - - if (fd != -1) close(fd); - - for (;;) { - - // Are there no more files? - if (!*filename) - return (fd == -1) ? iswrite : -1; - - // A filename of "-" means stdin. - if (*filename == '-' && !filename[1]) return 0; - - fd = xcreate(filename, flags[iswrite], 0777); - } -} - -int readfileargs(char ***files, int fd) -{ - return dofileargs(files, fd, 0); -} - -int writefileargs(char ***files, int fd) -{ - return dofileargs(files, fd, 1); -} -- cgit v1.2.3