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