aboutsummaryrefslogtreecommitdiff
path: root/lib/args.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/args.c')
-rw-r--r--lib/args.c633
1 files changed, 312 insertions, 321 deletions
diff --git a/lib/args.c b/lib/args.c
index 2f6132ab..b7119651 100644
--- a/lib/args.c
+++ b/lib/args.c
@@ -1,5 +1,4 @@
-/* vi: set sw=4 ts=4 :
- * args.c - Command line argument parsing.
+/* args.c - Command line argument parsing.
*
* Copyright 2006 Rob Landley <rob@landley.net>
*/
@@ -34,6 +33,7 @@
// ~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. TODO
+//
// at the beginning:
// ^ stop at first nonoption argument
// <0 die if less than # leftover arguments (default 0)
@@ -48,19 +48,19 @@
// - is a synonym for stdin in file arguments
// -abc means -a -b -c
-/* This uses a getopt-like option string, but not getopt() itself. We call
+/* This uses a getopt-like option string, but not getopt() itself. We call
* it the get_opt string.
*
* 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.
+ * 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
* 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 objects should be freed by main() when command_main() returns.
+ * space. List objects should be freed by main() when command_main() returns.
*
* Example:
* Calling get_optflags() when toys.which->options="ab:c:d" and
@@ -79,343 +79,334 @@
// Linked list of all known options (get_opt string is parsed into this).
struct opts {
- struct opts *next;
- long *arg; // Pointer into union "this" to store arguments at.
- uint32_t edx[3]; // Flag mask to enable/disable/exclude.
- int c; // Short argument character
- int flags; // |=1, ^=2
- char type; // Type of arguments to store
- union {
- long l;
- FLOAT f;
- } val[3]; // low, high, default - range of allowed values
+ struct opts *next;
+ long *arg; // Pointer into union "this" to store arguments at.
+ uint32_t edx[3]; // Flag mask to enable/disable/exclude.
+ int c; // Short argument character
+ int flags; // |=1, ^=2
+ char type; // Type of arguments to store
+ union {
+ long l;
+ FLOAT f;
+ } val[3]; // low, high, default - range of allowed values
};
struct longopts {
- struct longopts *next;
- struct opts *opt;
- char *str;
- int len;
+ struct longopts *next;
+ struct opts *opt;
+ char *str;
+ int len;
};
// State during argument parsing.
struct getoptflagstate
{
- int argc, minargs, maxargs, nodash;
- char *arg;
- struct opts *opts, *this;
- struct longopts *longopts;
- int noerror, nodash_now, stopearly;
- uint32_t excludes;
+ int argc, minargs, maxargs, nodash;
+ char *arg;
+ struct opts *opts, *this;
+ struct longopts *longopts;
+ int noerror, nodash_now, stopearly;
+ uint32_t excludes;
};
// Parse one command line option.
static int gotflag(struct getoptflagstate *gof)
{
- int type;
- struct opts *opt = gof->this;
-
- // Did we recognize this option?
- if (!opt) {
- if (gof->noerror) return 1;
- error_exit("Unknown option %s", gof->arg);
- }
-
- // Set flags
- toys.optflags |= opt->edx[0];
- toys.optflags &= ~opt->edx[1];
- gof->excludes = opt->edx[2];
- if (opt->flags&2) gof->stopearly=2;
-
- // Does this option take an argument?
- gof->arg++;
- type = opt->type;
- 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]) arg = toys.argv[++gof->argc];
- // TODO: The following line doesn't display --longopt correctly
- if (!arg) error_exit("Missing argument to -%c", opt->c);
-
- 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)
- error_exit("-%c < %ld", opt->c, opt->val[0].l);
- if (l > opt->val[1].l)
- error_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)
- error_exit("-%c < %lf", opt->c, (double)opt->val[0].f);
- if (opt->val[1].l != LONG_MAX && *f > opt->val[1].f)
- error_exit("-%c > %lf", opt->c, (double)opt->val[1].f);
- } else if (type == '@') ++*(opt->arg);
-
- if (!gof->nodash_now) gof->arg = "";
- }
-
- gof->this = NULL;
- return 0;
+ int type;
+ struct opts *opt = gof->this;
+
+ // Did we recognize this option?
+ if (!opt) {
+ if (gof->noerror) return 1;
+ error_exit("Unknown option %s", gof->arg);
+ }
+
+ // Set flags
+ toys.optflags |= opt->edx[0];
+ toys.optflags &= ~opt->edx[1];
+ gof->excludes = opt->edx[2];
+ if (opt->flags&2) gof->stopearly=2;
+
+ // Does this option take an argument?
+ gof->arg++;
+ type = opt->type;
+ 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]) arg = toys.argv[++gof->argc];
+ // TODO: The following line doesn't display --longopt correctly
+ if (!arg) error_exit("Missing argument to -%c", opt->c);
+
+ 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) error_exit("-%c < %ld", opt->c, opt->val[0].l);
+ if (l > opt->val[1].l) error_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)
+ error_exit("-%c < %lf", opt->c, (double)opt->val[0].f);
+ if (opt->val[1].l != LONG_MAX && *f > opt->val[1].f)
+ error_exit("-%c > %lf", opt->c, (double)opt->val[1].f);
+ } else if (type == '@') ++*(opt->arg);
+
+ if (!gof->nodash_now) gof->arg = "";
+ }
+
+ gof->this = NULL;
+ return 0;
}
// Fill out toys.optflags and toys.optargs.
void parse_optflaglist(struct getoptflagstate *gof)
{
- char *options = toys.which->options;
- long *nextarg = (long *)&this;
- struct opts *new = 0;
-
- // Parse option format string
- memset(gof, 0, sizeof(struct getoptflagstate));
- gof->maxargs = INT_MAX;
- if (!options) return;
-
- // Parse leading special behavior indicators
- for (;;) {
- if (*options == '^') gof->stopearly++;
- else if (*options == '<') gof->minargs=*(++options)-'0';
- else if (*options == '>') gof->maxargs=*(++options)-'0';
- else if (*options == '?') gof->noerror++;
- else if (*options == '&') gof->nodash++;
- else break;
- options++;
- }
-
- // Parse the rest of the option string into a linked list
- // of options with attributes.
-
- if (!*options) gof->stopearly++;
- while (*options) {
- char *temp;
- int idx;
-
- // Allocate a new list entry when necessary
- if (!new) {
- new = xzalloc(sizeof(struct opts));
- new->next = gof->opts;
- gof->opts = new;
- new->val[0].l = LONG_MIN;
- new->val[1].l = LONG_MAX;
- ++*(new->edx);
- }
- // Each option must start with "(" or an option character. (Bare
- // longopts only come at the start of the string.)
- if (*options == '(') {
- char *end;
- struct longopts *lo = xmalloc(sizeof(struct longopts));
-
- // Find the end of the longopt
- for (end = ++options; *end && *end != ')'; end++);
- if (CFG_TOYBOX_DEBUG && !*end)
- error_exit("(longopt) didn't end");
-
- // init a new struct longopts
- lo->next = gof->longopts;
- lo->opt = new;
- lo->str = options;
- lo->len = end-options;
- gof->longopts = lo;
- options = end;
-
- // Mark this struct opt as used, even when no short opt.
- if (!new->c) new->c = -1;
-
- // If this is the start of a new option that wasn't a longopt,
-
- } else if (strchr(":*#@.-", *options)) {
- if (CFG_TOYBOX_DEBUG && new->type)
- error_exit("multiple types %c:%c%c", new->c, new->type, *options);
- new->type = *options;
- } else if (-1 != (idx = stridx("+~!", *options))) {
- struct opts *opt;
- int i;
-
- if (!*++options && CFG_TOYBOX_DEBUG)
- error_exit("+~! no target");
- // Find this option flag (in previously parsed struct opt)
- for (i=0, opt = new; ; opt = opt->next) {
- if (CFG_TOYBOX_DEBUG && !opt)
- error_exit("+~! unknown target");
- if (opt->c == *options) break;
- i++;
- }
- new->edx[idx] |= 1<<i;
- } else if (*options == '[') { // TODO
- } else if (-1 != (idx = stridx("|^ ", *options)))
- new->flags |= 1<<idx;
- // bounds checking
- else if (-1 != (idx = stridx("<>=", *options))) {
- if (new->type == '#') {
- long l = strtol(++options, &temp, 10);
- if (temp != options) new->val[idx].l = l;
- } else if (CFG_TOYBOX_FLOAT && new->type == '.') {
- FLOAT f = strtod(++options, &temp);
- if (temp != options) new->val[idx].f = f;
- } else if (CFG_TOYBOX_DEBUG) 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
- // 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) {
- new = NULL;
- continue;
-
- // Claim this option, loop to see what's after it.
- } else new->c = *options;
-
- options++;
- }
-
- // 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. We have to do this after creating the list
- // because we reverse direction: last entry created gets first global slot.)
- int pos = 0;
- for (new = gof->opts; new; new = new->next) {
- int i;
-
- for (i=0;i<3;i++) new->edx[i] <<= pos;
- pos++;
- if (new->type) {
- new->arg = (void *)nextarg;
- *(nextarg++) = new->val[2].l;
- }
- }
+ char *options = toys.which->options;
+ long *nextarg = (long *)&this;
+ struct opts *new = 0;
+
+ // Parse option format string
+ memset(gof, 0, sizeof(struct getoptflagstate));
+ gof->maxargs = INT_MAX;
+ if (!options) return;
+
+ // Parse leading special behavior indicators
+ for (;;) {
+ if (*options == '^') gof->stopearly++;
+ else if (*options == '<') gof->minargs=*(++options)-'0';
+ else if (*options == '>') gof->maxargs=*(++options)-'0';
+ else if (*options == '?') gof->noerror++;
+ else if (*options == '&') gof->nodash++;
+ else break;
+ options++;
+ }
+
+ // Parse the rest of the option string into a linked list
+ // of options with attributes.
+
+ if (!*options) gof->stopearly++;
+ while (*options) {
+ char *temp;
+ int idx;
+
+ // Allocate a new list entry when necessary
+ if (!new) {
+ new = xzalloc(sizeof(struct opts));
+ new->next = gof->opts;
+ gof->opts = new;
+ new->val[0].l = LONG_MIN;
+ new->val[1].l = LONG_MAX;
+ ++*(new->edx);
+ }
+ // Each option must start with "(" or an option character. (Bare
+ // longopts only come at the start of the string.)
+ if (*options == '(') {
+ char *end;
+ struct longopts *lo = xmalloc(sizeof(struct longopts));
+
+ // Find the end of the longopt
+ for (end = ++options; *end && *end != ')'; end++);
+ if (CFG_TOYBOX_DEBUG && !*end) error_exit("(longopt) didn't end");
+
+ // init a new struct longopts
+ lo->next = gof->longopts;
+ lo->opt = new;
+ lo->str = options;
+ lo->len = end-options;
+ gof->longopts = lo;
+ options = end;
+
+ // Mark this struct opt as used, even when no short opt.
+ if (!new->c) new->c = -1;
+
+ // If this is the start of a new option that wasn't a longopt,
+
+ } else if (strchr(":*#@.-", *options)) {
+ if (CFG_TOYBOX_DEBUG && new->type)
+ error_exit("multiple types %c:%c%c", new->c, new->type, *options);
+ new->type = *options;
+ } else if (-1 != (idx = stridx("+~!", *options))) {
+ struct opts *opt;
+ int i;
+
+ if (!*++options && CFG_TOYBOX_DEBUG) error_exit("+~! no target");
+ // Find this option flag (in previously parsed struct opt)
+ for (i=0, opt = new; ; opt = opt->next) {
+ if (CFG_TOYBOX_DEBUG && !opt) error_exit("+~! unknown target");
+ if (opt->c == *options) break;
+ i++;
+ }
+ new->edx[idx] |= 1<<i;
+ } else if (*options == '[') { // TODO
+ } else if (-1 != (idx = stridx("|^ ", *options))) new->flags |= 1<<idx;
+ // bounds checking
+ else if (-1 != (idx = stridx("<>=", *options))) {
+ if (new->type == '#') {
+ long l = strtol(++options, &temp, 10);
+ if (temp != options) new->val[idx].l = l;
+ } else if (CFG_TOYBOX_FLOAT && new->type == '.') {
+ FLOAT f = strtod(++options, &temp);
+ if (temp != options) new->val[idx].f = f;
+ } else if (CFG_TOYBOX_DEBUG) 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
+ // 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) {
+ new = NULL;
+ continue;
+
+ // Claim this option, loop to see what's after it.
+ } else new->c = *options;
+
+ options++;
+ }
+
+ // 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. We have to do this after creating the list
+ // because we reverse direction: last entry created gets first global slot.)
+ int pos = 0;
+ for (new = gof->opts; new; new = new->next) {
+ int i;
+
+ for (i=0;i<3;i++) new->edx[i] <<= pos;
+ pos++;
+ if (new->type) {
+ new->arg = (void *)nextarg;
+ *(nextarg++) = new->val[2].l;
+ }
+ }
}
void get_optflags(void)
{
- struct getoptflagstate gof;
- long saveflags;
- char *letters[]={"s",""};
-
- // Option parsing is a two stage process: parse the option string into
- // a struct opts list, then use that list to process argv[];
-
- if (CFG_HELP) toys.exithelp++;
- // Allocate memory for optargs
- saveflags = 0;
- while (toys.argv[saveflags++]);
- toys.optargs = xzalloc(sizeof(char *)*saveflags);
-
- parse_optflaglist(&gof);
-
- // Iterate through command line arguments, skipping argv[0]
- for (gof.argc=1; toys.argv[gof.argc]; gof.argc++) {
- gof.arg = toys.argv[gof.argc];
- gof.this = NULL;
-
- // Parse this argument
- if (gof.stopearly>1) goto notflag;
-
- gof.nodash_now = 0;
-
- // Various things with dashes
- if (*gof.arg == '-') {
-
- // Handle -
- if (!gof.arg[1]) goto notflag;
- gof.arg++;
- if (*gof.arg=='-') {
- struct longopts *lo;
-
- gof.arg++;
- // Handle --
- if (!*gof.arg) {
- gof.stopearly += 2;
- goto notflag;
- }
- // Handle --longopt
-
- for (lo = gof.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)
- gof.arg += lo->len;
- else continue;
- }
- // It's a match.
- gof.arg = "";
- gof.this = lo->opt;
- break;
- }
- }
-
- // Should we handle this --longopt as a non-option argument?
- if (!lo && gof.noerror) {
- gof.arg-=2;
- goto notflag;
- }
-
- // Long option parsed, handle option.
- gotflag(&gof);
- continue;
- }
-
- // Handle things that don't start with a dash.
- } else {
- if (gof.nodash && (gof.nodash>1 || gof.argc == 1))
- gof.nodash_now = 1;
- else goto notflag;
- }
-
- // 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) {
-
- // Identify next option char.
- for (gof.this = gof.opts; gof.this; gof.this = gof.this->next)
- if (*gof.arg == gof.this->c)
- if (!((gof.this->flags&4) && gof.arg[1])) break;
-
- // Handle option char (advancing past what was used)
- if (gotflag(&gof) ) {
- toys.optflags = saveflags;
- gof.arg = toys.argv[gof.argc];
- goto notflag;
- }
- }
- continue;
-
- // Not a flag, save value in toys.optargs[]
+ struct getoptflagstate gof;
+ long saveflags;
+ char *letters[]={"s",""};
+
+ // Option parsing is a two stage process: parse the option string into
+ // a struct opts list, then use that list to process argv[];
+
+ if (CFG_HELP) toys.exithelp++;
+ // Allocate memory for optargs
+ saveflags = 0;
+ while (toys.argv[saveflags++]);
+ toys.optargs = xzalloc(sizeof(char *)*saveflags);
+
+ parse_optflaglist(&gof);
+
+ // Iterate through command line arguments, skipping argv[0]
+ for (gof.argc=1; toys.argv[gof.argc]; gof.argc++) {
+ gof.arg = toys.argv[gof.argc];
+ gof.this = NULL;
+
+ // Parse this argument
+ if (gof.stopearly>1) goto notflag;
+
+ gof.nodash_now = 0;
+
+ // Various things with dashes
+ if (*gof.arg == '-') {
+
+ // Handle -
+ if (!gof.arg[1]) goto notflag;
+ gof.arg++;
+ if (*gof.arg=='-') {
+ struct longopts *lo;
+
+ gof.arg++;
+ // Handle --
+ if (!*gof.arg) {
+ gof.stopearly += 2;
+ goto notflag;
+ }
+ // Handle --longopt
+
+ for (lo = gof.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) gof.arg += lo->len;
+ else continue;
+ }
+ // It's a match.
+ gof.arg = "";
+ gof.this = lo->opt;
+ break;
+ }
+ }
+
+ // Should we handle this --longopt as a non-option argument?
+ if (!lo && gof.noerror) {
+ gof.arg-=2;
+ goto notflag;
+ }
+
+ // Long option parsed, handle option.
+ gotflag(&gof);
+ continue;
+ }
+
+ // Handle things that don't start with a dash.
+ } else {
+ if (gof.nodash && (gof.nodash>1 || gof.argc == 1)) gof.nodash_now = 1;
+ else goto notflag;
+ }
+
+ // 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) {
+
+ // Identify next option char.
+ for (gof.this = gof.opts; gof.this; gof.this = gof.this->next)
+ if (*gof.arg == gof.this->c)
+ if (!((gof.this->flags&4) && gof.arg[1])) break;
+
+ // Handle option char (advancing past what was used)
+ if (gotflag(&gof) ) {
+ toys.optflags = saveflags;
+ gof.arg = toys.argv[gof.argc];
+ goto notflag;
+ }
+ }
+ continue;
+
+ // Not a flag, save value in toys.optargs[]
notflag:
- if (gof.stopearly) gof.stopearly++;
- toys.optargs[toys.optc++] = toys.argv[gof.argc];
- }
-
- // Sanity check
- if (toys.optc<gof.minargs) {
- error_exit("Need%s %d argument%s", letters[!!(gof.minargs-1)],
- gof.minargs, letters[!(gof.minargs-1)]);
- }
- if (toys.optc>gof.maxargs)
- error_exit("Max %d argument%s", gof.maxargs, letters[!(gof.maxargs-1)]);
- if (CFG_HELP) toys.exithelp = 0;
-
- if (CFG_TOYBOX_FREE) {
- llist_traverse(gof.opts, free);
- llist_traverse(gof.longopts, free);
- }
+ if (gof.stopearly) gof.stopearly++;
+ toys.optargs[toys.optc++] = toys.argv[gof.argc];
+ }
+
+ // Sanity check
+ if (toys.optc<gof.minargs)
+ error_exit("Need%s %d argument%s", letters[!!(gof.minargs-1)],
+ gof.minargs, letters[!(gof.minargs-1)]);
+ if (toys.optc>gof.maxargs)
+ error_exit("Max %d argument%s", gof.maxargs, letters[!(gof.maxargs-1)]);
+ if (CFG_HELP) toys.exithelp = 0;
+
+ if (CFG_TOYBOX_FREE) {
+ llist_traverse(gof.opts, free);
+ llist_traverse(gof.longopts, free);
+ }
}