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