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 #ifndef OPTPARSE_H
48 #define OPTPARSE_H
49 
50 #ifndef OPTPARSE_API
51 #  define OPTPARSE_API
52 #endif
53 
54 struct optparse {
55     char **argv;
56     int permute;
57     int optind;
58     int optopt;
59     char *optarg;
60     char errmsg[64];
61     int subopt;
62 };
63 
64 enum optparse_argtype {
65     OPTPARSE_NONE,
66     OPTPARSE_REQUIRED,
67     OPTPARSE_OPTIONAL
68 };
69 
70 struct optparse_long {
71     const char *longname;
72     int shortname;
73     enum optparse_argtype argtype;
74 };
75 
76 /**
77  * Initializes the parser state.
78  */
79 OPTPARSE_API
80 void optparse_init(struct optparse *options, char **argv);
81 
82 /**
83  * Read the next option in the argv array.
84  * @param optstring a getopt()-formatted option string.
85  * @return the next option character, -1 for done, or '?' for error
86  *
87  * Just like getopt(), a character followed by no colons means no
88  * argument. One colon means the option has a required argument. Two
89  * colons means the option takes an optional argument.
90  */
91 OPTPARSE_API
92 int optparse(struct optparse *options, const char *optstring);
93 
94 /**
95  * Handles GNU-style long options in addition to getopt() options.
96  * This works a lot like GNU's getopt_long(). The last option in
97  * longopts must be all zeros, marking the end of the array. The
98  * longindex argument may be NULL.
99  */
100 OPTPARSE_API
101 int optparse_long(struct optparse *options,
102                   const struct optparse_long *longopts,
103                   int *longindex);
104 
105 /**
106  * Used for stepping over non-option arguments.
107  * @return the next non-option argument, or NULL for no more arguments
108  *
109  * Argument parsing can continue with optparse() after using this
110  * function. That would be used to parse the options for the
111  * subcommand returned by optparse_arg(). This function allows you to
112  * ignore the value of optind.
113  */
114 OPTPARSE_API
115 char *optparse_arg(struct optparse *options);
116 
117 /* Implementation */
118 #ifdef OPTPARSE_IMPLEMENTATION
119 
120 #define OPTPARSE_MSG_INVALID "invalid option"
121 #define OPTPARSE_MSG_MISSING "option requires an argument"
122 #define OPTPARSE_MSG_TOOMANY "option takes no arguments"
123 
124 static int
optparse_error(struct optparse * options,const char * msg,const char * data)125 optparse_error(struct optparse *options, const char *msg, const char *data)
126 {
127     unsigned p = 0;
128     const char *sep = " -- '";
129     while (*msg)
130         options->errmsg[p++] = *msg++;
131     while (*sep)
132         options->errmsg[p++] = *sep++;
133     while (p < sizeof(options->errmsg) - 2 && *data)
134         options->errmsg[p++] = *data++;
135     options->errmsg[p++] = '\'';
136     options->errmsg[p++] = '\0';
137     return '?';
138 }
139 
140 OPTPARSE_API
141 void
optparse_init(struct optparse * options,char ** argv)142 optparse_init(struct optparse *options, char **argv)
143 {
144     options->argv = argv;
145     options->permute = 1;
146     options->optind = 1;
147     options->subopt = 0;
148     options->optarg = 0;
149     options->errmsg[0] = '\0';
150 }
151 
152 static int
optparse_is_dashdash(const char * arg)153 optparse_is_dashdash(const char *arg)
154 {
155     return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
156 }
157 
158 static int
optparse_is_shortopt(const char * arg)159 optparse_is_shortopt(const char *arg)
160 {
161     return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
162 }
163 
164 static int
optparse_is_longopt(const char * arg)165 optparse_is_longopt(const char *arg)
166 {
167     return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
168 }
169 
170 static void
optparse_permute(struct optparse * options,int index)171 optparse_permute(struct optparse *options, int index)
172 {
173     char *nonoption = options->argv[index];
174     int i;
175     for (i = index; i < options->optind - 1; i++)
176         options->argv[i] = options->argv[i + 1];
177     options->argv[options->optind - 1] = nonoption;
178 }
179 
180 static int
optparse_argtype(const char * optstring,char c)181 optparse_argtype(const char *optstring, char c)
182 {
183     int count = OPTPARSE_NONE;
184     if (c == ':')
185         return -1;
186     for (; *optstring && c != *optstring; optstring++);
187     if (!*optstring)
188         return -1;
189     if (optstring[1] == ':')
190         count += optstring[2] == ':' ? 2 : 1;
191     return count;
192 }
193 
194 OPTPARSE_API
195 int
optparse(struct optparse * options,const char * optstring)196 optparse(struct optparse *options, const char *optstring)
197 {
198     int type;
199     char *next;
200     char *option = options->argv[options->optind];
201     options->errmsg[0] = '\0';
202     options->optopt = 0;
203     options->optarg = 0;
204     if (option == 0) {
205         return -1;
206     } else if (optparse_is_dashdash(option)) {
207         options->optind++; /* consume "--" */
208         return -1;
209     } else if (!optparse_is_shortopt(option)) {
210         if (options->permute) {
211             int index = options->optind++;
212             int r = optparse(options, optstring);
213             optparse_permute(options, index);
214             options->optind--;
215             return r;
216         } else {
217             return -1;
218         }
219     }
220     option += options->subopt + 1;
221     options->optopt = option[0];
222     type = optparse_argtype(optstring, option[0]);
223     next = options->argv[options->optind + 1];
224     switch (type) {
225     case -1: {
226         char str[2] = {0, 0};
227         str[0] = option[0];
228         options->optind++;
229         return optparse_error(options, OPTPARSE_MSG_INVALID, str);
230     }
231     case OPTPARSE_NONE:
232         if (option[1]) {
233             options->subopt++;
234         } else {
235             options->subopt = 0;
236             options->optind++;
237         }
238         return option[0];
239     case OPTPARSE_REQUIRED:
240         options->subopt = 0;
241         options->optind++;
242         if (option[1]) {
243             options->optarg = option + 1;
244         } else if (next != 0) {
245             options->optarg = next;
246             options->optind++;
247         } else {
248             char str[2] = {0, 0};
249             str[0] = option[0];
250             options->optarg = 0;
251             return optparse_error(options, OPTPARSE_MSG_MISSING, str);
252         }
253         return option[0];
254     case OPTPARSE_OPTIONAL:
255         options->subopt = 0;
256         options->optind++;
257         if (option[1])
258             options->optarg = option + 1;
259         else
260             options->optarg = 0;
261         return option[0];
262     }
263     return 0;
264 }
265 
266 OPTPARSE_API
267 char *
optparse_arg(struct optparse * options)268 optparse_arg(struct optparse *options)
269 {
270     char *option = options->argv[options->optind];
271     options->subopt = 0;
272     if (option != 0)
273         options->optind++;
274     return option;
275 }
276 
277 static int
optparse_longopts_end(const struct optparse_long * longopts,int i)278 optparse_longopts_end(const struct optparse_long *longopts, int i)
279 {
280     return !longopts[i].longname && !longopts[i].shortname;
281 }
282 
283 static void
optparse_from_long(const struct optparse_long * longopts,char * optstring)284 optparse_from_long(const struct optparse_long *longopts, char *optstring)
285 {
286     char *p = optstring;
287     int i;
288     for (i = 0; !optparse_longopts_end(longopts, i); i++) {
289         if (longopts[i].shortname) {
290             int a;
291             *p++ = longopts[i].shortname;
292             for (a = 0; a < (int)longopts[i].argtype; a++)
293                 *p++ = ':';
294         }
295     }
296     *p = '\0';
297 }
298 
299 /* Unlike strcmp(), handles options containing "=". */
300 static int
optparse_longopts_match(const char * longname,const char * option)301 optparse_longopts_match(const char *longname, const char *option)
302 {
303     const char *a = option, *n = longname;
304     if (longname == 0)
305         return 0;
306     for (; *a && *n && *a != '='; a++, n++)
307         if (*a != *n)
308             return 0;
309     return *n == '\0' && (*a == '\0' || *a == '=');
310 }
311 
312 /* Return the part after "=", or NULL. */
313 static char *
optparse_longopts_arg(char * option)314 optparse_longopts_arg(char *option)
315 {
316     for (; *option && *option != '='; option++);
317     if (*option == '=')
318         return option + 1;
319     else
320         return 0;
321 }
322 
323 static int
optparse_long_fallback(struct optparse * options,const struct optparse_long * longopts,int * longindex)324 optparse_long_fallback(struct optparse *options,
325                        const struct optparse_long *longopts,
326                        int *longindex)
327 {
328     int result;
329     char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
330     optparse_from_long(longopts, optstring);
331     result = optparse(options, optstring);
332     if (longindex != 0) {
333         *longindex = -1;
334         if (result != -1) {
335             int i;
336             for (i = 0; !optparse_longopts_end(longopts, i); i++)
337                 if (longopts[i].shortname == options->optopt)
338                     *longindex = i;
339         }
340     }
341     return result;
342 }
343 
344 OPTPARSE_API
345 int
optparse_long(struct optparse * options,const struct optparse_long * longopts,int * longindex)346 optparse_long(struct optparse *options,
347               const struct optparse_long *longopts,
348               int *longindex)
349 {
350     int i;
351     char *option = options->argv[options->optind];
352     if (option == 0) {
353         return -1;
354     } else if (optparse_is_dashdash(option)) {
355         options->optind++; /* consume "--" */
356         return -1;
357     } else if (optparse_is_shortopt(option)) {
358         return optparse_long_fallback(options, longopts, longindex);
359     } else if (!optparse_is_longopt(option)) {
360         if (options->permute) {
361             int index = options->optind++;
362             int r = optparse_long(options, longopts, longindex);
363             optparse_permute(options, index);
364             options->optind--;
365             return r;
366         } else {
367             return -1;
368         }
369     }
370 
371     /* Parse as long option. */
372     options->errmsg[0] = '\0';
373     options->optopt = 0;
374     options->optarg = 0;
375     option += 2; /* skip "--" */
376     options->optind++;
377     for (i = 0; !optparse_longopts_end(longopts, i); i++) {
378         const char *name = longopts[i].longname;
379         if (optparse_longopts_match(name, option)) {
380             char *arg;
381             if (longindex)
382                 *longindex = i;
383             options->optopt = longopts[i].shortname;
384             arg = optparse_longopts_arg(option);
385             if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
386                 return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
387             } if (arg != 0) {
388                 options->optarg = arg;
389             } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
390                 options->optarg = options->argv[options->optind];
391                 if (options->optarg == 0)
392                     return optparse_error(options, OPTPARSE_MSG_MISSING, name);
393                 else
394                     options->optind++;
395             }
396             return options->optopt;
397         }
398     }
399     return optparse_error(options, OPTPARSE_MSG_INVALID, option);
400 }
401 
402 #endif /* OPTPARSE_IMPLEMENTATION */
403 #endif /* OPTPARSE_H */
404 
405