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