comparison lib/args.c @ 705:3e81cd0bad4b

Teach option parsing about [groups] of related options.
author Rob Landley <rob@landley.net>
date Mon, 19 Nov 2012 01:49:53 -0600
parents 786841fdb1e0
children 6164dcc7384d
comparison
equal deleted inserted replaced
704:e45ab88c477d 705:3e81cd0bad4b
26 // | this is required. If more than one marked, only one required. TODO 26 // | this is required. If more than one marked, only one required. TODO
27 // ^ Stop parsing after encountering this argument 27 // ^ Stop parsing after encountering this argument
28 // " " (space char) the "plus an argument" must be separate 28 // " " (space char) the "plus an argument" must be separate
29 // I.E. "-j 3" not "-j3". So "kill -stop" != "kill -s top" 29 // I.E. "-j 3" not "-j3". So "kill -stop" != "kill -s top"
30 // 30 //
31 // These modify other option letters (previously seen in string):
32 // +X enabling this enables X (switch on)
33 // ~X enabling this disables X (switch off)
34 // !X die with error if X already set (x!x die if x supplied twice)
35 // [yz] needs at least one of y or z. TODO
36 //
37 // at the beginning: 31 // at the beginning:
38 // ^ stop at first nonoption argument 32 // ^ stop at first nonoption argument
39 // <0 die if less than # leftover arguments (default 0) 33 // <0 die if less than # leftover arguments (default 0)
40 // >9 die if > # leftover arguments (default MAX_INT) 34 // >9 die if > # leftover arguments (default MAX_INT)
41 // ? Allow unknown arguments (pass them through to command). 35 // ? Allow unknown arguments (pass them through to command).
42 // & first argument has imaginary dash (ala tar/ps) 36 // & first argument has imaginary dash (ala tar/ps)
43 // If given twice, all arguments have imaginary dash 37 // If given twice, all arguments have imaginary dash
38 //
39 // At the end: [groups] of previously seen options
40 // - Only one in group (switch off) [-abc] means -ab=-b, -ba=-a, -abc=-c
41 // | Synonyms (switch on all) [|abc] means -ab=-abc, -c=-abc
42 // ! More than one in group is error [!abc] means -ab calls error_exit()
43 // + First in group switches rest on [+abc] means -a=-abc, -b=-b, -c=-c
44 // primarily useful if you can switch things back off again.
45 //
44 46
45 // Notes from getopt man page 47 // Notes from getopt man page
46 // - and -- cannot be arguments. 48 // - and -- cannot be arguments.
47 // -- force end of arguments 49 // -- force end of arguments
48 // - is a synonym for stdin in file arguments 50 // - is a synonym for stdin in file arguments
75 * Changes to union this: 77 * Changes to union this:
76 * this[0]=NULL (because -c didn't get an argument this time) 78 * this[0]=NULL (because -c didn't get an argument this time)
77 * this[1]="fruit" (argument to -b) 79 * this[1]="fruit" (argument to -b)
78 */ 80 */
79 81
80 // Linked list of all known options (get_opt string is parsed into this). 82 // Linked list of all known options (option string parsed into this).
81 struct opts { 83 struct opts {
82 struct opts *next; 84 struct opts *next;
83 long *arg; // Pointer into union "this" to store arguments at. 85 long *arg; // Pointer into union "this" to store arguments at.
84 uint32_t edx[3]; // Flag mask to enable/disable/exclude. 86 int c; // Argument character to match
85 int c; // Short argument character
86 int flags; // |=1, ^=2 87 int flags; // |=1, ^=2
87 char type; // Type of arguments to store 88 unsigned dex[3]; // which bits to disable/enable/exclude in toys.optflags
89 char type; // Type of arguments to store union "this"
88 union { 90 union {
89 long l; 91 long l;
90 FLOAT f; 92 FLOAT f;
91 } val[3]; // low, high, default - range of allowed values 93 } val[3]; // low, high, default - range of allowed values
92 }; 94 };
101 // State during argument parsing. 103 // State during argument parsing.
102 struct getoptflagstate 104 struct getoptflagstate
103 { 105 {
104 int argc, minargs, maxargs, nodash; 106 int argc, minargs, maxargs, nodash;
105 char *arg; 107 char *arg;
106 struct opts *opts, *this; 108 struct opts *opts;
107 struct longopts *longopts; 109 struct longopts *longopts;
108 int noerror, nodash_now, stopearly; 110 int noerror, nodash_now, stopearly;
109 uint32_t excludes; 111 unsigned excludes;
110 }; 112 };
111 113
112 // Parse one command line option. 114 // Use getoptflagstate to parse parse one command line option from argv
113 static int gotflag(struct getoptflagstate *gof) 115 static int gotflag(struct getoptflagstate *gof, struct opts *opt)
114 { 116 {
115 int type; 117 int type;
116 struct opts *opt = gof->this;
117 118
118 // Did we recognize this option? 119 // Did we recognize this option?
119 if (!opt) { 120 if (!opt) {
120 if (gof->noerror) return 1; 121 if (gof->noerror) return 1;
121 error_exit("Unknown option %s", gof->arg); 122 error_exit("Unknown option %s", gof->arg);
122 } 123 }
123 124
124 // Set flags 125 // Set flags
125 toys.optflags |= opt->edx[0]; 126 toys.optflags &= ~opt->dex[0];
126 toys.optflags &= ~opt->edx[1]; 127 toys.optflags |= opt->dex[1];
127 gof->excludes = opt->edx[2]; 128 gof->excludes |= opt->dex[2];
128 if (opt->flags&2) gof->stopearly=2; 129 if (opt->flags&2) gof->stopearly=2;
130
131 if (toys.optflags && gof->excludes) {
132 struct opts *bad;
133 unsigned i = 1;
134
135 for (bad=gof->opts; gof->excludes && i; bad = bad->next) i<<=1;
136 error_exit("No '%c' with '%c'", opt->c, bad->c);
137 }
129 138
130 // Does this option take an argument? 139 // Does this option take an argument?
131 gof->arg++; 140 gof->arg++;
132 type = opt->type; 141 type = opt->type;
133 if (type) { 142 if (type) {
167 } else if (type == '@') ++*(opt->arg); 176 } else if (type == '@') ++*(opt->arg);
168 177
169 if (!gof->nodash_now) gof->arg = ""; 178 if (!gof->nodash_now) gof->arg = "";
170 } 179 }
171 180
172 gof->this = NULL;
173 return 0; 181 return 0;
174 } 182 }
175 183
176 // Fill out toys.optflags and toys.optargs. 184 // Parse this command's options string into struct getoptflagstate, which
177 185 // includes a struct opts linked list in reverse order (I.E. right-to-left)
178 void parse_optflaglist(struct getoptflagstate *gof) 186 void parse_optflaglist(struct getoptflagstate *gof)
179 { 187 {
180 char *options = toys.which->options; 188 char *options = toys.which->options;
181 long *nextarg = (long *)&this; 189 long *nextarg = (long *)&this;
182 struct opts *new = 0; 190 struct opts *new = 0;
191 int idx;
183 192
184 // Parse option format string 193 // Parse option format string
185 memset(gof, 0, sizeof(struct getoptflagstate)); 194 memset(gof, 0, sizeof(struct getoptflagstate));
186 gof->maxargs = INT_MAX; 195 gof->maxargs = INT_MAX;
187 if (!options) return; 196 if (!options) return;
195 else if (*options == '&') gof->nodash++; 204 else if (*options == '&') gof->nodash++;
196 else break; 205 else break;
197 options++; 206 options++;
198 } 207 }
199 208
200 // Parse the rest of the option string into a linked list 209 // Parse option string into a linked list of options with attributes.
201 // of options with attributes.
202 210
203 if (!*options) gof->stopearly++; 211 if (!*options) gof->stopearly++;
204 while (*options) { 212 while (*options) {
205 char *temp; 213 char *temp;
206 int idx; 214
215 // Option groups come after all options are defined
216 if (*options == '[') break;
207 217
208 // Allocate a new list entry when necessary 218 // Allocate a new list entry when necessary
209 if (!new) { 219 if (!new) {
210 new = xzalloc(sizeof(struct opts)); 220 new = xzalloc(sizeof(struct opts));
211 new->next = gof->opts; 221 new->next = gof->opts;
212 gof->opts = new; 222 gof->opts = new;
213 new->val[0].l = LONG_MIN; 223 new->val[0].l = LONG_MIN;
214 new->val[1].l = LONG_MAX; 224 new->val[1].l = LONG_MAX;
215 ++*(new->edx);
216 } 225 }
217 // Each option must start with "(" or an option character. (Bare 226 // Each option must start with "(" or an option character. (Bare
218 // longopts only come at the start of the string.) 227 // longopts only come at the start of the string.)
219 if (*options == '(') { 228 if (*options == '(') {
220 char *end; 229 char *end;
239 248
240 } else if (strchr(":*#@.-", *options)) { 249 } else if (strchr(":*#@.-", *options)) {
241 if (CFG_TOYBOX_DEBUG && new->type) 250 if (CFG_TOYBOX_DEBUG && new->type)
242 error_exit("multiple types %c:%c%c", new->c, new->type, *options); 251 error_exit("multiple types %c:%c%c", new->c, new->type, *options);
243 new->type = *options; 252 new->type = *options;
244 } else if (-1 != (idx = stridx("+~!", *options))) {
245 struct opts *opt;
246 int i;
247
248 if (!*++options && CFG_TOYBOX_DEBUG) error_exit("+~! no target");
249 // Find this option flag (in previously parsed struct opt)
250 for (i=0, opt = new; ; opt = opt->next) {
251 if (CFG_TOYBOX_DEBUG && !opt) error_exit("+~! unknown target");
252 if (opt->c == *options) break;
253 i++;
254 }
255 new->edx[idx] |= 1<<i;
256 } else if (*options == '[') { // TODO
257 } else if (-1 != (idx = stridx("|^ ", *options))) new->flags |= 1<<idx; 253 } else if (-1 != (idx = stridx("|^ ", *options))) new->flags |= 1<<idx;
258 // bounds checking 254 // bounds checking
259 else if (-1 != (idx = stridx("<>=", *options))) { 255 else if (-1 != (idx = stridx("<>=", *options))) {
260 if (new->type == '#') { 256 if (new->type == '#') {
261 long l = strtol(++options, &temp, 10); 257 long l = strtol(++options, &temp, 10);
280 276
281 options++; 277 options++;
282 } 278 }
283 279
284 // Initialize enable/disable/exclude masks and pointers to store arguments. 280 // Initialize enable/disable/exclude masks and pointers to store arguments.
285 // (We have to calculate all this ahead of time because longopts jump into 281 // (This goes right to left so we need the whole list before we can start.)
286 // the middle of the list. We have to do this after creating the list 282 idx = 0;
287 // because we reverse direction: last entry created gets first global slot.)
288 int pos = 0;
289 for (new = gof->opts; new; new = new->next) { 283 for (new = gof->opts; new; new = new->next) {
290 int i; 284 new->dex[1] = 1<<idx++;
291
292 for (i=0;i<3;i++) new->edx[i] <<= pos;
293 pos++;
294 if (new->type) { 285 if (new->type) {
295 new->arg = (void *)nextarg; 286 new->arg = (void *)nextarg;
296 *(nextarg++) = new->val[2].l; 287 *(nextarg++) = new->val[2].l;
297 } 288 }
298 } 289 }
290
291 // Parse trailing group indicators
292 while (*options) {
293 unsigned bits = 0;
294
295 if (CFG_TOYBOX_DEBUG && *options) error_exit("trailing %s", options);
296
297 idx = stridx("-|!+", *++options);
298 if (CFG_TOYBOX_DEBUG && idx == -1) error_exit("[ needs +-!");
299
300 // Don't advance past ] but do process it once in loop.
301 while (*(options++) != ']') {
302 struct opts *opt, *opt2 = 0;
303 int i;
304
305 if (CFG_TOYBOX_DEBUG && !*options) error_exit("[ without ]");
306 // Find this option flag (in previously parsed struct opt)
307 for (i=0, opt = gof->opts; ; i++, opt = opt->next) {
308 if (*options == ']') {
309 if (!opt) break;
310 if (idx == 3) {
311 opt2->dex[1] |= bits;
312 break;
313 }
314 if (bits&(1<<i)) opt->dex[idx] |= bits&~(1<<i);
315 } else {
316 if (CFG_TOYBOX_DEBUG && !opt)
317 error_exit("[] unknown target %c", *options);
318 if (opt->c == *options) {
319 bits |= 1<<i;
320 if (!opt2) opt2=opt;
321 break;
322 }
323 }
324 }
325 }
326 }
299 } 327 }
328
329 // Fill out toys.optflags, toys.optargs, and this[] from toys.argv
300 330
301 void get_optflags(void) 331 void get_optflags(void)
302 { 332 {
303 struct getoptflagstate gof; 333 struct getoptflagstate gof;
334 struct opts *catch;
304 long saveflags; 335 long saveflags;
305 char *letters[]={"s",""}; 336 char *letters[]={"s",""};
306 337
307 // Option parsing is a two stage process: parse the option string into 338 // Option parsing is a two stage process: parse the option string into
308 // a struct opts list, then use that list to process argv[]; 339 // a struct opts list, then use that list to process argv[];
316 parse_optflaglist(&gof); 347 parse_optflaglist(&gof);
317 348
318 // Iterate through command line arguments, skipping argv[0] 349 // Iterate through command line arguments, skipping argv[0]
319 for (gof.argc=1; toys.argv[gof.argc]; gof.argc++) { 350 for (gof.argc=1; toys.argv[gof.argc]; gof.argc++) {
320 gof.arg = toys.argv[gof.argc]; 351 gof.arg = toys.argv[gof.argc];
321 gof.this = NULL; 352 catch = NULL;
322 353
323 // Parse this argument 354 // Parse this argument
324 if (gof.stopearly>1) goto notflag; 355 if (gof.stopearly>1) goto notflag;
325 356
326 gof.nodash_now = 0; 357 gof.nodash_now = 0;
348 if (gof.arg[lo->len]=='=' && lo->opt->type) gof.arg += lo->len; 379 if (gof.arg[lo->len]=='=' && lo->opt->type) gof.arg += lo->len;
349 else continue; 380 else continue;
350 } 381 }
351 // It's a match. 382 // It's a match.
352 gof.arg = ""; 383 gof.arg = "";
353 gof.this = lo->opt; 384 catch = lo->opt;
354 break; 385 break;
355 } 386 }
356 } 387 }
357 388
358 // Should we handle this --longopt as a non-option argument? 389 // Should we handle this --longopt as a non-option argument?
360 gof.arg-=2; 391 gof.arg-=2;
361 goto notflag; 392 goto notflag;
362 } 393 }
363 394
364 // Long option parsed, handle option. 395 // Long option parsed, handle option.
365 gotflag(&gof); 396 gotflag(&gof, catch);
366 continue; 397 continue;
367 } 398 }
368 399
369 // Handle things that don't start with a dash. 400 // Handle things that don't start with a dash.
370 } else { 401 } else {
376 // each entry (could be -abc meaning -a -b -c) 407 // each entry (could be -abc meaning -a -b -c)
377 saveflags = toys.optflags; 408 saveflags = toys.optflags;
378 while (*gof.arg) { 409 while (*gof.arg) {
379 410
380 // Identify next option char. 411 // Identify next option char.
381 for (gof.this = gof.opts; gof.this; gof.this = gof.this->next) 412 for (catch = gof.opts; catch; catch = catch->next)
382 if (*gof.arg == gof.this->c) 413 if (*gof.arg == catch->c)
383 if (!((gof.this->flags&4) && gof.arg[1])) break; 414 if (!((catch->flags&4) && gof.arg[1])) break;
384 415
385 // Handle option char (advancing past what was used) 416 // Handle option char (advancing past what was used)
386 if (gotflag(&gof) ) { 417 if (gotflag(&gof, catch) ) {
387 toys.optflags = saveflags; 418 toys.optflags = saveflags;
388 gof.arg = toys.argv[gof.argc]; 419 gof.arg = toys.argv[gof.argc];
389 goto notflag; 420 goto notflag;
390 } 421 }
391 } 422 }