1#include "argparse/libargparse.h"
2
3#include <stdio.h>
4
5#define ARGPARSE_MSG_INVALID "invalid option"
6#define ARGPARSE_MSG_MISSING "option requires an argument"
7#define ARGPARSE_MSG_TOOMANY "option takes no arguments"
8
9static int argparse_error(argparse_state_t *state, const char *msg, const char *data)
10{
11 unsigned p = 0;
12 const char *sep = " -- '";
13 while (*msg)
14 state->errmsg[p++] = *msg++;
15 while (*sep)
16 state->errmsg[p++] = *sep++;
17 while (p < sizeof(state->errmsg) - 2 && *data)
18 state->errmsg[p++] = *data++;
19 state->errmsg[p++] = '\'';
20 state->errmsg[p++] = '\0';
21 return '?';
22}
23
24void argparse_init(argparse_state_t *state, const char *argv[])
25{
26 state->argv = argv;
27 state->permute = 1;
28 state->optind = argv[0] != 0;
29 state->subopt = 0;
30 state->optarg = 0;
31 state->errmsg[0] = '\0';
32}
33
34static int argparse_is_dashdash(const char *arg)
35{
36 return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
37}
38
39static int argparse_is_shortopt(const char *arg)
40{
41 return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
42}
43
44static int argparse_is_longopt(const char *arg)
45{
46 return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
47}
48
49static void argparse_permute(argparse_state_t *state, int index)
50{
51 const char *nonoption = state->argv[index];
52 int i;
53 for (i = index; i < state->optind - 1; i++)
54 state->argv[i] = state->argv[i + 1];
55 state->argv[state->optind - 1] = nonoption;
56}
57
58static int argparse_get_argtype(const char *optstring, char c)
59{
60 int count = ARGPARSE_NONE;
61 if (c == ':')
62 return -1;
63 for (; *optstring && c != *optstring; optstring++)
64 ;
65 if (!*optstring)
66 return -1;
67 if (optstring[1] == ':')
68 count += optstring[2] == ':' ? 2 : 1;
69 return count;
70}
71
72int argparse(argparse_state_t *state, const char *optstring)
73{
74 int type;
75 const char *next;
76 const char *option = state->argv[state->optind];
77 state->errmsg[0] = '\0';
78 state->optopt = 0;
79 state->optarg = 0;
80 if (option == 0)
81 {
82 return -1;
83 }
84 else if (argparse_is_dashdash(arg: option))
85 {
86 state->optind++; /* consume "--" */
87 return -1;
88 }
89 else if (!argparse_is_shortopt(arg: option))
90 {
91 if (state->permute)
92 {
93 int index = state->optind++;
94 int r = argparse(state, optstring);
95 argparse_permute(state, index);
96 state->optind--;
97 return r;
98 }
99 else
100 {
101 return -1;
102 }
103 }
104 option += state->subopt + 1;
105 state->optopt = option[0];
106 type = argparse_get_argtype(optstring, c: option[0]);
107 next = state->argv[state->optind + 1];
108 switch (type)
109 {
110 case -1:
111 {
112 char str[2] = { 0, 0 };
113 str[0] = option[0];
114 state->optind++;
115 return argparse_error(state, ARGPARSE_MSG_INVALID, data: str);
116 }
117 case ARGPARSE_NONE:
118 if (option[1])
119 {
120 state->subopt++;
121 }
122 else
123 {
124 state->subopt = 0;
125 state->optind++;
126 }
127 return option[0];
128 case ARGPARSE_REQUIRED:
129 state->subopt = 0;
130 state->optind++;
131 if (option[1])
132 {
133 state->optarg = option + 1;
134 }
135 else if (next != 0)
136 {
137 state->optarg = next;
138 state->optind++;
139 }
140 else
141 {
142 char str[2] = { 0, 0 };
143 str[0] = option[0];
144 state->optarg = 0;
145 return argparse_error(state, ARGPARSE_MSG_MISSING, data: str);
146 }
147 return option[0];
148 case ARGPARSE_OPTIONAL:
149 state->subopt = 0;
150 state->optind++;
151 if (option[1])
152 state->optarg = option + 1;
153 else
154 state->optarg = 0;
155 return option[0];
156 }
157 return 0;
158}
159
160const char *argparse_arg(argparse_state_t *options)
161{
162 const char *option = options->argv[options->optind];
163 options->subopt = 0;
164 if (option != 0)
165 options->optind++;
166 return option;
167}
168
169static int argparse_longopts_end(const argparse_arg_t *longopts, int i)
170{
171 return !longopts[i].full && !longopts[i].abbr;
172}
173
174static void argparse_from_long(const argparse_arg_t *longopts, char *optstring)
175{
176 char *p = optstring;
177 int i;
178 for (i = 0; !argparse_longopts_end(longopts, i); i++)
179 {
180 if (longopts[i].abbr && longopts[i].abbr < 127)
181 {
182 int a;
183 *p++ = longopts[i].abbr;
184 for (a = 0; a < (int) longopts[i].argtype; a++)
185 *p++ = ':';
186 }
187 }
188 *p = '\0';
189}
190
191/* Unlike strcmp(), handles options containing "=". */
192static int argparse_longopts_match(const char *longname, const char *option)
193{
194 const char *a = option, *n = longname;
195 if (longname == 0)
196 return 0;
197 for (; *a && *n && *a != '='; a++, n++)
198 if (*a != *n)
199 return 0;
200 return *n == '\0' && (*a == '\0' || *a == '=');
201}
202
203/* Return the part after "=", or NULL. */
204static const char *argparse_longopts_arg(const char *option)
205{
206 for (; *option && *option != '='; option++)
207 ;
208 if (*option == '=')
209 return option + 1;
210 else
211 return 0;
212}
213
214static int argparse_long_fallback(argparse_state_t *options, const argparse_arg_t *longopts, int *longindex)
215{
216 int result;
217 char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
218 argparse_from_long(longopts, optstring);
219 result = argparse(state: options, optstring);
220 if (longindex != 0)
221 {
222 *longindex = -1;
223 if (result != -1)
224 {
225 int i;
226 for (i = 0; !argparse_longopts_end(longopts, i); i++)
227 if (longopts[i].abbr == options->optopt)
228 *longindex = i;
229 }
230 }
231 return result;
232}
233
234int argparse_long(argparse_state_t *options, const argparse_arg_t *longopts, int *longindex)
235{
236 int i;
237 const char *option = options->argv[options->optind];
238 if (option == 0)
239 {
240 return -1;
241 }
242 else if (argparse_is_dashdash(arg: option))
243 {
244 options->optind++; /* consume "--" */
245 return -1;
246 }
247 else if (argparse_is_shortopt(arg: option))
248 {
249 return argparse_long_fallback(options, longopts, longindex);
250 }
251 else if (!argparse_is_longopt(arg: option))
252 {
253 if (options->permute)
254 {
255 int index = options->optind++;
256 int r = argparse_long(options, longopts, longindex);
257 argparse_permute(state: options, index);
258 options->optind--;
259 return r;
260 }
261 else
262 {
263 return -1;
264 }
265 }
266
267 /* Parse as long option. */
268 options->errmsg[0] = '\0';
269 options->optopt = 0;
270 options->optarg = 0;
271 option += 2; /* skip "--" */
272 options->optind++;
273 for (i = 0; !argparse_longopts_end(longopts, i); i++)
274 {
275 const char *name = longopts[i].full;
276 if (argparse_longopts_match(longname: name, option))
277 {
278 const char *arg;
279 if (longindex)
280 *longindex = i;
281 options->optopt = longopts[i].abbr;
282 arg = argparse_longopts_arg(option);
283 if (longopts[i].argtype == ARGPARSE_NONE && arg != 0)
284 {
285 return argparse_error(state: options, ARGPARSE_MSG_TOOMANY, data: name);
286 }
287 if (arg != 0)
288 {
289 options->optarg = arg;
290 }
291 else if (longopts[i].argtype == ARGPARSE_REQUIRED)
292 {
293 options->optarg = options->argv[options->optind];
294 if (options->optarg == 0)
295 return argparse_error(state: options, ARGPARSE_MSG_MISSING, data: name);
296 else
297 options->optind++;
298 }
299 return options->optopt;
300 }
301 }
302 return argparse_error(state: options, ARGPARSE_MSG_INVALID, data: option);
303}
304
305void argparse_usage(argparse_state_t *options, const argparse_arg_t *args, const char *usage)
306{
307 fprintf(stream: stderr, format: "Usage: %s, %s\n", options->argv[0], usage);
308 static const int help_indent = 24;
309
310 for (int i = 0; !argparse_longopts_end(longopts: args, i); i++)
311 {
312 const char *name = args[i].full;
313 const char abbr = args[i].abbr;
314 const char *help = args[i].help ? args[i].help : "";
315 const char *arg = args[i].argtype == ARGPARSE_NONE ? "" : args[i].argtype == ARGPARSE_REQUIRED ? " ARG" : " [ARG]";
316
317 int printed = 0;
318 if (abbr)
319 printed += fprintf(stream: stderr, format: " -%c", abbr);
320
321 if (name)
322 printed += fprintf(stream: stderr, format: ", --%s%s", name, arg);
323 else
324 printed++;
325
326 if (printed < help_indent)
327 fprintf(stream: stderr, format: "%*s", help_indent - printed, "");
328 else
329 fprintf(stream: stderr, format: "\n%*s", help_indent, "");
330
331 fprintf(stream: stderr, format: "%s\n", help);
332 }
333
334 fprintf(stream: stderr, format: "\n");
335}
336