#include "blue/core/misc.h" #include "command.h" #include #include #include #include #include #include #include #include B_BTREE_DEFINE_SIMPLE_INSERT( struct b_arglist_option, opt_node, opt_id, put_arglist_option) B_BTREE_DEFINE_SIMPLE_GET( struct b_arglist_option, unsigned int, opt_node, opt_id, get_arglist_option) B_BTREE_DEFINE_SIMPLE_INSERT( struct b_arglist_value, val_node, val_id, put_arglist_value) B_BTREE_DEFINE_SIMPLE_GET( struct b_arglist_value, unsigned int, val_node, val_id, get_arglist_value) struct argv_parser { struct b_command *cmd; struct b_arglist *arglist; b_queue_iterator arg_it; int nr_values_cur_arg; int argc; const char **argv; int index; }; static const char *peek(struct argv_parser *parser) { if (parser->index >= parser->argc) { return NULL; } return parser->argv[parser->index]; } static const char *advance(struct argv_parser *parser) { if (parser->index >= parser->argc) { return NULL; } return parser->argv[parser->index++]; } struct b_arglist *b_arglist_create(void) { struct b_arglist *out = malloc(sizeof *out); if (!out) { return NULL; } memset(out, 0x0, sizeof *out); return out; } static void move_to_subcommand(struct argv_parser *parser, struct b_command *cmd) { parser->cmd = cmd; b_queue_iterator_begin(&cmd->b_arg, &parser->arg_it); parser->nr_values_cur_arg = 0; } static b_status set_opt(struct b_arglist *args, struct b_command_option *opt) { struct b_arglist_option *arg_opt = malloc(sizeof *arg_opt); if (!arg_opt) { return B_ERR_NO_MEMORY; } memset(arg_opt, 0x0, sizeof *arg_opt); arg_opt->opt_id = opt->opt_id; put_arglist_option(&args->list_options, arg_opt); return B_SUCCESS; } static void report_invalid_arg_value( struct argv_parser *parser, struct b_command_option *opt, struct b_command_arg *arg, const char *value) { struct b_string *usage = z__b_command_default_usage_string(parser->cmd, opt); struct b_string *opt_string = b_string_create(); if (opt) { z__b_get_option_usage_string(opt, 0, opt_string); } else { z__b_get_arg_usage_string(arg, 0, opt_string); } b_err("invalid value '" F_YELLOW "%s" F_RESET "' for '" F_YELLOW "%s" F_RESET "'.", value, b_string_ptr(opt_string)); if (opt) { b_i("'" F_YELLOW "%s" F_RESET "' accepts the following values for '" F_YELLOW "%s" F_RESET "':", b_string_ptr(opt_string), arg->arg_name); } else { b_i("'" F_YELLOW "%s" F_RESET "' accepts the following values:", b_string_ptr(opt_string)); } for (int i = 0; arg->arg_allowed_values[i]; i++) { b_printf( " * " F_GREEN "%s" F_RESET "\n", arg->arg_allowed_values[i]); } b_printf("\n"); b_i("usage: %s", b_string_ptr(usage)); b_i("for more information, use '" F_YELLOW "--help" F_RESET); b_string_release(usage); b_string_release(opt_string); } static b_status put_arg( struct b_arglist *args, struct b_command_arg *arg, const char *value) { struct b_arglist_option *arglist_opt = get_arglist_option(&args->list_options, B_COMMAND_INVALID_ID); if (!arglist_opt) { arglist_opt = malloc(sizeof *arglist_opt); if (!arglist_opt) { return B_ERR_NO_MEMORY; } memset(arglist_opt, 0x0, sizeof *arglist_opt); arglist_opt->opt_id = B_COMMAND_INVALID_ID; put_arglist_option(&args->list_options, arglist_opt); } if (arg->arg_allowed_values) { bool value_ok = false; for (int i = 0; arg->arg_allowed_values[i]; i++) { if (!strcmp(value, arg->arg_allowed_values[i])) { value_ok = true; break; } } if (!value_ok) { return B_ERR_INVALID_ARGUMENT; } } struct b_arglist_value *val = malloc(sizeof *val); if (!val) { return B_ERR_NO_MEMORY; } memset(val, 0x0, sizeof *val); val->val_id = arg->arg_id; val->val_type = B_COMMAND_ARG_STRING; val->val_str = b_strdup(value); put_arglist_value(&arglist_opt->opt_values, val); return B_SUCCESS; } static b_status put_opt_arg( struct b_arglist_option *arglist_opt, struct b_command_option *opt, struct b_command_arg *arg, const char *value) { if (arg->arg_allowed_values) { bool value_ok = false; for (int i = 0; arg->arg_allowed_values[i]; i++) { if (!strcmp(value, arg->arg_allowed_values[i])) { value_ok = true; break; } } if (!value_ok) { return B_ERR_INVALID_ARGUMENT; } } struct b_arglist_value *val = malloc(sizeof *val); if (!val) { return B_ERR_NO_MEMORY; } memset(val, 0x0, sizeof *val); val->val_id = arg->arg_id; val->val_type = B_COMMAND_ARG_STRING; val->val_str = b_strdup(value); put_arglist_value(&arglist_opt->opt_values, val); return B_SUCCESS; } static void report_missing_args( struct argv_parser *parser, struct b_command_option *opt, struct b_command_arg *arg, int args_supplied) { struct b_string *usage = z__b_command_default_usage_string(parser->cmd, opt); struct b_string *opt_string = b_string_create(); if (opt) { z__b_get_option_usage_string(opt, 0, opt_string); } else { z__b_get_arg_usage_string(arg, 0, opt_string); } char supplied_arg_str[64]; if (args_supplied == 0) { snprintf( supplied_arg_str, sizeof supplied_arg_str, F_RED_BOLD "none" F_RESET " were provided"); } else if (args_supplied == 1) { snprintf( supplied_arg_str, sizeof supplied_arg_str, "only " F_YELLOW_BOLD "%d" F_RESET " was provided", args_supplied); } else { snprintf( supplied_arg_str, sizeof supplied_arg_str, "only " F_YELLOW_BOLD "%d" F_RESET " were provided", args_supplied); } char required_arg_count[64]; switch (arg->arg_nr_values) { case B_ARG_1_OR_MORE_VALUES: snprintf( required_arg_count, sizeof required_arg_count, "one or more"); break; default: snprintf( required_arg_count, sizeof required_arg_count, "%d", arg->arg_nr_values); } b_err("argument '" F_YELLOW "%s" F_RESET "' requires " F_GREEN_BOLD "%s" F_RESET " `" F_YELLOW "%s" F_RESET "` values, but %s.\n\n", b_string_ptr(opt_string), required_arg_count, arg->arg_name, supplied_arg_str); b_i("usage: %s", b_string_ptr(usage)); b_i("for more information, use '" F_YELLOW "--help" F_RESET "'"); b_string_release(usage); b_string_release(opt_string); } static void report_unexpected_arg(struct argv_parser *parser, const char *value) { struct b_string *usage = z__b_command_default_usage_string(parser->cmd, NULL); b_err("unexpected argument '" F_YELLOW "%s" F_RESET "' found.", value); b_i("usage: %s", b_string_ptr(usage)); b_i("for more information, use '" F_YELLOW "--help" F_RESET "'"); } static b_status check_required_args(struct argv_parser *parser) { if (!b_queue_iterator_is_valid(&parser->arg_it)) { return B_SUCCESS; } struct b_command_arg *arg = b_unbox(struct b_command_arg, parser->arg_it.entry, arg_entry); if (!arg) { return B_SUCCESS; } if (arg->arg_nr_values == B_ARG_0_OR_MORE_VALUES) { return B_SUCCESS; } if (arg->arg_nr_values == B_ARG_1_OR_MORE_VALUES) { if (parser->nr_values_cur_arg > 0) { return B_SUCCESS; } report_missing_args(parser, NULL, arg, parser->nr_values_cur_arg); return B_ERR_BAD_FORMAT; } if (parser->nr_values_cur_arg != arg->arg_nr_values) { report_missing_args(parser, NULL, arg, parser->nr_values_cur_arg); return B_ERR_BAD_FORMAT; } return B_SUCCESS; } static b_status parse_arg(struct argv_parser *parser) { while (1) { const char *value = peek(parser); if (!value || value[0] == '-') { break; } struct b_command *subcmd = b_command_get_subcommand_with_name(parser->cmd, value); if (subcmd) { move_to_subcommand(parser, subcmd); advance(parser); continue; } struct b_command_arg *arg = b_unbox( struct b_command_arg, parser->arg_it.entry, arg_entry); if (!arg) { report_unexpected_arg(parser, value); return B_ERR_BAD_FORMAT; } b_status status = put_arg(parser->arglist, arg, value); parser->nr_values_cur_arg++; advance(parser); if (status == B_ERR_INVALID_ARGUMENT) { report_invalid_arg_value(parser, NULL, arg, value); } if (B_ERR(status)) { return status; } if (arg->arg_nr_values == B_ARG_0_OR_1_VALUES) { b_queue_iterator_next(&parser->arg_it); parser->nr_values_cur_arg = 0; continue; } if (parser->nr_values_cur_arg == arg->arg_nr_values) { b_queue_iterator_next(&parser->arg_it); parser->nr_values_cur_arg = 0; continue; } } return B_SUCCESS; } static struct b_command_arg *get_first_arg(struct b_command *cmd) { struct b_command_arg *arg = NULL; struct b_queue_entry *first_arg = b_queue_first(&cmd->b_arg); if (first_arg) { arg = b_unbox(struct b_command_arg, first_arg, arg_entry); } return arg; } static b_status parse_short_opt(struct argv_parser *parser) { const char *flags = peek(parser); flags += 1; struct b_command_option *opt = NULL; struct b_command_arg *arg = NULL; struct b_command *subcmd = NULL; int nr_args_cur_opt = 0; while (1) { char flag = *flags; if (!flag) { break; } subcmd = NULL; opt = b_command_get_option_with_short_name(parser->cmd, flag); if (!opt) { subcmd = b_command_get_subcommand_with_short_name( parser->cmd, flag); } if (subcmd) { move_to_subcommand(parser, subcmd); flags++; continue; } if (!opt) { struct b_string *usage = z__b_command_default_usage_string( parser->cmd, NULL); b_err("unrecognised argument '" F_YELLOW "-%c" F_RESET "'\n\n", flag, b_string_ptr(usage)); b_i("usage: %s", b_string_ptr(usage)); b_i("for more information, use '" F_YELLOW "--help" F_RESET "'\n"); b_string_release(usage); return B_ERR_NO_ENTRY; } flags++; if (b_queue_empty(&opt->opt_args)) { set_opt(parser->arglist, opt); continue; } break; } if (!opt || b_queue_empty(&opt->opt_args)) { advance(parser); return B_SUCCESS; } struct b_arglist_option *arglist_opt = malloc(sizeof *arglist_opt); if (!arglist_opt) { return B_ERR_NO_MEMORY; } memset(arglist_opt, 0x0, sizeof *arglist_opt); arglist_opt->opt_id = opt->opt_id; put_arglist_option(&parser->arglist->list_options, arglist_opt); b_queue_iterator it; b_queue_iterator_begin(&opt->opt_args, &it); const char *value = flags; if (*value == '\0') { advance(parser); } while (b_queue_iterator_is_valid(&it)) { value = peek(parser); if (!value || *value == '-') { value = NULL; } arg = b_unbox(struct b_command_arg, it.entry, arg_entry); if (!arg) { b_queue_iterator_next(&it); continue; } if (!value || *value == '\0' || *value == '-') { value = NULL; } if (value) { b_status status = put_opt_arg(arglist_opt, opt, arg, value); nr_args_cur_opt++; if (status == B_ERR_INVALID_ARGUMENT) { report_invalid_arg_value(parser, NULL, arg, value); } if (B_ERR(status)) { return status; } } if (arg->arg_nr_values == B_ARG_0_OR_1_VALUES) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; goto next_value; } if (arg->arg_nr_values == B_ARG_0_OR_MORE_VALUES && !value) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; goto next_value; } if (arg->arg_nr_values == B_ARG_1_OR_MORE_VALUES && !value) { if (nr_args_cur_opt > 0) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; goto next_value; } report_missing_args(parser, opt, arg, nr_args_cur_opt); return B_ERR_BAD_FORMAT; } if (nr_args_cur_opt == arg->arg_nr_values) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; goto next_value; } if (!value) { report_missing_args(parser, opt, arg, nr_args_cur_opt); return B_ERR_BAD_FORMAT; } next_value: value = advance(parser); } return B_SUCCESS; } static b_status parse_long_opt(struct argv_parser *parser) { const char *opt_name = peek(parser); opt_name += 2; struct b_command_option *opt = NULL; struct b_command *subcmd = NULL; opt = b_command_get_option_with_long_name(parser->cmd, opt_name); if (!opt) { subcmd = b_command_get_subcommand_with_long_name( parser->cmd, opt_name); } if (!opt && !subcmd) { struct b_string *usage = z__b_command_default_usage_string(parser->cmd, NULL); b_err("unrecognised argument '" F_YELLOW "--%s" F_RESET "'\n\nusage: %s\n\nfor more information, use '" F_YELLOW "--help" F_RESET "'\n", opt_name, b_string_ptr(usage)); b_string_release(usage); return B_ERR_NO_ENTRY; } advance(parser); if (subcmd) { parser->cmd = subcmd; return B_SUCCESS; } if (b_queue_empty(&opt->opt_args)) { return set_opt(parser->arglist, opt); } int nr_args_total = 0; int nr_args_cur_opt = 0; struct b_arglist_option *arglist_opt = malloc(sizeof *arglist_opt); if (!arglist_opt) { return B_ERR_NO_MEMORY; } memset(arglist_opt, 0x0, sizeof *arglist_opt); arglist_opt->opt_id = opt->opt_id; put_arglist_option(&parser->arglist->list_options, arglist_opt); b_queue_iterator it; b_queue_iterator_begin(&opt->opt_args, &it); struct b_command_arg *arg = b_unbox(struct b_command_arg, it.entry, arg_entry); while (b_queue_iterator_is_valid(&it)) { arg = b_unbox(struct b_command_arg, it.entry, arg_entry); if (!arg) { break; } const char *value = peek(parser); if (!value || value[0] == '-') { value = NULL; } if (value) { b_status status = put_opt_arg(arglist_opt, opt, arg, value); nr_args_total++; nr_args_cur_opt++; advance(parser); if (status == B_ERR_INVALID_ARGUMENT) { report_invalid_arg_value(parser, NULL, arg, value); } if (B_ERR(status)) { return status; } } if (arg->arg_nr_values == B_ARG_0_OR_1_VALUES) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; continue; } if (arg->arg_nr_values == B_ARG_0_OR_MORE_VALUES && !value) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; continue; } if (arg->arg_nr_values == B_ARG_1_OR_MORE_VALUES && !value) { if (nr_args_cur_opt > 0) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; continue; } report_missing_args(parser, opt, arg, nr_args_cur_opt); return B_ERR_BAD_FORMAT; } if (nr_args_cur_opt == arg->arg_nr_values) { b_queue_iterator_next(&it); nr_args_cur_opt = 0; continue; } if (!value) { report_missing_args(parser, opt, arg, nr_args_cur_opt); return B_ERR_BAD_FORMAT; } } return B_SUCCESS; } static bool should_show_help(struct b_command *cmd, struct b_arglist *args) { bool show_help = false; size_t count = 0; b_arglist_iterator it; b_arglist_foreach(&it, args) { if (it.opt_id == B_COMMAND_OPTION_HELP) { show_help = true; break; } count++; } if (count == 0 && (cmd->b_flags & B_COMMAND_SHOW_HELP_BY_DEFAULT)) { show_help = true; } return show_help; } b_status b_arglist_parse( struct b_arglist *args, struct b_command **cmd, int argc, const char **argv) { struct argv_parser parser = {.arglist = args, .argc = argc - 1, .argv = argv + 1}; move_to_subcommand(&parser, *cmd); b_status status = B_SUCCESS; bool arg_only = false; while (1) { const char *arg = peek(&parser); if (!arg) { break; } size_t len = strlen(arg); status = B_SUCCESS; if (len < 2 || arg_only) { status = parse_arg(&parser); } else if (arg[0] == '-' && arg[1] != '-') { status = parse_short_opt(&parser); } else if (arg[0] == '-' && arg[1] == '-' && arg[2] == '\0') { status = B_SUCCESS; arg_only = true; advance(&parser); continue; } else if (arg[0] == '-' && arg[1] == '-' && arg[2] != '-') { status = parse_long_opt(&parser); } else { status = parse_arg(&parser); } if (B_ERR(status)) { return status; } } bool show_help = should_show_help(parser.cmd, args); if (!show_help) { status = check_required_args(&parser); } if (B_ERR(status)) { return status; } *cmd = parser.cmd; return B_SUCCESS; } static void arglist_option_destroy(struct b_arglist_option *opt) { free(opt); } static void arglist_value_destroy(struct b_arglist_value *val) { if (val->val_type == B_COMMAND_ARG_STRING) { free(val->val_str); } free(val); } void b_arglist_destroy(struct b_arglist *args) { b_btree_iterator opt_it, args_it; b_btree_iterator_begin(&args->list_options, &opt_it); while (b_btree_iterator_is_valid(&opt_it)) { struct b_arglist_option *opt = b_unbox(struct b_arglist_option, opt_it.node, opt_node); if (!opt) { b_btree_iterator_next(&opt_it); continue; } b_btree_iterator_begin(&opt->opt_values, &args_it); while (b_btree_iterator_is_valid(&args_it)) { struct b_arglist_value *val = b_unbox( struct b_arglist_value, args_it.node, val_node); if (!val) { b_btree_iterator_next(&args_it); continue; } b_btree_iterator_erase(&args_it); arglist_value_destroy(val); } b_btree_iterator_erase(&opt_it); arglist_option_destroy(opt); } free(args); } b_status b_arglist_get_string( const b_arglist *args, unsigned int opt_id, unsigned int arg_id, unsigned int index, const char **out) { b_arglist_iterator it = {0}; b_arglist_iterator_begin(args, opt_id, arg_id, &it); while (b_arglist_iterator_is_valid(&it)) { if (it.value && it.value->val_str) { *out = it.value->val_str; return B_SUCCESS; } b_arglist_iterator_next(&it); } return B_ERR_NO_ENTRY; } b_status b_arglist_get_int( const b_arglist *args, unsigned int opt_id, unsigned int arg_id, unsigned int index, long long *out) { b_arglist_iterator it = {0}; b_arglist_iterator_begin(args, opt_id, arg_id, &it); while (b_arglist_iterator_is_valid(&it)) { if (it.value) { *out = it.value->val_int; return B_SUCCESS; } b_arglist_iterator_next(&it); } return B_ERR_NO_ENTRY; } b_status b_arglist_get_uint( const b_arglist *args, unsigned int opt_id, unsigned int arg_id, unsigned int index, unsigned long long *out) { b_arglist_iterator it = {0}; b_arglist_iterator_begin(args, opt_id, arg_id, &it); while (b_arglist_iterator_is_valid(&it)) { if (it.value && it.value->val_uint) { *out = it.value->val_uint; return B_SUCCESS; } b_arglist_iterator_next(&it); } return B_ERR_NO_ENTRY; } b_status b_arglist_get_option( const b_arglist *args, unsigned int opt_id, unsigned int index, b_arglist_option **out) { b_btree_iterator it = {0}; b_btree_foreach (&it, &args->list_options) { b_arglist_option *cur = b_unbox(b_arglist_option, it.node, opt_node); if (cur->opt_id != opt_id) { continue; } if (index == 0) { *out = cur; return B_SUCCESS; } index--; } return B_ERR_NO_ENTRY; } size_t b_arglist_get_count( const b_arglist *args, unsigned int opt_id, unsigned int arg_id) { size_t count = 0; b_arglist_iterator it; b_arglist_iterator_begin(args, opt_id, arg_id, &it); while (b_arglist_iterator_is_valid(&it)) { count++; b_arglist_iterator_next(&it); } return count; } b_status b_arglist_option_get_value( const b_arglist_option *opt, unsigned int arg_id, unsigned int index, b_arglist_value **out) { b_btree_iterator it = {0}; b_btree_foreach (&it, &opt->opt_values) { b_arglist_value *cur = b_unbox(b_arglist_value, it.node, val_node); if (cur->val_id != arg_id) { continue; } if (index == 0) { *out = cur; return B_SUCCESS; } index--; } return B_ERR_NO_ENTRY; } static bool arglist_iterator_next(struct b_iterator *it) { return b_arglist_iterator_next((struct b_arglist_iterator *)it); } static bool arglist_iterator_is_valid(const struct b_iterator *it) { return b_arglist_iterator_is_valid((const struct b_arglist_iterator *)it); } static struct b_arglist_option *advance_to_next_opt(struct b_arglist_iterator *it) { struct b_arglist_option *opt; // b_btree_iterator_next(&it->_opt_it); while (b_btree_iterator_is_valid(&it->_opt_it)) { opt = b_unbox(struct b_arglist_option, it->_opt_it.node, opt_node); if (opt && (opt->opt_id == it->_opt_filter || it->_opt_filter == B_COMMAND_INVALID_ID)) { it->opt_id = opt->opt_id; return opt; } b_btree_iterator_next(&it->_opt_it); } return NULL; } static struct b_arglist_value *advance_to_next_arg(struct b_arglist_iterator *it) { struct b_arglist_value *val; // b_btree_iterator_next(&it->_arg_it); while (b_btree_iterator_is_valid(&it->_arg_it)) { val = b_unbox(struct b_arglist_value, it->_arg_it.node, val_node); if (val && (val->val_id == it->_arg_filter || it->_arg_filter == B_COMMAND_INVALID_ID)) { it->value = val; return val; } b_btree_iterator_next(&it->_arg_it); } return NULL; } static b_iterator_ops it_ops = { .it_next = arglist_iterator_next, .it_is_valid = arglist_iterator_is_valid, }; int b_arglist_iterator_begin( const struct b_arglist *args, unsigned int opt_filter, unsigned int arg_filter, struct b_arglist_iterator *it) { memset(it, 0x0, sizeof *it); it->opt_id = B_COMMAND_INVALID_ID; it->_base.it_ops = &it_ops; it->_opt_filter = opt_filter; it->_arg_filter = arg_filter; b_btree_iterator_begin(&args->list_options, &it->_opt_it); struct b_arglist_option *opt = NULL; struct b_arglist_value *val = NULL; while (1) { if (!b_btree_iterator_is_valid(&it->_opt_it)) { opt = NULL; return -1; } opt = b_unbox(struct b_arglist_option, it->_opt_it.node, opt_node); if (!opt || (opt_filter != opt->opt_id && opt_filter != B_COMMAND_INVALID_ID)) { b_btree_iterator_next(&it->_opt_it); continue; } b_btree_iterator_begin(&opt->opt_values, &it->_arg_it); bool done = false; while (1) { if (!b_btree_iterator_is_valid(&it->_arg_it)) { if (arg_filter == B_COMMAND_INVALID_ID) { done = true; } break; } val = b_unbox( struct b_arglist_value, it->_arg_it.node, val_node); if (!val || (arg_filter != val->val_id && arg_filter != B_COMMAND_INVALID_ID)) { b_btree_iterator_next(&it->_arg_it); continue; } done = true; break; } if (done) { break; } b_btree_iterator_next(&it->_opt_it); } it->opt_id = opt->opt_id; it->value = val; return 0; } bool b_arglist_iterator_next(struct b_arglist_iterator *it) { struct b_arglist_option *opt; struct b_arglist_value *val; b_btree_iterator_next(&it->_arg_it); while (1) { val = advance_to_next_arg(it); if (val) { it->value = val; it->i++; return true; } b_btree_iterator_next(&it->_opt_it); opt = advance_to_next_opt(it); if (!opt) { it->value = NULL; it->opt_id = B_COMMAND_INVALID_ID; return false; } b_btree_iterator_begin(&opt->opt_values, &it->_arg_it); if (it->_arg_filter == B_COMMAND_INVALID_ID) { return true; } } } bool b_arglist_iterator_is_valid(const struct b_arglist_iterator *it) { return it->opt_id != B_COMMAND_INVALID_ID || it->value != NULL; }