2 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
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:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
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
28 #include "common/assert.h"
29 #include "common/common.h"
34 void destroy_item(struct bt_argpar_item
* const item
)
40 if (item
->type
== BT_ARGPAR_ITEM_TYPE_OPT
) {
41 struct bt_argpar_item_opt
* const opt_item
= (void *) item
;
43 g_free((void *) opt_item
->arg
);
53 struct bt_argpar_item_opt
*create_opt_item(
54 const struct bt_argpar_opt_descr
* const descr
,
55 const char * const arg
)
57 struct bt_argpar_item_opt
*opt_item
=
58 g_new0(struct bt_argpar_item_opt
, 1);
64 opt_item
->base
.type
= BT_ARGPAR_ITEM_TYPE_OPT
;
65 opt_item
->descr
= descr
;
68 opt_item
->arg
= g_strdup(arg
);
77 destroy_item(&opt_item
->base
);
85 struct 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
)
89 struct bt_argpar_item_non_opt
* const non_opt_item
=
90 g_new0(struct bt_argpar_item_non_opt
, 1);
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
;
106 const 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
)
110 const struct bt_argpar_opt_descr
*descr
;
112 for (descr
= descrs
; descr
->short_name
|| descr
->long_name
; descr
++) {
113 if (short_name
&& descr
->short_name
&&
114 short_name
== descr
->short_name
) {
118 if (long_name
&& descr
->long_name
&&
119 strcmp(long_name
, descr
->long_name
) == 0) {
125 return !descr
->short_name
&& !descr
->long_name
? NULL
: descr
;
128 enum 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,
135 enum 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
)
141 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
142 const char *short_opt_ch
= short_opts
;
144 if (strlen(short_opts
) == 0) {
145 g_string_append(parse_ret
->error
, "Invalid argument");
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
;
154 /* Find corresponding option descriptor */
155 descr
= find_descr(descrs
, *short_opt_ch
, NULL
);
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
);
163 if (descr
->with_arg
) {
164 if (short_opt_ch
[1]) {
166 opt_arg
= &short_opt_ch
[1];
169 opt_arg
= next_orig_arg
;
170 *used_next_orig_arg
= true;
174 * We accept `-o ''` (empty option's argument),
175 * but not `-o` alone if an option's argument is
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`",
182 *used_next_orig_arg
= false;
187 /* Create and append option argument */
188 opt_item
= create_opt_item(descr
, opt_arg
);
193 g_ptr_array_add(parse_ret
->items
, opt_item
);
195 if (descr
->with_arg
) {
196 /* Option has an argument: no more options */
200 /* Go to next short option */
207 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
208 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
216 enum 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
)
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
;
227 /* Option's argument, if any */
228 const char *opt_arg
= NULL
;
230 /* Position of first `=`, if any */
233 /* Buffer holding option name when `long_opt_arg` contains `=` */
234 char buf
[max_len
+ 1];
237 const char *long_opt_name
= long_opt_arg
;
239 if (strlen(long_opt_arg
) == 0) {
240 g_string_append(parse_ret
->error
, "Invalid argument");
244 /* Find the first `=` in original argument */
245 eq_pos
= strchr(long_opt_arg
, '=');
247 const size_t long_opt_name_size
= eq_pos
- long_opt_arg
;
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
);
256 memcpy(buf
, long_opt_arg
, long_opt_name_size
);
257 buf
[long_opt_name_size
] = '\0';
261 /* Find corresponding option descriptor */
262 descr
= find_descr(descrs
, '\0', long_opt_name
);
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
;
270 /* Find option's argument if any */
271 if (descr
->with_arg
) {
273 /* `--long-opt=arg` style */
274 opt_arg
= eq_pos
+ 1;
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`",
284 opt_arg
= next_orig_arg
;
285 *used_next_orig_arg
= true;
289 /* Create and append option argument */
290 opt_item
= create_opt_item(descr
, opt_arg
);
295 g_ptr_array_add(parse_ret
->items
, opt_item
);
299 if (ret
== PARSE_ORIG_ARG_OPT_RET_OK
) {
300 ret
= PARSE_ORIG_ARG_OPT_RET_ERROR
;
308 enum 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
)
314 enum parse_orig_arg_opt_ret ret
= PARSE_ORIG_ARG_OPT_RET_OK
;
316 BT_ASSERT(orig_arg
[0] == '-');
318 if (orig_arg
[1] == '-') {
320 ret
= parse_long_opt(&orig_arg
[2],
321 next_orig_arg
, descrs
, parse_ret
,
325 ret
= parse_short_opts(&orig_arg
[1],
326 next_orig_arg
, descrs
, parse_ret
,
334 void prepend_while_parsing_arg_to_error(GString
* const error
,
335 const unsigned int i
, const char * const arg
)
337 /* 🙁 There's no g_string_prepend_printf()! */
338 GString
* const tmp_str
= g_string_new(NULL
);
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
);
357 struct 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
)
362 struct bt_argpar_parse_ret parse_ret
= { 0 };
364 unsigned int non_opt_index
= 0;
366 parse_ret
.error
= g_string_new(NULL
);
367 if (!parse_ret
.error
) {
371 parse_ret
.items
= g_ptr_array_new_with_free_func(
372 (GDestroyNotify
) destroy_item
);
373 if (!parse_ret
.items
) {
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
;
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
);
394 g_ptr_array_add(parse_ret
.items
, non_opt_item
);
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
:
404 case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT
:
405 BT_ASSERT(!used_next_orig_arg
);
407 if (fail_on_unknown_opt
) {
408 prepend_while_parsing_arg_to_error(
409 parse_ret
.error
, i
, orig_arg
);
414 * The current original argument is not
415 * considered ingested because it triggered an
418 parse_ret
.ingested_orig_args
= i
;
419 g_string_free(parse_ret
.error
, TRUE
);
420 parse_ret
.error
= NULL
;
422 case PARSE_ORIG_ARG_OPT_RET_ERROR
:
423 prepend_while_parsing_arg_to_error(
424 parse_ret
.error
, i
, orig_arg
);
430 if (used_next_orig_arg
) {
435 parse_ret
.ingested_orig_args
= argc
;
436 g_string_free(parse_ret
.error
, TRUE
);
437 parse_ret
.error
= NULL
;
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
;
452 void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret
*ret
)
457 g_ptr_array_free(ret
->items
, TRUE
);
462 g_string_free(ret
->error
, TRUE
);