EM-ODP 4.4.0
Event Machine on ODP
Loading...
Searching...
No Matches
optparse.h
1/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
2 *
3 * This is free and unencumbered software released into the public domain.
4 *
5 * To get the implementation, define OPTPARSE_IMPLEMENTATION.
6 * Optionally define OPTPARSE_API to control the API's visibility
7 * and/or linkage (static, __attribute__, __declspec).
8 *
9 * The POSIX getopt() option parser has three fatal flaws. These flaws
10 * are solved by Optparse.
11 *
12 * 1) Parser state is stored entirely in global variables, some of
13 * which are static and inaccessible. This means only one thread can
14 * use getopt(). It also means it's not possible to recursively parse
15 * nested sub-arguments while in the middle of argument parsing.
16 * Optparse fixes this by storing all state on a local struct.
17 *
18 * 2) The POSIX standard provides no way to properly reset the parser.
19 * This means for portable code that getopt() is only good for one
20 * run, over one argv with one option string. It also means subcommand
21 * options cannot be processed with getopt(). Most implementations
22 * provide a method to reset the parser, but it's not portable.
23 * Optparse provides an optparse_arg() function for stepping over
24 * subcommands and continuing parsing of options with another option
25 * string. The Optparse struct itself can be passed around to
26 * subcommand handlers for additional subcommand option parsing. A
27 * full reset can be achieved by with an additional optparse_init().
28 *
29 * 3) Error messages are printed to stderr. This can be disabled with
30 * opterr, but the messages themselves are still inaccessible.
31 * Optparse solves this by writing an error message in its errmsg
32 * field. The downside to Optparse is that this error message will
33 * always be in English rather than the current locale.
34 *
35 * Optparse should be familiar with anyone accustomed to getopt(), and
36 * it could be a nearly drop-in replacement. The option string is the
37 * same and the fields have the same names as the getopt() global
38 * variables (optarg, optind, optopt).
39 *
40 * Optparse also supports GNU-style long options with optparse_long().
41 * The interface is slightly different and simpler than getopt_long().
42 *
43 * By default, argv is permuted as it is parsed, moving non-option
44 * arguments to the end. This can be disabled by setting the `permute`
45 * field to 0 after initialization.
46 */
47
48#ifndef MISC_OPTPARSE_H
49#define MISC_OPTPARSE_H
50
51#ifndef OPTPARSE_API
52# define OPTPARSE_API
53#endif
54
55struct optparse {
56 char **argv;
57 int permute;
58 int optind;
59 int optopt;
60 char *optarg;
61 char errmsg[64];
62 int subopt;
63};
64
65enum optparse_argtype {
66 OPTPARSE_NONE,
67 OPTPARSE_REQUIRED,
68 OPTPARSE_OPTIONAL
69};
70
72 const char *longname;
73 int shortname;
74 enum optparse_argtype argtype;
75};
76
77/**
78 * Initializes the parser state.
79 */
80OPTPARSE_API
81void optparse_init(struct optparse *options, char **argv);
82
83/**
84 * Read the next option in the argv array.
85 * @param optstring a getopt()-formatted option string.
86 * @return the next option character, -1 for done, or '?' for error
87 *
88 * Just like getopt(), a character followed by no colons means no
89 * argument. One colon means the option has a required argument. Two
90 * colons means the option takes an optional argument.
91 */
92OPTPARSE_API
93int optparse(struct optparse *options, const char *optstring);
94
95/**
96 * Handles GNU-style long options in addition to getopt() options.
97 * This works a lot like GNU's getopt_long(). The last option in
98 * longopts must be all zeros, marking the end of the array. The
99 * longindex argument may be NULL.
100 */
101OPTPARSE_API
102int optparse_long(struct optparse *options,
103 const struct optparse_long *longopts,
104 int *longindex);
105
106/**
107 * Used for stepping over non-option arguments.
108 * @return the next non-option argument, or NULL for no more arguments
109 *
110 * Argument parsing can continue with optparse() after using this
111 * function. That would be used to parse the options for the
112 * subcommand returned by optparse_arg(). This function allows you to
113 * ignore the value of optind.
114 */
115OPTPARSE_API
116char *optparse_arg(struct optparse *options);
117
118/* Implementation */
119#ifdef OPTPARSE_IMPLEMENTATION
120
121#define OPTPARSE_MSG_INVALID "invalid option"
122#define OPTPARSE_MSG_MISSING "option requires an argument"
123#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
124#define OPTPARSE_MSG_UNKNOWN "unknown error"
125
126static int
127optparse_error(struct optparse *options, const char *msg, const char *data)
128{
129 unsigned int p = 0;
130 const char *sep = " -- '";
131
132 while (*msg)
133 options->errmsg[p++] = *msg++;
134
135 while (*sep)
136 options->errmsg[p++] = *sep++;
137
138 while (p < sizeof(options->errmsg) - 2 && *data)
139 options->errmsg[p++] = *data++;
140
141 options->errmsg[p++] = '\'';
142 options->errmsg[p++] = '\0';
143 return '?';
144}
145
146OPTPARSE_API
147void
148optparse_init(struct optparse *options, char **argv)
149{
150 options->argv = argv;
151 options->permute = 1;
152 options->optind = argv[0] != 0;
153 options->subopt = 0;
154 options->optarg = 0;
155 options->errmsg[0] = '\0';
156}
157
158static int
159optparse_is_dashdash(const char *arg)
160{
161 return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
162}
163
164static int
165optparse_is_shortopt(const char *arg)
166{
167 return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
168}
169
170static int
171optparse_is_longopt(const char *arg)
172{
173 return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
174}
175
176static void
177optparse_permute(const struct optparse * const options, int index)
178{
179 char *nonoption = options->argv[index];
180 int i;
181
182 for (i = index; i < options->optind - 1; i++)
183 options->argv[i] = options->argv[i + 1];
184
185 options->argv[options->optind - 1] = nonoption;
186}
187
188static int
189optparse_argtype(const char *optstring, char c)
190{
191 int count = OPTPARSE_NONE;
192
193 if (c == ':')
194 return -1;
195
196 for (; *optstring && c != *optstring; optstring++)
197 ;
198
199 if (!*optstring)
200 return -1;
201
202 if (optstring[1] == ':')
203 count += optstring[2] == ':' ? 2 : 1;
204
205 return count;
206}
207
208OPTPARSE_API
209int
210optparse(struct optparse *options, const char *optstring)
211{
212 int type;
213 char *next;
214 char *option = options->argv[options->optind];
215
216 if (option == 0)
217 return -1;
218
219 /* "--" forces an end of option-scanning */
220 if (optparse_is_dashdash(option)) {
221 options->optind++; /* consume "--" */
222 return -1;
223 }
224
225 if (!optparse_is_shortopt(option)) {
226 if (options->permute) {
227 int index = options->optind;
228
229 options->optind++;
230
231 int r = optparse(options, optstring);
232
233 optparse_permute(options, index);
234 options->optind--;
235 return r;
236 }
237
238 return -1;
239 }
240
241 options->errmsg[0] = '\0';
242 options->optopt = 0;
243 options->optarg = 0;
244 option += options->subopt + 1; /* skip "-" */
245 options->optopt = option[0];
246 type = optparse_argtype(optstring, option[0]);
247 next = options->argv[options->optind + 1];
248
249 char str[2] = {0, 0};
250
251 str[0] = options->optopt;
252
253 switch (type) {
254 case -1:
255 options->optind++;
256 return optparse_error(options, OPTPARSE_MSG_INVALID, str);
257 case OPTPARSE_NONE:
258 if (option[1]) {
259 options->subopt++;
260 } else {
261 options->subopt = 0;
262 options->optind++;
263 }
264 return option[0];
265 case OPTPARSE_REQUIRED:
266 options->subopt = 0;
267 options->optind++;
268 if (option[1]) {
269 options->optarg = option + 1;
270 } else if (next != 0) {
271 options->optarg = next;
272 options->optind++;
273 } else {
274 options->optarg = 0;
275 return optparse_error(options, OPTPARSE_MSG_MISSING, str);
276 }
277 return option[0];
278 case OPTPARSE_OPTIONAL:
279 options->subopt = 0;
280 options->optind++;
281 if (option[1])
282 options->optarg = option + 1;
283 else
284 options->optarg = 0;
285 return option[0];
286 default:
287 return optparse_error(options, OPTPARSE_MSG_UNKNOWN, str);
288 }
289}
290
291OPTPARSE_API
292char *
293optparse_arg(struct optparse *options)
294{
295 char *option = options->argv[options->optind];
296
297 options->subopt = 0;
298
299 if (option != 0)
300 options->optind++;
301 return option;
302}
303
304static int
305optparse_longopts_end(const struct optparse_long *longopts, int i)
306{
307 return !longopts[i].longname && !longopts[i].shortname;
308}
309
310static void
311optparse_from_long(const struct optparse_long *longopts, char *optstring)
312{
313 char *p = optstring;
314 int i;
315
316 for (i = 0; !optparse_longopts_end(longopts, i); i++) {
317 if (longopts[i].shortname && longopts[i].shortname < 127) {
318 int a;
319
320 *p++ = longopts[i].shortname;
321 for (a = 0; a < (int)longopts[i].argtype; a++)
322 *p++ = ':';
323 }
324 }
325
326 *p = '\0';
327}
328
329/* Unlike strcmp(), handles options containing "=". */
330static int
331optparse_longopts_match(const char *longname, const char *option)
332{
333 const char *a = option;
334 const char *n = longname;
335
336 if (longname == 0)
337 return 0;
338
339 for (; *a && *n && *a != '='; a++, n++) {
340 if (*a != *n)
341 return 0;
342 }
343
344 return *n == '\0' && (*a == '\0' || *a == '=');
345}
346
347/* Return the part after "=", or NULL. */
348static char *
349optparse_longopts_arg(char *option)
350{
351 for (; *option && *option != '='; option++)
352 ;
353
354 if (*option == '=')
355 return option + 1;
356 else
357 return 0;
358}
359
360static int
361optparse_long_fallback(struct optparse *options,
362 const struct optparse_long *longopts,
363 int *longindex)
364{
365 int result;
366 char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
367
368 optparse_from_long(longopts, optstring);
369 result = optparse(options, optstring);
370
371 /* longindex is NULL*/
372 if (!longindex)
373 return result;
374
375 /* longindex is not NULL*/
376 *longindex = -1; /* Default is -1 */
377
378 if (result != -1) {
379 for (int i = 0; !optparse_longopts_end(longopts, i); i++) {
380 if (longopts[i].shortname == options->optopt)
381 *longindex = i;
382 }
383 }
384
385 return result;
386}
387
388OPTPARSE_API
389int
390optparse_long(struct optparse *options,
391 const struct optparse_long *longopts,
392 int *longindex)
393{
394 int i;
395 char *option = options->argv[options->optind];
396
397 if (option == 0)
398 return -1;
399
400 if (optparse_is_dashdash(option)) {
401 options->optind++; /* consume "--" */
402 return -1;
403 }
404
405 if (optparse_is_shortopt(option))
406 return optparse_long_fallback(options, longopts, longindex);
407
408 if (!optparse_is_longopt(option)) {
409 if (options->permute) {
410 int index = options->optind;
411
412 options->optind++;
413
414 int r = optparse_long(options, longopts, longindex);
415
416 optparse_permute(options, index);
417 options->optind--;
418 return r;
419 }
420
421 return -1;
422 }
423
424 /* Parse as long option. */
425 options->errmsg[0] = '\0';
426 options->optopt = 0;
427 options->optarg = 0;
428 option += 2; /* skip "--" */
429 options->optind++;
430
431 for (i = 0; !optparse_longopts_end(longopts, i); i++) {
432 const char *name = longopts[i].longname;
433
434 if (optparse_longopts_match(name, option)) {
435 char *arg;
436
437 if (longindex)
438 *longindex = i;
439
440 options->optopt = longopts[i].shortname;
441 arg = optparse_longopts_arg(option);
442
443 if (longopts[i].argtype == OPTPARSE_NONE && arg != 0)
444 return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
445
446 if (arg != 0) {
447 options->optarg = arg;
448 return options->optopt;
449 }
450
451 /* arg is NULL */
452 if (longopts[i].argtype == OPTPARSE_REQUIRED) {
453 options->optarg = options->argv[options->optind];
454
455 if (options->optarg == 0)
456 return optparse_error(options, OPTPARSE_MSG_MISSING, name);
457
458 options->optind++;
459 }
460
461 return options->optopt;
462 }
463 }
464
465 return optparse_error(options, OPTPARSE_MSG_INVALID, option);
466}
467
468#endif /* OPTPARSE_IMPLEMENTATION */
469#endif /* MISC_OPTPARSE_H */