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