Mercurial > hg > toybox
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 } |