1 #include <stdio.h>
2 #ifdef _MSC_VER
3 #include <stdlib.h>     /* for malloc(), free() */
4 #endif
5 #include "optparse.h"
6 
7 #define opterror(options, format, ...) \
8     snprintf(options->errmsg, sizeof(options->errmsg), format, __VA_ARGS__);
9 
10 #define options_argv(i) \
11     ((i) < options->argc ? options->argv[i] : NULL)
12 
optparse_init(struct optparse * options,int argc,char ** argv)13 void optparse_init(struct optparse *options, int argc, char **argv)
14 {
15     options->argv = argv;
16     options->argc = argc;
17     options->permute = 1;
18     options->optind = 1;
19     options->subopt = 0;
20     options->optarg = NULL;
21     options->errmsg[0] = '\0';
22 }
23 
24 static inline int
is_dashdash(const char * arg)25 is_dashdash(const char *arg)
26 {
27     return arg != NULL && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
28 }
29 
30 static inline int
is_shortopt(const char * arg)31 is_shortopt(const char *arg)
32 {
33     return arg != NULL && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
34 }
35 
36 static inline int
is_longopt(const char * arg)37 is_longopt(const char *arg)
38 {
39     return arg != NULL && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
40 }
41 
42 static void
permute(struct optparse * options,int index)43 permute(struct optparse *options, int index)
44 {
45     char *nonoption = options->argv[index];
46     for (int i = index; i < options->optind - 1; i++)
47         options->argv[i] = options->argv[i + 1];
48     options->argv[options->optind - 1] = nonoption;
49 }
50 
51 static enum optparse_argtype
argtype(const char * optstring,char c)52 argtype(const char *optstring, char c)
53 {
54     if (c == ':')
55         return -1;
56     for (; *optstring && c != *optstring; optstring++);
57     if (!*optstring)
58         return -1;
59     enum optparse_argtype count = OPTPARSE_NONE;
60     if (optstring[1] == ':')
61         count += optstring[2] == ':' ? 2 : 1;
62     return count;
63 }
64 
optparse(struct optparse * options,const char * optstring)65 int optparse(struct optparse *options, const char *optstring)
66 {
67     options->errmsg[0] = '\0';
68     options->optopt = 0;
69     options->optarg = NULL;
70     char *option = options_argv(options->optind);
71     if (option == NULL) {
72         return -1;
73     } else if (is_dashdash(option)) {
74         options->optind++; // consume "--"
75         return -1;
76     } else if (!is_shortopt(option)) {
77         if (options->permute) {
78             int index = options->optind;
79             options->optind++;
80             int r = optparse(options, optstring);
81             permute(options, index);
82             options->optind--;
83             return r;
84         } else {
85             return -1;
86         }
87     }
88     option += options->subopt + 1;
89     options->optopt = option[0];
90     int type = argtype(optstring, option[0]);
91     char *next = options_argv(options->optind + 1);
92     switch (type) {
93     case -1:
94         opterror(options, "invalid option -- '%c'", option[0]);
95         options->optind++;
96         return '?';
97     case OPTPARSE_NONE:
98         if (option[1]) {
99             options->subopt++;
100         } else {
101             options->subopt = 0;
102             options->optind++;
103         }
104         return option[0];
105     case OPTPARSE_REQUIRED:
106         options->subopt = 0;
107         options->optind++;
108         if (option[1]) {
109             options->optarg = option + 1;
110         } else if (next != NULL) {
111             options->optarg = next;
112             options->optind++;
113         } else {
114             opterror(options, "option requires an argument -- '%c'", option[0]);
115             options->optarg = NULL;
116             return '?';
117         }
118         return option[0];
119     case OPTPARSE_OPTIONAL:
120         options->subopt = 0;
121         options->optind++;
122         if (option[1])
123             options->optarg = option + 1;
124         else
125             options->optarg = NULL;
126         return option[0];
127     }
128     return 0;
129 }
130 
optparse_arg(struct optparse * options)131 char *optparse_arg(struct optparse *options)
132 {
133     options->subopt = 0;
134     char *option = options->argv[options->optind];
135     if (option != NULL)
136         options->optind++;
137     return option;
138 }
139 
140 static inline int
longopts_end(const struct optparse_long * longopts,int i)141 longopts_end(const struct optparse_long *longopts, int i)
142 {
143     return !longopts[i].longname && !longopts[i].shortname;
144 }
145 
146 static size_t
optstring_length(const struct optparse_long * longopts)147 optstring_length(const struct optparse_long *longopts)
148 {
149     int length = 0;
150     for (int i = 0; !longopts_end(longopts, i); i++, length++)
151         length += longopts[i].argtype;
152     return length + 1;
153 }
154 
155 static void
optstring_from_long(const struct optparse_long * longopts,char * optstring)156 optstring_from_long(const struct optparse_long *longopts, char *optstring)
157 {
158     char *p = optstring;
159     for (int i = 0; !longopts_end(longopts, i); i++) {
160         if (longopts[i].shortname) {
161             *p++ = longopts[i].shortname;
162             for (unsigned int a = 0; a < longopts[i].argtype; a++)
163                 *p++ = ':';
164         }
165     }
166     *p = '\0';
167 }
168 
169 /* Unlike strcmp(), handles options containing "=". */
170 static int
longopts_match(const char * longname,const char * option)171 longopts_match(const char *longname, const char *option)
172 {
173     if (longname == NULL)
174         return 0;
175     const char *a = option, *n = longname;
176     for (; *a && *n && *a != '='; a++, n++)
177         if (*a != *n)
178             return 0;
179     return *n == '\0' && (*a == '\0' || *a == '=');
180 }
181 
182 /* Return the part after "=", or NULL. */
183 static const char *
longopts_arg(const char * option)184 longopts_arg(const char *option)
185 {
186     for (; *option && *option != '='; option++);
187     if (*option == '=')
188         return option + 1;
189     else
190         return NULL;
191 }
192 
193 static int
long_fallback(struct optparse * options,const struct optparse_long * longopts,int * longindex)194 long_fallback(struct optparse *options,
195               const struct optparse_long *longopts,
196               int *longindex)
197 {
198 #ifdef _MSC_VER
199     /* Variable length arrays are not currently supported in Visual Studio */
200     char *optstring = malloc(optstring_length(longopts));
201 #else
202     char optstring[optstring_length(longopts)];
203 #endif
204     optstring_from_long(longopts, optstring);
205     int result = optparse(options, optstring);
206     if (longindex != NULL) {
207         *longindex = -1;
208         if (result != -1)
209             for (int i = 0; !longopts_end(longopts, i); i++)
210                 if (longopts[i].shortname == options->optopt)
211                     *longindex = i;
212     }
213 #ifdef _MSC_VER
214     free(optstring);
215 #endif
216     return result;
217 }
218 
219 int
optparse_long(struct optparse * options,const struct optparse_long * longopts,int * longindex)220 optparse_long(struct optparse *options,
221               const struct optparse_long *longopts,
222               int *longindex)
223 {
224 //    printf("%i < %i\n",options->optind,options->argc);
225     char *option = options_argv(options->optind);
226     if (option == NULL) {
227         return -1;
228     } else if (is_dashdash(option)) {
229         options->optind++; // consume "--"
230         return -1;
231     } else if (is_shortopt(option)) {
232         return long_fallback(options, longopts, longindex);
233     } else if (!is_longopt(option)) {
234         if (options->permute) {
235             int index = options->optind;
236             options->optind++;
237             int r = optparse_long(options, longopts, longindex);
238             permute(options, index);
239             options->optind--;
240             return r;
241         } else {
242             return -1;
243         }
244     }
245 
246     /* Parse as long option. */
247     options->errmsg[0] = '\0';
248     options->optopt = 0;
249     options->optarg = NULL;
250     option += 2; // skip "--"
251     options->optind++;
252     for (int i = 0; !longopts_end(longopts, i); i++) {
253         const char *name = longopts[i].longname;
254         if (longopts_match(name, option)) {
255             if (longindex)
256                 *longindex = i;
257             options->optopt = longopts[i].shortname;
258             const char *arg = longopts_arg(option);
259             if (longopts[i].argtype == OPTPARSE_NONE && arg != NULL) {
260                 opterror(options, "option takes no arguments -- '%s'", name);
261                 return '?';
262             } if (arg != NULL) {
263                 options->optarg = arg;
264             } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
265                 options->optarg = options_argv(options->optind);
266                 options->optind++;
267                 if (options->optarg == NULL) {
268                     opterror(options, "option requires argument -- '%s'", name);
269                     return '?';
270                 }
271             }
272             return options->optopt;
273         }
274     }
275     opterror(options, "invalid option -- '%s'", option);
276     return '?';
277 }
278