d3451207f548127ad34f41dbc77fd8b934fc398e
[babeltrace.git] / src / argpar / argpar.c
1 /*
2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <glib.h>
26
27 #include "common/assert.h"
28
29 #include "argpar.h"
30
31 static
32 void destroy_item(struct bt_argpar_item * const item)
33 {
34 if (!item) {
35 goto end;
36 }
37
38 if (item->type == BT_ARGPAR_ITEM_TYPE_OPT) {
39 struct bt_argpar_item_opt * const opt_item = (void *) item;
40
41 g_free((void *) opt_item->arg);
42 }
43
44 g_free(item);
45
46 end:
47 return;
48 }
49
50 static
51 struct bt_argpar_item_opt *create_opt_item(
52 const struct bt_argpar_opt_descr * const descr,
53 const char * const arg)
54 {
55 struct bt_argpar_item_opt *opt_item =
56 g_new0(struct bt_argpar_item_opt, 1);
57
58 if (!opt_item) {
59 goto end;
60 }
61
62 opt_item->base.type = BT_ARGPAR_ITEM_TYPE_OPT;
63 opt_item->descr = descr;
64
65 if (arg) {
66 opt_item->arg = g_strdup(arg);
67 if (!opt_item->arg) {
68 goto error;
69 }
70 }
71
72 goto end;
73
74 error:
75 destroy_item(&opt_item->base);
76 opt_item = NULL;
77
78 end:
79 return opt_item;
80 }
81
82 static
83 struct bt_argpar_item_non_opt *create_non_opt_item(const char * const arg,
84 const unsigned int orig_index,
85 const unsigned int non_opt_index)
86 {
87 struct bt_argpar_item_non_opt * const non_opt_item =
88 g_new0(struct bt_argpar_item_non_opt, 1);
89
90 if (!non_opt_item) {
91 goto end;
92 }
93
94 non_opt_item->base.type = BT_ARGPAR_ITEM_TYPE_NON_OPT;
95 non_opt_item->arg = arg;
96 non_opt_item->orig_index = orig_index;
97 non_opt_item->non_opt_index = non_opt_index;
98
99 end:
100 return non_opt_item;
101 }
102
103 static
104 const struct bt_argpar_opt_descr *find_descr(
105 const struct bt_argpar_opt_descr * const descrs,
106 const char short_name, const char * const long_name)
107 {
108 const struct bt_argpar_opt_descr *descr;
109
110 for (descr = descrs; descr->short_name || descr->long_name; descr++) {
111 if (short_name && descr->short_name &&
112 short_name == descr->short_name) {
113 goto end;
114 }
115
116 if (long_name && descr->long_name &&
117 strcmp(long_name, descr->long_name) == 0) {
118 goto end;
119 }
120 }
121
122 end:
123 return !descr->short_name && !descr->long_name ? NULL : descr;
124 }
125
126 enum parse_orig_arg_opt_ret {
127 PARSE_ORIG_ARG_OPT_RET_OK,
128 PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2,
129 PARSE_ORIG_ARG_OPT_RET_ERROR = -1,
130 };
131
132 static
133 enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts,
134 const char * const next_orig_arg,
135 const struct bt_argpar_opt_descr * const descrs,
136 struct bt_argpar_parse_ret * const parse_ret,
137 bool * const used_next_orig_arg)
138 {
139 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
140 const char *short_opt_ch = short_opts;
141
142 if (strlen(short_opts) == 0) {
143 g_string_append(parse_ret->error, "Invalid argument");
144 goto error;
145 }
146
147 while (*short_opt_ch) {
148 const char *opt_arg = NULL;
149 const struct bt_argpar_opt_descr *descr;
150 struct bt_argpar_item_opt *opt_item;
151
152 /* Find corresponding option descriptor */
153 descr = find_descr(descrs, *short_opt_ch, NULL);
154 if (!descr) {
155 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
156 g_string_append_printf(parse_ret->error,
157 "Unknown option `-%c`", *short_opt_ch);
158 goto error;
159 }
160
161 if (descr->with_arg) {
162 if (short_opt_ch[1]) {
163 /* `-oarg` form */
164 opt_arg = &short_opt_ch[1];
165 } else {
166 /* `-o arg` form */
167 opt_arg = next_orig_arg;
168 *used_next_orig_arg = true;
169 }
170
171 /*
172 * We accept `-o ''` (empty option's argument),
173 * but not `-o` alone if an option's argument is
174 * expected.
175 */
176 if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) {
177 g_string_append_printf(parse_ret->error,
178 "Missing required argument for option `-%c`",
179 *short_opt_ch);
180 *used_next_orig_arg = false;
181 goto error;
182 }
183 }
184
185 /* Create and append option argument */
186 opt_item = create_opt_item(descr, opt_arg);
187 if (!opt_item) {
188 goto error;
189 }
190
191 g_ptr_array_add(parse_ret->items, opt_item);
192
193 if (descr->with_arg) {
194 /* Option has an argument: no more options */
195 break;
196 }
197
198 /* Go to next short option */
199 short_opt_ch++;
200 }
201
202 goto end;
203
204 error:
205 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
206 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
207 }
208
209 end:
210 return ret;
211 }
212
213 static
214 enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg,
215 const char * const next_orig_arg,
216 const struct bt_argpar_opt_descr * const descrs,
217 struct bt_argpar_parse_ret * const parse_ret,
218 bool * const used_next_orig_arg)
219 {
220 const size_t max_len = 127;
221 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
222 const struct bt_argpar_opt_descr *descr;
223 struct bt_argpar_item_opt *opt_item;
224
225 /* Option's argument, if any */
226 const char *opt_arg = NULL;
227
228 /* Position of first `=`, if any */
229 const char *eq_pos;
230
231 /* Buffer holding option name when `long_opt_arg` contains `=` */
232 char buf[max_len + 1];
233
234 /* Option name */
235 const char *long_opt_name = long_opt_arg;
236
237 if (strlen(long_opt_arg) == 0) {
238 g_string_append(parse_ret->error, "Invalid argument");
239 goto error;
240 }
241
242 /* Find the first `=` in original argument */
243 eq_pos = strchr(long_opt_arg, '=');
244 if (eq_pos) {
245 const size_t long_opt_name_size = eq_pos - long_opt_arg;
246
247 /* Isolate the option name */
248 if (long_opt_name_size > max_len) {
249 g_string_append_printf(parse_ret->error,
250 "Invalid argument `--%s`", long_opt_arg);
251 goto error;
252 }
253
254 memcpy(buf, long_opt_arg, long_opt_name_size);
255 buf[long_opt_name_size] = '\0';
256 long_opt_name = buf;
257 }
258
259 /* Find corresponding option descriptor */
260 descr = find_descr(descrs, '\0', long_opt_name);
261 if (!descr) {
262 g_string_append_printf(parse_ret->error,
263 "Unknown option `--%s`", long_opt_name);
264 ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT;
265 goto error;
266 }
267
268 /* Find option's argument if any */
269 if (descr->with_arg) {
270 if (eq_pos) {
271 /* `--long-opt=arg` style */
272 opt_arg = eq_pos + 1;
273 } else {
274 /* `--long-opt arg` style */
275 if (!next_orig_arg) {
276 g_string_append_printf(parse_ret->error,
277 "Missing required argument for option `--%s`",
278 long_opt_name);
279 goto error;
280 }
281
282 opt_arg = next_orig_arg;
283 *used_next_orig_arg = true;
284 }
285 }
286
287 /* Create and append option argument */
288 opt_item = create_opt_item(descr, opt_arg);
289 if (!opt_item) {
290 goto error;
291 }
292
293 g_ptr_array_add(parse_ret->items, opt_item);
294 goto end;
295
296 error:
297 if (ret == PARSE_ORIG_ARG_OPT_RET_OK) {
298 ret = PARSE_ORIG_ARG_OPT_RET_ERROR;
299 }
300
301 end:
302 return ret;
303 }
304
305 static
306 enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg,
307 const char * const next_orig_arg,
308 const struct bt_argpar_opt_descr * const descrs,
309 struct bt_argpar_parse_ret * const parse_ret,
310 bool * const used_next_orig_arg)
311 {
312 enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK;
313
314 BT_ASSERT(orig_arg[0] == '-');
315
316 if (orig_arg[1] == '-') {
317 /* Long option */
318 ret = parse_long_opt(&orig_arg[2],
319 next_orig_arg, descrs, parse_ret,
320 used_next_orig_arg);
321 } else {
322 /* Short option */
323 ret = parse_short_opts(&orig_arg[1],
324 next_orig_arg, descrs, parse_ret,
325 used_next_orig_arg);
326 }
327
328 return ret;
329 }
330
331 static
332 void prepend_while_parsing_arg_to_error(GString * const error,
333 const unsigned int i, const char * const arg)
334 {
335 /* 🙁 There's no g_string_prepend_printf()! */
336 GString * const tmp_str = g_string_new(NULL);
337
338 BT_ASSERT(error);
339 BT_ASSERT(arg);
340
341 if (!tmp_str) {
342 goto end;
343 }
344
345 g_string_append_printf(tmp_str, "While parsing argument #%u (`%s`): %s",
346 i + 1, arg, error->str);
347 g_string_assign(error, tmp_str->str);
348 g_string_free(tmp_str, TRUE);
349
350 end:
351 return;
352 }
353
354 BT_HIDDEN
355 struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc,
356 const char * const *argv,
357 const struct bt_argpar_opt_descr * const descrs,
358 bool fail_on_unknown_opt)
359 {
360 struct bt_argpar_parse_ret parse_ret = { 0 };
361 unsigned int i;
362 unsigned int non_opt_index = 0;
363
364 parse_ret.error = g_string_new(NULL);
365 if (!parse_ret.error) {
366 goto error;
367 }
368
369 parse_ret.items = g_ptr_array_new_with_free_func(
370 (GDestroyNotify) destroy_item);
371 if (!parse_ret.items) {
372 goto error;
373 }
374
375 for (i = 0; i < argc; i++) {
376 enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret;
377 bool used_next_orig_arg = false;
378 const char * const orig_arg = argv[i];
379 const char * const next_orig_arg =
380 i < argc - 1 ? argv[i + 1] : NULL;
381
382 if (orig_arg[0] != '-') {
383 /* Non-option argument */
384 struct bt_argpar_item_non_opt *non_opt_item =
385 create_non_opt_item(orig_arg, i, non_opt_index);
386
387 if (!non_opt_item) {
388 goto error;
389 }
390
391 non_opt_index++;
392 g_ptr_array_add(parse_ret.items, non_opt_item);
393 continue;
394 }
395
396 /* Option argument */
397 parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg,
398 next_orig_arg, descrs, &parse_ret, &used_next_orig_arg);
399 switch (parse_orig_arg_opt_ret) {
400 case PARSE_ORIG_ARG_OPT_RET_OK:
401 break;
402 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT:
403 BT_ASSERT(!used_next_orig_arg);
404
405 if (fail_on_unknown_opt) {
406 prepend_while_parsing_arg_to_error(
407 parse_ret.error, i, orig_arg);
408 goto error;
409 }
410
411 /*
412 * The current original argument is not
413 * considered ingested because it triggered an
414 * unknown option.
415 */
416 parse_ret.ingested_orig_args = i;
417 g_string_free(parse_ret.error, TRUE);
418 parse_ret.error = NULL;
419 goto end;
420 case PARSE_ORIG_ARG_OPT_RET_ERROR:
421 prepend_while_parsing_arg_to_error(
422 parse_ret.error, i, orig_arg);
423 goto error;
424 default:
425 abort();
426 }
427
428 if (used_next_orig_arg) {
429 i++;
430 }
431 }
432
433 parse_ret.ingested_orig_args = argc;
434 g_string_free(parse_ret.error, TRUE);
435 parse_ret.error = NULL;
436 goto end;
437
438 error:
439 if (parse_ret.items) {
440 /* That's how we indicate that an error occured */
441 g_ptr_array_free(parse_ret.items, TRUE);
442 parse_ret.items = NULL;
443 }
444
445 end:
446 return parse_ret;
447 }
448
449 BT_HIDDEN
450 void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret)
451 {
452 BT_ASSERT(ret);
453
454 if (ret->items) {
455 g_ptr_array_free(ret->items, TRUE);
456 ret->items = NULL;
457 }
458
459 if (ret->error) {
460 g_string_free(ret->error, TRUE);
461 ret->error = NULL;
462 }
463 }
This page took 0.044851 seconds and 3 git commands to generate.