add cmd module from corelib

This commit is contained in:
2024-10-24 21:32:28 +01:00
parent 901a60206f
commit d0dcee9c6f
9 changed files with 2530 additions and 1 deletions

View File

@@ -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)

175
cmd-test/example.c Normal file
View File

@@ -0,0 +1,175 @@
#include <blue/cmd.h>
#include <stdio.h>
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);
}

3
cmd/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
include(../cmake/Templates.cmake)
add_bluelib_module(NAME cmd DEPENDENCIES core object term)

154
cmd/arg.c Normal file
View File

@@ -0,0 +1,154 @@
#include "command.h"
#include <blue/cmd.h>
#include <blue/object/string.h>
#include <stdlib.h>
#include <string.h>
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, "]");
}

914
cmd/arglist.c Normal file
View File

@@ -0,0 +1,914 @@
#include "command.h"
#include <blue/cmd.h>
#include <blue/object/string.h>
#include <blue/term.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
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;
}

732
cmd/command.c Normal file
View File

@@ -0,0 +1,732 @@
#include "command.h"
#include <blue/cmd.h>
#include <blue/object/string.h>
#include <blue/term.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

131
cmd/command.h Normal file
View File

@@ -0,0 +1,131 @@
#ifndef _B_COMMAND_H_
#define _B_COMMAND_H_
#include <blue/cmd.h>
#include <blue/core/btree.h>
#include <blue/core/queue.h>
#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

214
cmd/include/blue/cmd.h Normal file
View File

@@ -0,0 +1,214 @@
#ifndef BLUELIB_COMMAND_H_
#define BLUELIB_COMMAND_H_
#include <blue/core/btree.h>
#include <blue/core/init.h>
#include <blue/core/iterator.h>
#include <blue/core/queue.h>
#include <blue/object/array.h>
#include <stdbool.h>
#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

206
cmd/option.c Normal file
View File

@@ -0,0 +1,206 @@
#include "command.h"
#include <blue/cmd.h>
#include <blue/object/string.h>
#include <stdlib.h>
#include <string.h>
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);
}
}
}