aboutsummaryrefslogtreecommitdiff
path: root/cli/cli.h
diff options
context:
space:
mode:
Diffstat (limited to 'cli/cli.h')
-rw-r--r--cli/cli.h251
1 files changed, 163 insertions, 88 deletions
diff --git a/cli/cli.h b/cli/cli.h
index f5ce8f3..07f0ab4 100644
--- a/cli/cli.h
+++ b/cli/cli.h
@@ -13,6 +13,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <inttypes.h>
+
+#include "../utils.h"
/**
* enum cli_rc - Return codes for CLI parsing results.
@@ -40,24 +43,21 @@ enum cli_rc {
* @argv: the array of command line arguments passed on to the command.
*/
struct cli_ctx {
- const struct cli_opt *opts;
- int argc;
- char *const *argv;
+ struct cli_opt **opts;
+ int argc;
+ char *const *argv;
};
/**
* enum cli_opt_type - The type of option.
- * @__CLI_OT_NONE: used as sentinel value; shouldn't be used directly.
- * @CLI_OT_INT: an integer; will be parsed as a C `long`.
- * @CLI_OT_STRING: a string; the pointer to the string will be passed as is.
- * @CLI_OT_FLAG: a flag; if the key was given, it wil be set; has no value.
+ * @__CLI_OT_NONE: unused.
+ * @CLI_OT_FLAG: a flag option; has no value.
+ * @CLI_OT_VALUE: a value option; it expects a value after the option key.
*/
-enum cli_opt_type {
+enum cli_opt_type : uint8_t {
__CLI_OT_NONE,
- CLI_OT_INT,
- CLI_OT_UINT,
- CLI_OT_STRING,
CLI_OT_FLAG,
+ CLI_OT_VALUE,
};
/**
@@ -66,27 +66,125 @@ enum cli_opt_type {
* @shor: short key, e.g. 'k' for "-k".
* @lon: long key, e.g. "key" for "--key".
* @desc: a short description of the option to be displayed in the help.
- * @value: pointer to variable to set:
- * - STRING: @value.s, will be set to point to the argv with the
- * value.
- * - INT: @value.i, will be set to the parsed value of the argv.
- * - UINT: @value.u, same as INT but unsigned
- * - FLAG: @value.f, will be set to true upon encountering the
- * option.
*/
struct cli_opt {
enum cli_opt_type type;
char shor;
const char *lon;
const char *desc;
- union {
- long *i;
- unsigned long *u;
- const char **s;
- bool *f;
- } value;
+ int (*set)(struct cli_opt *self, const char *val);
+};
+
+struct cli_opt_long {
+ struct cli_opt opt;
+ long value;
+};
+
+#define CLI_OPT_LONG(name, sh, ln, dsc) \
+ struct cli_opt_long name = { \
+ .opt = \
+ { \
+ .type = CLI_OT_VALUE, \
+ .shor = sh, \
+ .lon = ln, \
+ .desc = dsc, \
+ .set = cli_opt_long_set, \
+ }, \
+ }
+
+int cli_opt_long_set(struct cli_opt *self, const char *val)
+{
+ struct cli_opt_long *opt = container_of(self, struct cli_opt_long, opt);
+
+ errno = 0;
+ opt->value = strtol(val, NULL, 0);
+ if (errno != 0)
+ return CLI_RC_BAD_ARGS;
+
+ return CLI_RC_OK;
+}
+
+struct cli_opt_ulong {
+ struct cli_opt opt;
+ long value;
+};
+
+#define CLI_OPT_ULONG(name, sh, ln, dsc) \
+ struct cli_opt_ulong name = { \
+ .opt = \
+ { \
+ .type = CLI_OT_VALUE, \
+ .shor = sh, \
+ .lon = ln, \
+ .desc = dsc, \
+ .set = cli_opt_ulong_set, \
+ }, \
+ }
+
+int cli_opt_ulong_set(struct cli_opt *self, const char *val)
+{
+ struct cli_opt_ulong *opt = container_of(self, struct cli_opt_ulong, opt);
+
+ errno = 0;
+ opt->value = strtoul(val, NULL, 0);
+ if (errno != 0)
+ return CLI_RC_BAD_ARGS;
+
+ return CLI_RC_OK;
+}
+
+struct cli_opt_string {
+ struct cli_opt opt;
+ const char *value;
+};
+
+#define CLI_OPT_STRING(name, sh, ln, dsc) \
+ struct cli_opt_string name = { \
+ .opt = \
+ { \
+ .type = CLI_OT_VALUE, \
+ .shor = sh, \
+ .lon = ln, \
+ .desc = dsc, \
+ .set = cli_opt_string_set, \
+ }, \
+ }
+
+int cli_opt_string_set(struct cli_opt *self, const char *val)
+{
+ struct cli_opt_string *opt = container_of(self, struct cli_opt_string, opt);
+
+ if (!val)
+ return CLI_RC_BAD_ARGS;
+ opt->value = val;
+
+ return CLI_RC_OK;
+}
+
+struct cli_opt_flag {
+ struct cli_opt opt;
+ bool value;
};
+#define CLI_OPT_FLAG(name, sh, ln, dsc) \
+ struct cli_opt_flag name = { \
+ .opt = \
+ { \
+ .type = CLI_OT_FLAG, \
+ .shor = sh, \
+ .lon = ln, \
+ .desc = dsc, \
+ .set = cli_opt_flag_set, \
+ }, \
+ }
+
+int cli_opt_flag_set(struct cli_opt *self, const char *)
+{
+ struct cli_opt_flag *opt = container_of(self, struct cli_opt_flag, opt);
+ opt->value = true;
+ return CLI_RC_OK;
+}
+
/**
* struct cli_cmd - Represents a single command for the CLI.
* @name: the command name as it should be read from argv.
@@ -98,17 +196,17 @@ struct cli_opt {
* they are read from the arguments. cli.h will stop parsing once <-->
* is encountered, or a value (non-option arg) without key is read.
* The remaining args will be passed in cli_cmd_ctx.
- * @func: the function to execute when the command is invoked; the options
- * that were passed in this struct will be filled according to what is
+ * @run: the function to execute when the command is invoked; the options
+ * that were passed in this struct will be set according to what is
* parsed from the command-line arguments.
*/
struct cli_cmd {
- const char *name;
- const char *desc;
- const char *usage;
- const char *help;
- const struct cli_opt *opts;
- int (*func)(const struct cli_ctx *ctx);
+ const char *name;
+ const char *desc;
+ const char *usage;
+ const char *help;
+ struct cli_opt **opts;
+ int (*run)(const struct cli_cmd *self, const struct cli_ctx *ctx);
};
/**
@@ -124,7 +222,7 @@ struct cli {
const char *header;
const char *footer;
const struct cli_cmd *cmds;
- const struct cli_opt *opts;
+ struct cli_opt **opts;
};
static inline bool arg_is_help(const char *const arg)
@@ -132,9 +230,10 @@ static inline bool arg_is_help(const char *const arg)
return !strcmp(arg, "--help") || !strcmp(arg, "help") || !strcmp(arg, "-h");
}
-static void list_options(const struct cli_opt *opts)
+static void list_options(struct cli_opt **opts)
{
- for (const struct cli_opt *opt = opts; opt->type; opt++) {
+ for (size_t i = 0; opts[i]; i++) {
+ const struct cli_opt *opt = opts[i];
if (opt->lon) {
if (opt->shor) {
printf("\t--%s, -%c\t", opt->lon, opt->shor);
@@ -155,7 +254,8 @@ static void explain_program(const struct cli *const cli)
printf("%s\n", cli->header);
printf("Usage: %s%s <command> [command options]\n\n",
- cli->opts ? "[global options] " : "", cli->binary);
+ cli->opts ? "[global options] " : "",
+ cli->binary);
if (cli->cmds) {
puts("Commands:");
@@ -183,8 +283,11 @@ static void explain_command(const struct cli *const cli,
if (cli->header)
printf("%s\n", cli->header);
- printf("Usage: %s%s %s %s\n\n", cli->opts ? "[global options] " : "",
- cli->binary, cmd->name, cmd->usage ?: "[options]");
+ printf("Usage: %s%s %s %s\n\n",
+ cli->opts ? "[global options] " : "",
+ cli->binary,
+ cmd->name,
+ cmd->usage ?: "[options]");
printf("%s\n\n", cmd->help);
if (cmd->opts) {
@@ -196,35 +299,6 @@ static void explain_command(const struct cli *const cli,
printf("%s\n", cli->footer);
}
-static int parse_value(const struct cli_opt *opt, const char *value)
-{
- switch (opt->type) {
- case CLI_OT_INT:
- errno = 0;
- *opt->value.i = strtol(value, NULL, 0);
- if (errno != 0)
- return CLI_RC_BAD_ARGS;
- break;
- case CLI_OT_UINT:
- errno = 0;
- *opt->value.u = strtoul(value, NULL, 0);
- if (errno != 0)
- return CLI_RC_BAD_ARGS;
- break;
- case CLI_OT_STRING:
- *opt->value.s = value;
- break;
- case CLI_OT_FLAG:
- *opt->value.f = true;
- break;
- default:
- fprintf(stderr, "BUG: incorrect option type %d\n", opt->type);
- return CLI_RC_BUG;
- }
-
- return CLI_RC_OK;
-}
-
static int parse_long(struct cli_ctx *ctx)
{
char *key = ctx->argv[0] + 2;
@@ -238,15 +312,15 @@ static int parse_long(struct cli_ctx *ctx)
}
}
- const struct cli_opt *opt = ctx->opts;
- if (opt) {
- for (; opt->type; opt++) {
- if (!strcmp(key, opt->lon))
- break;
+ struct cli_opt *opt = NULL;
+ for (size_t i = 0; ctx->opts[i]; i++) {
+ if (!strcmp(key, ctx->opts[i]->lon)) {
+ opt = ctx->opts[i];
+ break;
}
}
- if (!opt || !opt->type) {
+ if (!opt) {
fprintf(stderr, "bad long option '--%s'\n", key);
return CLI_RC_BAD_ARGS;
}
@@ -263,7 +337,7 @@ static int parse_long(struct cli_ctx *ctx)
value = *ctx->argv;
}
- int rc = parse_value(opt, value);
+ int rc = opt->set(opt, value);
if (rc == CLI_RC_BAD_ARGS)
fprintf(stderr, "bad value '%s' for option '--%s'\n", value, key);
@@ -276,15 +350,15 @@ static int parse_long(struct cli_ctx *ctx)
static int parse_short(struct cli_ctx *ctx)
{
for (const char *c = ctx->argv[0] + 1; *c != '\0'; c++) {
- const struct cli_opt *opt = ctx->opts;
- if (opt) {
- for (; opt->type; opt++) {
- if (*c == opt->shor)
- break;
+ struct cli_opt *opt = NULL;
+ for (size_t i = 0; ctx->opts[i]; i++) {
+ if (*c == ctx->opts[i]->shor) {
+ opt = ctx->opts[i];
+ break;
}
}
- if (!opt || !opt->type) {
+ if (!opt) {
fprintf(stderr, "bad short option '-%c'\n", *c);
return CLI_RC_BAD_ARGS;
}
@@ -307,18 +381,18 @@ static int parse_short(struct cli_ctx *ctx)
value = *ctx->argv;
}
- int rc = parse_value(opt, value);
+ int rc = opt->set(opt, value);
if (rc != CLI_RC_OK) {
if (rc == CLI_RC_BAD_ARGS)
- fprintf(stderr, "bad value '%s' for option '-%c'\n", value,
- *c);
+ fprintf(
+ stderr, "bad value '%s' for option '-%c'\n", value, *c);
return rc;
}
goto finish;
}
- int rc = parse_value(opt, NULL);
+ int rc = opt->set(opt, NULL);
if (rc != CLI_RC_OK)
return rc;
/* we might have several flags strung together, e.g. '-abcd' */
@@ -380,7 +454,7 @@ static inline int cli_cmd_run(const struct cli *const cli,
goto out;
}
- rc = cmd->func(&cmd_ctx);
+ rc = cmd->run(cmd, &cmd_ctx);
if (rc == CLI_RC_BAD_ARGS)
goto help;
@@ -399,6 +473,7 @@ out:
*
* Return: CLI_RC_OK on success, anything else on error.
*/
+[[maybe_unused]]
static int cli_run(struct cli *const cli, int argc, char *argv[])
{
cli->binary = cli->binary ?: argv[0];
@@ -419,9 +494,9 @@ static int cli_run(struct cli *const cli, int argc, char *argv[])
for (const struct cli_cmd *cmd = cli->cmds; cmd->name; cmd++) {
if (!strcmp(cmd->name, ctx.argv[0])) {
- if (!cmd->func) {
- fprintf(stderr, "BUG: command `%s` has no function\n",
- cmd->name);
+ if (!cmd->run) {
+ fprintf(
+ stderr, "BUG: command `%s` has no function\n", cmd->name);
return CLI_RC_BUG;
}