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 | |
9 | static 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 | |
24 | void 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 | |
34 | static int argparse_is_dashdash(const char *arg) |
35 | { |
36 | return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; |
37 | } |
38 | |
39 | static int argparse_is_shortopt(const char *arg) |
40 | { |
41 | return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; |
42 | } |
43 | |
44 | static int argparse_is_longopt(const char *arg) |
45 | { |
46 | return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; |
47 | } |
48 | |
49 | static 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 | |
58 | static 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 | |
72 | int 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 | |
160 | const 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 | |
169 | static int argparse_longopts_end(const argparse_arg_t *longopts, int i) |
170 | { |
171 | return !longopts[i].full && !longopts[i].abbr; |
172 | } |
173 | |
174 | static 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 "=". */ |
192 | static 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. */ |
204 | static 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 | |
214 | static 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 | |
234 | int 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 | |
305 | void 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 | |