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