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