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