diff --git a/CMakeLists.txt b/CMakeLists.txt index 1abc72f..dfdbed8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25) project(bluelib C) -set(b_modules core object term) +set(b_modules core object term cmd) set(b_system_name ${CMAKE_SYSTEM_NAME}) string(TOLOWER ${b_system_name} b_system_name) diff --git a/cmd-test/example.c b/cmd-test/example.c new file mode 100644 index 0000000..b6df3ae --- /dev/null +++ b/cmd-test/example.c @@ -0,0 +1,175 @@ +#include +#include + +enum { + CMD_TEST, + CMD_SUB, + OPT_NAME, + OPT_NAME_VALUE, + OPT_OTHER, + OPT_OTHER_VALUE, + OPT_VERBOSE, + OPT_REALLY_VERBOSE, + OPT_MODE, + OPT_MODE_VALUE, + OPT_MODE_VALUE2, + ARG_FILE, + ARG_MORE, +}; + +const char *text + = "No one rejects, dislikes, or avoids pleasure itself, because it is " + "pleasure, but because those who do not know how to pursue pleasure " + "rationally encounter consequences that are extremely painful. Nor " + "again is there anyone who loves or pursues or desires to obtain " + "pain of itself, because it is pain, but because occasionally " + "circumstances occur in which toil and pain can procure him some " + "great pleasure."; + +static int test_command( + const b_command *self, const b_arglist *opt, const b_array *args) +{ + printf("Hello, world!\n"); + + b_arglist_iterator it; + b_arglist_iterator_begin( + opt, B_COMMAND_INVALID_ID, B_COMMAND_INVALID_ID, &it); + while (b_arglist_iterator_is_valid(&it)) { + printf("opt:%u,arg:%u,i:%zu,value: %s\n", it.opt_id, + it.value->val_id, it.i, it.value->val_str); + + b_arglist_iterator_next(&it); + } + + return 0; +} + +B_COMMAND(CMD_TEST, B_COMMAND_INVALID_ID) +{ + B_COMMAND_NAME("test"); + B_COMMAND_DESC("A test command."); + B_COMMAND_FLAGS(B_COMMAND_SHOW_HELP_BY_DEFAULT); + B_COMMAND_FUNCTION(test_command); + + B_COMMAND_OPTION(OPT_NAME) + { + B_OPTION_LONG_NAME("name"); + B_OPTION_SHORT_NAME('n'); + B_OPTION_DESC("The name of the data."); + B_OPTION_ARG(OPT_NAME_VALUE) + { + B_ARG_NAME("name"); + B_ARG_NR_VALUES(2); + } + } + + B_COMMAND_OPTION(OPT_OTHER) + { + B_OPTION_LONG_NAME("other"); + B_OPTION_DESC(text); + + B_OPTION_ARG(OPT_OTHER_VALUE) + { + B_ARG_NAME("value"); + B_ARG_NR_VALUES(B_ARG_1_OR_MORE_VALUES); + } + } + + B_COMMAND_OPTION(OPT_VERBOSE) + { + B_OPTION_LONG_NAME("verbose"); + B_OPTION_SHORT_NAME('v'); + B_OPTION_DESC("Show detailed log output."); + } + + B_COMMAND_OPTION(OPT_REALLY_VERBOSE) + { + B_OPTION_LONG_NAME("really-verbose"); + B_OPTION_SHORT_NAME('V'); + B_OPTION_DESC("Show REALLY detailed log output."); + } + + B_COMMAND_OPTION(OPT_MODE) + { + B_OPTION_SHORT_NAME('m'); + B_OPTION_LONG_NAME("mode"); + B_OPTION_DESC("modes to operate in."); + + B_OPTION_ARG(OPT_MODE_VALUE) + { + B_ARG_NAME("mode"); + B_ARG_NR_VALUES(1); + B_ARG_ALLOWED_VALUES("fast", "slow"); + } + + B_OPTION_ARG(OPT_MODE_VALUE2) + { + B_ARG_NAME("mode2"); + B_ARG_NR_VALUES(1); + B_ARG_ALLOWED_VALUES("very-fast", "very-slow"); + } + } + + B_COMMAND_ARG(ARG_FILE) + { + B_ARG_NAME("file"); + B_ARG_DESC("The file(s) to use"); + B_ARG_NR_VALUES(2); + } + + B_COMMAND_ARG(ARG_MORE) + { + B_ARG_NAME("more"); + B_ARG_DESC("More args to use"); + B_ARG_ALLOWED_VALUES("how", "wow"); + B_ARG_NR_VALUES(2); + } + + B_COMMAND_HELP_OPTION(); + + B_COMMAND_USAGE() + { + B_COMMAND_USAGE_OPT(OPT_NAME); + B_COMMAND_USAGE_ARG(ARG_FILE); + B_COMMAND_USAGE_ARG(ARG_MORE); + } +} + +B_COMMAND(CMD_SUB, CMD_TEST) +{ + B_COMMAND_NAME("sub"); + B_COMMAND_LONG_NAME("sub"); + B_COMMAND_SHORT_NAME('S'); + B_COMMAND_DESC("A test subcommand"); + B_COMMAND_FLAGS(B_COMMAND_SHOW_HELP_BY_DEFAULT); + B_COMMAND_FUNCTION(test_command); + + B_COMMAND_OPTION(OPT_NAME) + { + B_OPTION_LONG_NAME("name"); + B_OPTION_SHORT_NAME('n'); + B_OPTION_DESC("The name of the data"); + B_OPTION_ARG(OPT_NAME_VALUE) + { + B_ARG_NAME("name"); + } + } + + B_COMMAND_OPTION(OPT_OTHER) + { + B_OPTION_LONG_NAME("other"); + B_OPTION_SHORT_NAME('o'); + B_OPTION_DESC("The other argument"); + B_OPTION_ARG(OPT_OTHER_VALUE) + { + B_ARG_NAME("value"); + } + } + + B_COMMAND_HELP_OPTION(); +} + +int main(int argc, const char **argv) +{ + return b_command_dispatch(CMD_TEST, argc, argv); +} diff --git a/cmd/CMakeLists.txt b/cmd/CMakeLists.txt new file mode 100644 index 0000000..9f56738 --- /dev/null +++ b/cmd/CMakeLists.txt @@ -0,0 +1,3 @@ +include(../cmake/Templates.cmake) + +add_bluelib_module(NAME cmd DEPENDENCIES core object term) diff --git a/cmd/arg.c b/cmd/arg.c new file mode 100644 index 0000000..55c509c --- /dev/null +++ b/cmd/arg.c @@ -0,0 +1,154 @@ +#include "command.h" + +#include +#include +#include +#include + +struct b_command_arg *b_command_arg_create(void) +{ + struct b_command_arg *out = malloc(sizeof *out); + if (!out) { + return out; + } + + memset(out, 0x0, sizeof *out); + return out; +} + +b_status b_command_arg_set_name(struct b_command_arg *arg, const char *name) +{ + char *n = b_strdup(name); + if (!n) { + return B_ERR_NO_MEMORY; + } + + if (arg->arg_name) { + free(arg->arg_name); + arg->arg_name = NULL; + } + + arg->arg_name = n; + return B_SUCCESS; +} + +b_status b_command_arg_set_description( + struct b_command_arg *arg, const char *description) +{ + char *desc = b_strdup(description); + if (!desc) { + return B_ERR_NO_MEMORY; + } + + if (arg->arg_description) { + free(arg->arg_description); + arg->arg_description = NULL; + } + + arg->arg_description = desc; + return B_SUCCESS; +} + +b_status b_command_arg_set_nr_values( + struct b_command_arg *arg, enum b_command_arg_value_count nr_values) +{ + arg->arg_nr_values = nr_values; + return B_SUCCESS; +} + +b_status b_command_arg_set_allowed_values( + struct b_command_arg *arg, const char **allowed_values) +{ + size_t count; + for (count = 0; allowed_values[count]; count++) + ; + + char **values = calloc(count + 1, sizeof *values); + if (!values) { + return B_ERR_NO_MEMORY; + } + + for (size_t i = 0; i < count; i++) { + values[i] = b_strdup(allowed_values[i]); + if (!values[i]) { + /* TODO also free strings in `values` */ + free(values); + return B_ERR_NO_MEMORY; + } + } + + arg->arg_allowed_values = values; + return B_SUCCESS; +} + +void z__b_get_arg_usage_string( + struct b_command_arg *arg, bool colour, struct b_string *out) +{ + bool optional = false, multi = false; + switch (arg->arg_nr_values) { + case B_ARG_0_OR_1_VALUES: + optional = true; + multi = false; + break; + case B_ARG_0_OR_MORE_VALUES: + optional = true; + multi = true; + break; + case B_ARG_1_OR_MORE_VALUES: + optional = false; + multi = true; + break; + default: + optional = false; + multi = false; + break; + } + + if (optional) { + b_string_append_cstrf( + out, colour ? F_GREEN "[%s]" : "[%s]", arg->arg_name); + } else { + b_string_append_cstrf( + out, colour ? F_GREEN "<%s>" : "<%s>", arg->arg_name); + } + + for (int i = 1; i < arg->arg_nr_values; i++) { + b_string_append_cstrf(out, " <%s>", arg->arg_name); + } + + if (multi) { + b_string_append_cstr(out, "..."); + } + + if (colour) { + b_string_append_cstr(out, F_RESET); + } +} + +void z__b_get_arg_description(struct b_command_arg *arg, b_string *out) +{ + if (arg->arg_description) { + b_string_append_cstr(out, arg->arg_description); + } + + if (!arg->arg_allowed_values) { + return; + } + + if (arg->arg_description) { + b_string_append_cstr(out, " "); + } + + b_string_append_cstr(out, "[values:"); + + for (size_t i = 0; arg->arg_allowed_values[i]; i++) { + if (i > 0) { + b_string_append_cstr(out, ","); + } + + b_string_append_cstrf( + out, " " F_GREEN "%s" F_RESET, arg->arg_allowed_values[i]); + } + + b_string_append_cstr(out, "]"); +} diff --git a/cmd/arglist.c b/cmd/arglist.c new file mode 100644 index 0000000..cb2a0eb --- /dev/null +++ b/cmd/arglist.c @@ -0,0 +1,914 @@ +#include "command.h" + +#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 *args, struct b_command_option *opt, + struct b_command_arg *arg, const char *value) +{ + struct b_arglist_option *arglist_opt + = get_arglist_option(&args->list_options, opt->opt_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 = opt->opt_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 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; + } + + 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; + } + + advance(parser); + + if (!opt || b_queue_empty(&opt->opt_args)) { + return B_SUCCESS; + } + + b_queue_iterator it; + b_queue_iterator_begin(&opt->opt_args, &it); + const char *value = flags; + if (*value == '\0') { + value = advance(parser); + if (!value || *value == '-') { + value = NULL; + } + } + + while (b_queue_iterator_is_valid(&it)) { + 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(parser->arglist, 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; + 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; + } + + 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; + + 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(parser->arglist, 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); +} + +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; +} + +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->_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; + struct b_arglist_value *val; + + 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); + while (1) { + if (!b_btree_iterator_is_valid(&it->_arg_it)) { + val = NULL; + return -1; + } + + 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; + } + + break; + } + + break; + } + + 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; + return false; + } + + b_btree_iterator_begin(&opt->opt_values, &it->_arg_it); + } +} + +bool b_arglist_iterator_is_valid(const struct b_arglist_iterator *it) +{ + return it->value != NULL; +} diff --git a/cmd/command.c b/cmd/command.c new file mode 100644 index 0000000..11151aa --- /dev/null +++ b/cmd/command.c @@ -0,0 +1,732 @@ +#include "command.h" + +#include +#include +#include +#include +#include +#include +#include + +#define OUTPUT_STREAM stderr + +static struct b_btree command_list = {}; + +B_BTREE_DEFINE_SIMPLE_GET(struct b_command, unsigned int, b_node, b_id, get_command) +B_BTREE_DEFINE_SIMPLE_INSERT(struct b_command, b_node, b_id, put_command) + +enum item_type { + ITEM_OPTION, + ITEM_ARG, + ITEM_SUBCOMMAND, +}; + +struct b_command *b_command_create(unsigned int id) +{ + struct b_command *out = malloc(sizeof *out); + if (!out) { + return NULL; + } + + memset(out, 0x0, sizeof *out); + + out->b_id = id; + out->b_parent_id = B_COMMAND_INVALID_ID; + return out; +} + +void b_command_destroy(struct b_command *cmd) +{ +} + +b_status b_command_register(struct b_command *cmd) +{ + struct b_command *tmp = get_command(&command_list, cmd->b_id); + if (tmp) { + return B_ERR_NAME_EXISTS; + } + + put_command(&command_list, cmd); + return B_SUCCESS; +} + +b_status b_command_set_name(struct b_command *cmd, const char *name) +{ + char *n = b_strdup(name); + if (!n) { + return B_ERR_NO_MEMORY; + } + + if (cmd->b_name) { + free(cmd->b_name); + cmd->b_name = NULL; + } + + cmd->b_name = n; + return B_SUCCESS; +} + +b_status b_command_set_long_name(struct b_command *cmd, const char *name) +{ + char *n = b_strdup(name); + if (!n) { + return B_ERR_NO_MEMORY; + } + + if (cmd->b_long_name) { + free(cmd->b_long_name); + cmd->b_long_name = NULL; + } + + cmd->b_long_name = n; + return B_SUCCESS; +} + +b_status b_command_set_short_name(struct b_command *cmd, char name) +{ + cmd->b_short_name = name; + return B_SUCCESS; +} + +b_status b_command_set_flags(struct b_command *cmd, b_command_flags flags) +{ + cmd->b_flags = flags; + return B_SUCCESS; +} + +b_status b_command_set_description(struct b_command *cmd, const char *description) +{ + char *desc = b_strdup(description); + if (!desc) { + return B_ERR_NO_MEMORY; + } + + if (cmd->b_description) { + free(cmd->b_description); + cmd->b_description = NULL; + } + + cmd->b_description = desc; + return B_SUCCESS; +} + +b_status b_command_set_callback(struct b_command *cmd, b_command_callback callback) +{ + cmd->b_callback = callback; + return B_SUCCESS; +} + +b_status b_command_set_parent(b_command *cmd, unsigned int parent_id) +{ + cmd->b_parent_id = parent_id; + cmd->b_parent = NULL; + return B_SUCCESS; +} + +struct b_command_option *b_command_add_option(struct b_command *cmd, int id) +{ + struct b_command_option *opt = b_command_option_create(); + if (!opt) { + return NULL; + } + + opt->opt_id = id; + b_queue_push_back(&cmd->b_opt, &opt->opt_entry); + return opt; +} + +struct b_command_arg *b_command_add_arg(struct b_command *cmd, int id) +{ + struct b_command_arg *arg = b_command_arg_create(); + if (!arg) { + return NULL; + } + + arg->arg_id = id; + b_queue_push_back(&cmd->b_arg, &arg->arg_entry); + return arg; +} + +struct b_command_usage *b_command_add_usage(struct b_command *cmd) +{ + struct b_command_usage *usage = malloc(sizeof *usage); + if (!usage) { + return NULL; + } + + memset(usage, 0x0, sizeof *usage); + b_queue_push_back(&cmd->b_usage, &usage->u_entry); + + return usage; +} + +b_status b_command_usage_add_option( + struct b_command_usage *usage, struct b_command_option *opt) +{ + struct b_command_usage_opt *u_opt = malloc(sizeof *u_opt); + if (!u_opt) { + return B_ERR_NO_MEMORY; + } + + memset(u_opt, 0x0, sizeof *u_opt); + + u_opt->opt = opt; + + b_queue_push_back(&usage->u_opt, &u_opt->opt_entry); + return B_SUCCESS; +} + +b_status b_command_usage_add_arg( + struct b_command_usage *usage, struct b_command_arg *arg) +{ + struct b_command_usage_arg *u_arg = malloc(sizeof *u_arg); + if (!u_arg) { + return B_ERR_NO_MEMORY; + } + + memset(u_arg, 0x0, sizeof *u_arg); + + u_arg->arg = arg; + + b_queue_push_back(&usage->u_arg, &u_arg->arg_entry); + return B_SUCCESS; +} + +static void prepend_command_name(struct b_command *cmd, b_string *out) +{ + int nr_names = 0; + cmd->b_name &&nr_names++; + cmd->b_long_name &&nr_names++; + cmd->b_short_name &&nr_names++; + + if (nr_names > 1) { + b_string_prepend_cstr(out, "}"); + int r = 0; + + if (cmd->b_name) { + b_string_prepend_cstrf(out, "%s", cmd->b_name); + r++; + } + + if (r == 1) { + b_string_prepend_cstr(out, "|"); + } + + if (cmd->b_long_name) { + b_string_prepend_cstrf(out, "--%s", cmd->b_long_name); + r++; + } + + if (r == 2) { + b_string_prepend_cstr(out, "|"); + } + + if (cmd->b_short_name) { + b_string_prepend_cstrf(out, "-%c", cmd->b_short_name); + r++; + } + + b_string_prepend_cstr(out, "{"); + } else { + b_string_prepend_cstr(out, cmd->b_name); + } +} + +static void get_qualified_command_name(struct b_command *cmd, b_string *out) +{ + prepend_command_name(cmd, out); + cmd = cmd->b_parent; + + while (cmd) { + b_string_prepend_cstr(out, " "); + prepend_command_name(cmd, out); + cmd = cmd->b_parent; + } +} + +static void get_usage_string( + struct b_command *cmd, struct b_command_usage *usage, b_string *out) +{ + get_qualified_command_name(cmd, out); + + b_queue_iterator it; + b_queue_foreach (&it, &usage->u_opt) { + struct b_command_usage_opt *opt = b_unbox( + struct b_command_usage_opt, it.entry, opt_entry); + + if (!opt) { + break; + } + + b_string_append_cstr(out, " "); + z__b_get_option_usage_string(opt->opt, CMD_STR_DIRECT_USAGE, out); + } + + b_queue_foreach (&it, &usage->u_arg) { + struct b_command_usage_arg *arg = b_unbox( + struct b_command_usage_arg, it.entry, arg_entry); + + if (!arg) { + break; + } + + b_string_append_cstr(out, " "); + z__b_get_arg_usage_string(arg->arg, false, out); + } +} + +b_string *z__b_command_default_usage_string( + struct b_command *cmd, struct b_command_option *with_opt) +{ + b_string *str = b_string_create(); + get_qualified_command_name(cmd, str); + + if (with_opt) { + b_string_append_cstr(str, " "); + z__b_get_option_usage_string(with_opt, CMD_STR_DIRECT_USAGE, str); + } else if (!b_queue_empty(&cmd->b_opt)) { + b_string_append_cstr(str, " [OPTIONS]"); + } + + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_arg) { + struct b_command_arg *arg + = b_unbox(struct b_command_arg, it.entry, arg_entry); + + b_string_append_cstr(str, " "); + z__b_get_arg_usage_string(arg, false, str); + } + + if (!b_queue_empty(&cmd->b_subcommands)) { + b_string_append_cstr(str, " [COMMAND]"); + } + + return str; +} + +static void get_command_string(struct b_command *cmd, b_string *out) +{ + int r = 0; + + if (cmd->b_name) { + b_string_append_cstrf(out, F_GREEN "%s" F_RESET, cmd->b_name); + r++; + } + + if (r == 1) { + b_string_append_cstr(out, ", "); + } + + if (cmd->b_short_name) { + b_string_append_cstrf(out, F_GREEN "-%c" F_RESET, cmd->b_short_name); + r++; + } + + if (r == 2) { + b_string_append_cstr(out, ", "); + } + + if (cmd->b_long_name) { + b_string_append_cstrf(out, F_GREEN "--%s" F_RESET, cmd->b_long_name); + r++; + } +} + +static void get_command_description(struct b_command *cmd, b_string *out) +{ + b_string_append_cstr(out, cmd->b_description); +} + +static void print_options_list(struct b_command *cmd) +{ + b_fprintf(OUTPUT_STREAM, "\n" F_YELLOW "OPTIONS:" F_RESET "\n"); + + size_t desb_margin = 0; + b_string *str = b_string_create(); + + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_opt) { + struct b_command_option *opt + = b_unbox(struct b_command_option, it.entry, opt_entry); + if (!opt) { + continue; + } + + b_string_clear(str); + z__b_get_option_usage_string(opt, CMD_STR_COLOUR, str); + size_t len = b_string_get_size(str, B_STRLEN_IGNORE_ESC) + 4; + + if (len > desb_margin) { + desb_margin = len; + } + } + + b_paragraph_format format = {}; + format.p_flags = B_PARAGRAPH_DONT_INDENT_FIRST_LINE; + format.p_left_margin = desb_margin + 4; + format.p_right_margin = 4; + + size_t i = 0; + b_queue_foreach (&it, &cmd->b_opt) { + struct b_command_option *opt + = b_unbox(struct b_command_option, it.entry, opt_entry); + if (!opt) { + continue; + } + + b_string_clear(str); + z__b_get_option_usage_string(opt, CMD_STR_COLOUR, str); + + fputs(" ", OUTPUT_STREAM); + b_fputs(b_string_ptr(str), OUTPUT_STREAM); + unsigned int len = b_string_get_size(str, B_STRLEN_IGNORE_ESC) + 4; + while (len < format.p_left_margin) { + fputc(' ', OUTPUT_STREAM); + len++; + } + + b_string_clear(str); + z__b_get_option_description(opt, str); + + b_print_paragraph(b_string_ptr(str), OUTPUT_STREAM, &format); + } + + b_string_release(str); +} + +static void print_args_list(struct b_command *cmd) +{ + b_fprintf(OUTPUT_STREAM, "\n" F_YELLOW "ARGS:" F_RESET "\n"); + + size_t desb_margin = 0; + b_string *str = b_string_create(); + + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_arg) { + struct b_command_arg *arg + = b_unbox(struct b_command_arg, it.entry, arg_entry); + + b_string_clear(str); + z__b_get_arg_usage_string(arg, true, str); + size_t len = b_string_get_size(str, B_STRLEN_IGNORE_ESC) + 4; + + if (len > desb_margin) { + desb_margin = len; + } + } + + b_paragraph_format format = {}; + format.p_flags = B_PARAGRAPH_DONT_INDENT_FIRST_LINE; + format.p_left_margin = desb_margin + 4; + format.p_right_margin = 4; + + size_t i = 0; + b_queue_foreach (&it, &cmd->b_arg) { + struct b_command_arg *arg + = b_unbox(struct b_command_arg, it.entry, arg_entry); + + b_string_clear(str); + z__b_get_arg_usage_string(arg, true, str); + + fputs(" ", OUTPUT_STREAM); + b_fputs(b_string_ptr(str), OUTPUT_STREAM); + unsigned int len = b_string_get_size(str, B_STRLEN_IGNORE_ESC) + 4; + while (len < format.p_left_margin) { + fputc(' ', OUTPUT_STREAM); + len++; + } + + b_string_clear(str); + z__b_get_arg_description(arg, str); + + b_print_paragraph(b_string_ptr(str), OUTPUT_STREAM, &format); + } + + b_string_release(str); +} + +static void print_commands_list(struct b_command *cmd) +{ + b_fprintf(OUTPUT_STREAM, "\n" F_YELLOW "COMMANDS:" F_RESET "\n"); + + size_t desb_margin = 0; + b_string *str = b_string_create(); + + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_subcommands) { + struct b_command *sub + = b_unbox(struct b_command, it.entry, b_entry); + + b_string_clear(str); + get_command_string(sub, str); + size_t len = b_string_get_size(str, B_STRLEN_IGNORE_ESC) + 4; + + if (len > desb_margin) { + desb_margin = len; + } + } + + b_paragraph_format format = {}; + format.p_flags = B_PARAGRAPH_DONT_INDENT_FIRST_LINE; + format.p_left_margin = desb_margin + 4; + format.p_right_margin = 4; + + size_t i = 0; + b_queue_foreach (&it, &cmd->b_subcommands) { + struct b_command *sub + = b_unbox(struct b_command, it.entry, b_entry); + + b_string_clear(str); + get_command_string(sub, str); + + fputs(" ", OUTPUT_STREAM); + b_fputs(b_string_ptr(str), OUTPUT_STREAM); + unsigned int len = b_string_get_size(str, B_STRLEN_IGNORE_ESC) + 4; + while (len < format.p_left_margin) { + fputc(' ', OUTPUT_STREAM); + len++; + } + + b_string_clear(str); + get_command_description(sub, str); + + b_print_paragraph(b_string_ptr(str), OUTPUT_STREAM, &format); + } + + b_string_release(str); +} + +struct b_command *b_command_get_subcommand_with_name( + struct b_command *cmd, const char *name) +{ + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_subcommands) { + struct b_command *subcmd + = b_unbox(struct b_command, it.entry, b_entry); + if (!subcmd || !subcmd->b_name) { + continue; + } + + if (!strcmp(subcmd->b_name, name)) { + return subcmd; + } + } + + return NULL; +} + +struct b_command *b_command_get_subcommand_with_long_name( + struct b_command *cmd, const char *long_name) +{ + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_subcommands) { + struct b_command *subcmd + = b_unbox(struct b_command, it.entry, b_entry); + if (!subcmd || !subcmd->b_long_name) { + continue; + } + + if (!strcmp(subcmd->b_name, long_name)) { + return subcmd; + } + } + + return NULL; +} + +struct b_command *b_command_get_subcommand_with_short_name( + struct b_command *cmd, char short_name) +{ + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_subcommands) { + struct b_command *subcmd + = b_unbox(struct b_command, it.entry, b_entry); + if (!subcmd || !subcmd->b_short_name) { + continue; + } + + if (subcmd->b_short_name == short_name) { + return subcmd; + } + } + + return NULL; +} + +struct b_command_option *b_command_get_option_with_long_name( + struct b_command *cmd, const char *long_name) +{ + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_opt) { + struct b_command_option *opt + = b_unbox(struct b_command_option, it.entry, opt_entry); + if (!opt || !opt->opt_long_name) { + continue; + } + + if (!strcmp(opt->opt_long_name, long_name)) { + return opt; + } + } + + return NULL; +} + +struct b_command_option *b_command_get_option_with_short_name( + struct b_command *cmd, char short_name) +{ + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_opt) { + struct b_command_option *opt + = b_unbox(struct b_command_option, it.entry, opt_entry); + if (!opt || !opt->opt_long_name) { + continue; + } + + if (opt->opt_short_name == short_name) { + return opt; + } + } + + return NULL; +} + +static void print_usage(struct b_command *cmd) +{ + b_paragraph_format format = {}; + format.p_left_margin = format.p_right_margin = 4; + + b_fprintf(OUTPUT_STREAM, F_YELLOW "USAGE:" F_RESET "\n"); + + if (b_queue_empty(&cmd->b_usage)) { + b_string *usage = z__b_command_default_usage_string(cmd, NULL); + b_print_paragraph(b_string_ptr(usage), OUTPUT_STREAM, &format); + b_string_release(usage); + return; + } + + b_string *str = b_string_create(); + b_queue_iterator it; + b_queue_foreach (&it, &cmd->b_usage) { + struct b_command_usage *usage + = b_unbox(struct b_command_usage, it.entry, u_entry); + if (!usage) { + break; + } + + b_string_clear(str); + get_usage_string(cmd, usage, str); + b_print_paragraph(b_string_ptr(str), OUTPUT_STREAM, &format); + } + + b_string_release(str); +} + +static void print_help(struct b_command *cmd) +{ + b_paragraph_format format = {}; + + if (!cmd->b_parent) { + b_fprintf(OUTPUT_STREAM, F_GREEN "%s" F_RESET "\n", cmd->b_name); + } + + if (cmd->b_description) { + b_print_paragraph(cmd->b_description, OUTPUT_STREAM, &format); + } + + b_fprintf(OUTPUT_STREAM, "\n"); + + print_usage(cmd); + + if (!b_queue_empty(&cmd->b_opt)) { + print_options_list(cmd); + } + + if (!b_queue_empty(&cmd->b_arg)) { + print_args_list(cmd); + } + + if (!b_queue_empty(&cmd->b_subcommands)) { + print_commands_list(cmd); + } +} + +static int execute_command(struct b_command *cmd, struct b_arglist *args) +{ + if (!cmd) { + return -1; + } + + if (cmd->b_flags & B_COMMAND_SHOW_HELP_BY_DEFAULT + && b_arglist_get_count(args, B_COMMAND_INVALID_ID, B_COMMAND_INVALID_ID) + == 0) { + print_help(cmd); + return 0; + } + + if (b_arglist_get_count(args, B_COMMAND_OPTION_HELP, B_COMMAND_INVALID_ID) + > 0) { + print_help(cmd); + return 0; + } + + if (cmd->b_callback) { + return cmd->b_callback(cmd, args, NULL); + } + + return -1; +} + +static b_status add_subcommand(struct b_command *parent, struct b_command *child) +{ + b_queue_push_back(&parent->b_subcommands, &child->b_entry); + child->b_parent = parent; + + return B_SUCCESS; +} + +static int resolve_command_parents(struct b_btree *commands) +{ + struct b_btree_iterator it; + b_btree_foreach (&it, commands) { + struct b_command *cmd = b_unbox(struct b_command, it.node, b_node); + + if (cmd->b_parent_id == B_COMMAND_INVALID_ID) { + continue; + } + + cmd->b_parent = get_command(commands, cmd->b_parent_id); + if (!cmd->b_parent) { + return -1; + } + + add_subcommand(cmd->b_parent, cmd); + } + + return 0; +} + +int b_command_dispatch(unsigned int cmd_id, int argc, const char **argv) +{ + if (resolve_command_parents(&command_list) != 0) { + return -1; + } + + struct b_command *cmd = get_command(&command_list, cmd_id); + if (!cmd) { + return -1; + } + + struct b_arglist *args = b_arglist_create(); + b_status status = b_arglist_parse(args, &cmd, argc, argv); + if (B_ERR(status)) { + b_arglist_destroy(args); + return -1; + } + + int ret = execute_command(cmd, args); + + b_arglist_destroy(args); + // print_help(cmd); + return ret; +} diff --git a/cmd/command.h b/cmd/command.h new file mode 100644 index 0000000..c697d14 --- /dev/null +++ b/cmd/command.h @@ -0,0 +1,131 @@ +#ifndef _B_COMMAND_H_ +#define _B_COMMAND_H_ + +#include +#include +#include + +#define F_RED "\033[91m" +#define F_GREEN "\033[92m" +#define F_YELLOW "\033[93m" + +#define F_RED_BOLD "\033[1;91m" +#define F_GREEN_BOLD "\033[1;92m" +#define F_YELLOW_BOLD "\033[1;93m" +#define F_RESET "\033[0m" + +enum cmd_string_flags { + CMD_STR_COLOUR = 0x01u, + CMD_STR_DIRECT_USAGE = 0x02u, +}; + +struct b_string; + +struct b_command { + unsigned int b_id; + unsigned int b_parent_id; + struct b_command *b_parent; + enum b_command_flags b_flags; + + char *b_name; + char *b_long_name; + char b_short_name; + char *b_description; + struct b_queue b_opt; + struct b_queue b_arg; + struct b_queue b_subcommands; + struct b_queue b_usage; + + b_command_callback b_callback; + struct b_queue_entry b_entry; + struct b_btree_node b_node; +}; + +struct b_command_usage_opt { + struct b_queue_entry opt_entry; + struct b_command_option *opt; +}; + +struct b_command_usage_arg { + struct b_queue_entry arg_entry; + struct b_command_arg *arg; +}; + +struct b_command_usage { + b_command_usage_flags u_flags; + struct b_queue_entry u_entry; + + struct b_queue u_opt; + struct b_queue u_arg; +}; + +struct b_command_option { + unsigned int opt_id; + + char *opt_long_name; + char opt_short_name; + char *opt_description; + + // b_command_arg_value_count arg_nr_values; + // char **arg_allowed_values; + + struct b_queue opt_args; + struct b_queue_entry opt_entry; +}; + +struct b_arglist_option { + unsigned int opt_id; + struct b_btree opt_values; + struct b_btree_node opt_node; +}; + +struct b_arglist { + struct b_btree list_options; +}; + +struct b_command_arg { + unsigned int arg_id; + char *arg_name; + char *arg_description; + + b_command_arg_value_count arg_nr_values; + char **arg_allowed_values; + + struct b_queue_entry arg_entry; +}; + +extern struct b_command *b_command_get_subcommand_with_name( + struct b_command *cmd, const char *name); +extern struct b_command *b_command_get_subcommand_with_long_name( + struct b_command *cmd, const char *long_name); +extern struct b_command *b_command_get_subcommand_with_short_name( + struct b_command *cmd, char short_name); + +extern struct b_command_option *b_command_get_option_with_long_name( + struct b_command *cmd, const char *long_name); +extern struct b_command_option *b_command_get_option_with_short_name( + struct b_command *cmd, char short_name); + +extern struct b_command_option *b_command_option_create(void); +extern struct b_command_arg *b_command_arg_create(void); +extern struct b_arglist *b_arglist_create(void); +extern b_status b_arglist_parse( + struct b_arglist *args, struct b_command **cmd, int argc, + const char **argv); +extern void b_arglist_destroy(struct b_arglist *args); + +extern struct b_string *z__b_command_default_usage_string( + struct b_command *cmd, struct b_command_option *with_opt); + +extern void z__b_get_arg_usage_string( + struct b_command_arg *arg, bool colour, struct b_string *out); +extern void z__b_get_arg_description( + struct b_command_arg *arg, struct b_string *out); + +extern void z__b_get_option_usage_string( + struct b_command_option *opt, enum cmd_string_flags flags, + struct b_string *out); +extern void z__b_get_option_description( + struct b_command_option *opt, struct b_string *out); + +#endif diff --git a/cmd/include/blue/cmd.h b/cmd/include/blue/cmd.h new file mode 100644 index 0000000..4831be6 --- /dev/null +++ b/cmd/include/blue/cmd.h @@ -0,0 +1,214 @@ +#ifndef BLUELIB_COMMAND_H_ +#define BLUELIB_COMMAND_H_ + +#include +#include +#include +#include +#include +#include + +#define b_arglist_foreach(it, q) \ + for (int z__b_unique_name() = b_arglist_iterator_begin( \ + q, B_COMMAND_INVALID_ID, B_COMMAND_INVALID_ID, (it)); \ + b_arglist_iterator_is_valid(it); b_arglist_iterator_next(it)) + +#define b_arglist_foreach_filtered(it, q, opt_id, arg_id) \ + for (int z__b_unique_name() \ + = b_arglist_iterator_begin(q, opt_id, arg_id, (it)); \ + b_arglist_iterator_is_valid(it); b_arglist_iterator_next(it)) + +#define B_COMMAND(id, parent_id) \ + static b_command *command_##id = NULL; \ + static void __init_##id( \ + b_command *, b_command_option *, b_command_arg *, \ + b_command_usage *); \ + B_INIT(init_##id) \ + { \ + command_##id = b_command_create(id); \ + if ((parent_id) != B_COMMAND_INVALID_ID) { \ + b_command_set_parent(command_##id, parent_id); \ + } \ + __init_##id(command_##id, NULL, NULL, NULL); \ + b_command_register(command_##id); \ + } \ + static void __init_##id( \ + b_command *this_cmd, b_command_option *this_opt, \ + b_command_arg *this_arg, b_command_usage *this_usage) + +#define B_COMMAND_NAME(name) b_command_set_name(this_cmd, (name)) +#define B_COMMAND_LONG_NAME(name) b_command_set_long_name(this_cmd, (name)) +#define B_COMMAND_SHORT_NAME(name) b_command_set_short_name(this_cmd, (name)) +#define B_COMMAND_DESC(desc) b_command_set_description(this_cmd, (desc)) +#define B_COMMAND_FLAGS(flags) b_command_set_flags(this_cmd, (flags)) +#define B_COMMAND_FUNCTION(fn) b_command_set_callback(this_cmd, (fn)) + +#define B_COMMAND_OPTION(id) \ + b_command_option *opt_##id = b_command_add_option(this_cmd, (id)); \ + this_opt = opt_##id; \ + if (this_opt) + +#define B_COMMAND_HELP_OPTION() \ + do { \ + b_command_option *opt \ + = b_command_add_option(this_cmd, B_COMMAND_OPTION_HELP); \ + b_command_option_set_description(opt, "Show this help message"); \ + b_command_option_set_short_name(opt, 'h'); \ + b_command_option_set_long_name(opt, "help"); \ + } while (0) + +#define B_OPTION_LONG_NAME(name) \ + b_command_option_set_long_name(this_opt, (name)) + +#define B_OPTION_SHORT_NAME(name) \ + b_command_option_set_short_name(this_opt, (name)) + +#define B_OPTION_DESC(desc) b_command_option_set_description(this_opt, (desc)) + +#define B_OPTION_ARG(id) \ + b_command_arg *arg_##id = b_command_option_add_arg(this_opt, (id)); \ + this_arg = arg_##id; \ + if (this_arg) + +#define B_COMMAND_ARG(id) \ + b_command_arg *arg_##id = b_command_add_arg(this_cmd, (id)); \ + this_arg = arg_##id; \ + if (this_arg) + +#define B_ARG_NAME(name) b_command_arg_set_name(this_arg, (name)) + +#define B_ARG_DESC(desc) b_command_arg_set_description(this_arg, (desc)) + +#define B_ARG_NR_VALUES(nr_values) \ + b_command_arg_set_nr_values(this_arg, (nr_values)) + +#define B_ARG_ALLOWED_VALUES(...) \ + static const char *allowed_values[] = { \ + __VA_ARGS__, \ + NULL, \ + }; \ + b_command_arg_set_allowed_values(this_arg, allowed_values) + +#define B_COMMAND_USAGE() \ + b_command_usage *usage_##__LINE__ = b_command_add_usage(this_cmd); \ + this_usage = usage_##__LINE__; \ + if (this_usage) + +#define B_COMMAND_USAGE_OPT(opt_id) \ + b_command_usage_add_option(this_usage, opt_##opt_id) + +#define B_COMMAND_USAGE_ARG(opt_id) \ + b_command_usage_add_arg(this_usage, arg_##opt_id) + +#define B_COMMAND_OPTION_HELP ((unsigned int)0xF0000001) +#define B_COMMAND_INVALID_ID ((unsigned int)0xFFFFFFFF) + +typedef enum b_command_arg_value_count { + B_ARG_0_OR_1_VALUES = -1, + B_ARG_0_OR_MORE_VALUES = -2, + B_ARG_1_OR_MORE_VALUES = -3, +} b_command_arg_value_count; + +typedef enum b_command_arg_type { + B_COMMAND_ARG_NONE = 0, + B_COMMAND_ARG_STRING, + B_COMMAND_ARG_SIGNED_INT, + B_COMMAND_ARG_UNSIGNED_INT, + + B_COMMAND_ARG_INT = B_COMMAND_ARG_SIGNED_INT, +} b_command_arg_type; + +typedef enum b_command_flags { + B_COMMAND_SHOW_HELP_BY_DEFAULT = 0x01u, +} b_command_flags; + +typedef enum b_command_usage_flags { + B_COMMAND_USAGE_SHOW_OPTIONS = 0x01u, +} b_command_usage_flags; + +typedef struct b_arglist_value { + unsigned int val_id; + b_command_arg_type val_type; + struct b_btree_node val_node; + + union { + char *val_str; + long long val_int; + unsigned long long val_uint; + }; +} b_arglist_value; + +typedef struct b_arglist_iterator { + b_iterator _base; + size_t i; + + unsigned int opt_id; + struct b_arglist_value *value; + + b_btree_iterator _opt_it, _arg_it; + unsigned int _opt_filter, _arg_filter; +} b_arglist_iterator; + +typedef struct b_command b_command; +typedef struct b_command_option b_command_option; +typedef struct b_command_arg b_command_arg; +typedef struct b_command_usage b_command_usage; +typedef struct b_arglist b_arglist; + +typedef int (*b_command_callback)( + const b_command *, const b_arglist *, const b_array *); + +extern b_command *b_command_create(unsigned int id); +extern void b_command_destroy(b_command *cmd); +extern b_status b_command_register(b_command *cmd); +extern int b_command_dispatch(unsigned int cmd_id, int argc, const char **argv); + +extern b_status b_command_set_name(b_command *cmd, const char *name); +extern b_status b_command_set_long_name(b_command *cmd, const char *name); +extern b_status b_command_set_short_name(b_command *cmd, char name); +extern b_status b_command_set_flags(b_command *cmd, b_command_flags flags); +extern b_status b_command_set_description(b_command *cmd, const char *description); +extern b_status b_command_set_callback(b_command *cmd, b_command_callback callback); +extern b_status b_command_set_parent(b_command *cmd, unsigned int parent_id); +extern b_command_option *b_command_add_option(b_command *cmd, int id); +extern b_command_arg *b_command_add_arg(b_command *cmd, int id); +extern b_command_usage *b_command_add_usage(b_command *cmd); + +extern b_status b_command_option_set_long_name( + b_command_option *opt, const char *name); +extern b_status b_command_option_set_short_name(b_command_option *opt, char name); +extern b_status b_command_option_set_description( + b_command_option *opt, const char *description); +extern b_command_arg *b_command_option_add_arg(b_command_option *opt, int id); + +extern b_status b_command_arg_set_name(b_command_arg *arg, const char *name); +extern b_status b_command_arg_set_description( + b_command_arg *arg, const char *description); +extern b_status b_command_arg_set_nr_values( + b_command_arg *arg, b_command_arg_value_count nr_values); +extern b_status b_command_arg_set_allowed_values( + b_command_arg *arg, const char **allowed_values); + +extern b_status b_command_usage_add_option( + b_command_usage *usage, b_command_option *opt); +extern b_status b_command_usage_add_arg(b_command_usage *usage, b_command_arg *opt); + +extern b_status b_arglist_get_string( + const b_arglist *args, unsigned int opt_id, unsigned int arg_id, + unsigned int index, const char **out); +extern b_status b_arglist_get_int( + const b_arglist *args, unsigned int opt_id, unsigned int arg_id, + unsigned int index, long long *out); +extern 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); +extern size_t b_arglist_get_count( + const b_arglist *args, unsigned int opt_id, unsigned int arg_id); + +extern int b_arglist_iterator_begin( + const b_arglist *args, unsigned int opt_filter, unsigned int arg_filter, + b_arglist_iterator *it); +extern bool b_arglist_iterator_next(b_arglist_iterator *it); +extern bool b_arglist_iterator_is_valid(const b_arglist_iterator *it); + +#endif diff --git a/cmd/option.c b/cmd/option.c new file mode 100644 index 0000000..7133f3b --- /dev/null +++ b/cmd/option.c @@ -0,0 +1,206 @@ +#include "command.h" + +#include +#include +#include +#include + +struct b_command_option *b_command_option_create(void) +{ + struct b_command_option *out = malloc(sizeof *out); + if (!out) { + return out; + } + + memset(out, 0x0, sizeof *out); + return out; +} + +b_status b_command_option_set_long_name( + struct b_command_option *opt, const char *name) +{ + char *n = b_strdup(name); + if (!n) { + return B_ERR_NO_MEMORY; + } + + if (opt->opt_long_name) { + free(opt->opt_long_name); + opt->opt_long_name = NULL; + } + + opt->opt_long_name = n; + return B_SUCCESS; +} + +b_status b_command_option_set_short_name(struct b_command_option *opt, char name) +{ + opt->opt_short_name = name; + return B_SUCCESS; +} + +b_status b_command_option_set_description( + struct b_command_option *opt, const char *description) +{ + char *desc = b_strdup(description); + if (!desc) { + return B_ERR_NO_MEMORY; + } + + if (opt->opt_description) { + free(opt->opt_description); + opt->opt_description = NULL; + } + + opt->opt_description = desc; + return B_SUCCESS; +} + +struct b_command_arg *b_command_option_add_arg(struct b_command_option *opt, int id) +{ + struct b_command_arg *arg = malloc(sizeof *arg); + if (!arg) { + return NULL; + } + + memset(arg, 0x0, sizeof *arg); + + arg->arg_id = id; + + b_queue_push_back(&opt->opt_args, &arg->arg_entry); + return arg; +} + +void z__b_get_option_description(struct b_command_option *opt, b_string *out) +{ + if (opt->opt_description) { + b_string_append_cstr(out, opt->opt_description); + } + + size_t nr_args = b_queue_length(&opt->opt_args); + bool close_bracket = false; + + b_queue_iterator it; + b_queue_foreach (&it, &opt->opt_args) { + struct b_command_arg *arg + = b_unbox(struct b_command_arg, it.entry, arg_entry); + if (!arg || !arg->arg_allowed_values) { + continue; + } + + if (it.i > 0) { + b_string_append_cstr(out, "; "); + } else { + b_string_append_cstr(out, " ["); + close_bracket = true; + } + + if (nr_args > 1) { + b_string_append_cstrf( + out, "values for `%s`:", arg->arg_name); + } else { + b_string_append_cstr(out, "values:"); + } + + for (size_t i = 0; arg->arg_allowed_values[i]; i++) { + if (i > 0) { + b_string_append_cstr(out, ","); + } + + b_string_append_cstrf( + out, " " F_GREEN "%s" F_RESET, + arg->arg_allowed_values[i]); + } + } + + if (close_bracket) { + b_string_append_cstr(out, "]"); + } +} + +void z__b_get_option_usage_string( + struct b_command_option *opt, enum cmd_string_flags flags, + struct b_string *out) +{ + if (flags & CMD_STR_DIRECT_USAGE) { + b_string_append_cstr(out, "{"); + } + + if (opt->opt_short_name) { + b_string_append_cstrf( + out, + (flags & CMD_STR_COLOUR) ? F_GREEN "-%c" F_RESET : "-%c", + opt->opt_short_name); + } + + if (opt->opt_short_name && opt->opt_long_name) { + b_string_append_cstr( + out, (flags & CMD_STR_DIRECT_USAGE) ? "|" : ", "); + } + + if (opt->opt_long_name) { + b_string_append_cstrf( + out, + (flags & CMD_STR_COLOUR) ? F_GREEN "--%s" F_RESET : "--%s", + opt->opt_long_name); + } + + if (flags & CMD_STR_DIRECT_USAGE) { + b_string_append_cstr(out, "}"); + } + + b_queue_iterator it; + b_queue_foreach (&it, &opt->opt_args) { + struct b_command_arg *arg + = b_unbox(struct b_command_arg, it.entry, arg_entry); + if (!arg) { + continue; + } + + bool optional = false, multi = false; + switch (arg->arg_nr_values) { + case B_ARG_0_OR_1_VALUES: + optional = true; + multi = false; + break; + case B_ARG_0_OR_MORE_VALUES: + optional = true; + multi = true; + break; + case B_ARG_1_OR_MORE_VALUES: + optional = false; + multi = true; + break; + default: + optional = false; + multi = false; + break; + } + + if (optional) { + b_string_append_cstrf( + out, + (flags & CMD_STR_COLOUR) ? " " F_GREEN "[%s]" + : " [%s]", + arg->arg_name); + } else { + b_string_append_cstrf( + out, + (flags & CMD_STR_COLOUR) ? " " F_GREEN "<%s>" + : " <%s>", + arg->arg_name); + } + + for (int i = 1; i < arg->arg_nr_values; i++) { + b_string_append_cstrf(out, " <%s>", arg->arg_name); + } + + if (multi) { + b_string_append_cstr(out, "..."); + } + + if (flags & CMD_STR_COLOUR) { + b_string_append_cstr(out, F_RESET); + } + } +}