aboutsummaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/cli-opts.h79
-rw-r--r--cli/cli-test.c33
-rw-r--r--cli/cli.h23
3 files changed, 131 insertions, 4 deletions
diff --git a/cli/cli-opts.h b/cli/cli-opts.h
new file mode 100644
index 0000000..4a94bcc
--- /dev/null
+++ b/cli/cli-opts.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+/**
+ * cli-opts.h - Extra cli_opt implementations for cli.h
+ *
+ * Copyright (c) 2025 - Yaroslav de la Peña Smirnov
+ */
+#ifndef CLI_OPTS_H
+#define CLI_OPTS_H
+#include "cli.h"
+#include <ctype.h>
+
+/**
+ * struct cli_opt_data_size - an option that represents data size in bytes.
+ * @opt: parent class.
+ * @bytes: the amount given by the user in bytes.
+ *
+ * This CLI option can parse units in bytes, KiB, MiB, GiB, TiB. By giving the
+ * number a suffix, it multiplies the number by the equivalent in bytes. E.g.
+ * `1K` == `1024`.
+ */
+struct cli_opt_data_size {
+ struct cli_opt opt;
+ uint64_t bytes;
+};
+
+#define CLI_OPT_DATA_SIZE(name, sh, ln, dsc) \
+ struct cli_opt_data_size name = { \
+ .opt = \
+ { \
+ .type = CLI_OT_VALUE, \
+ .shor = sh, \
+ .lon = ln, \
+ .desc = dsc, \
+ .set = cli_opt_data_size_set, \
+ }, \
+ }
+
+int cli_opt_data_size_set(struct cli_opt *self, const char *val)
+{
+ struct cli_opt_data_size *opt =
+ container_of(self, struct cli_opt_data_size, opt);
+
+ char *endptr = NULL;
+ errno = 0;
+ unsigned long long num = strtoull(val, &endptr, 10);
+ if (errno != 0)
+ goto invalid;
+
+ if (endptr && *endptr != '\0') {
+ switch (toupper(*endptr)) {
+ case 'K':
+ opt->bytes = num <<= 10;
+ break;
+ case 'M':
+ opt->bytes = num <<= 20;
+ break;
+ case 'G':
+ opt->bytes = num <<= 30;
+ break;
+ case 'T':
+ opt->bytes = num <<= 40;
+ break;
+ default:
+ goto invalid;
+ }
+ } else {
+ opt->bytes = num;
+ }
+
+ return CLI_RC_OK;
+invalid:
+ fprintf(stderr,
+ "invalid value \"%s\", expected a number optionally followed "
+ "by a multiplicative suffix (K, M, G, T).",
+ val);
+ return CLI_RC_ERR;
+}
+
+#endif /* CLI_OPTS_H */
diff --git a/cli/cli-test.c b/cli/cli-test.c
index c7a74e9..3a89f53 100644
--- a/cli/cli-test.c
+++ b/cli/cli-test.c
@@ -1,4 +1,5 @@
#include "cli.h"
+#include "cli-opts.h"
#include "../utest/utest.h"
#include "../utils.h"
@@ -48,6 +49,33 @@ static void reset_opts(void)
copt.value = 0;
}
+TEST_BEGIN(test_cli_opt_data_size_set)
+{
+ CLI_OPT_DATA_SIZE(opt, 0, NULL, NULL);
+ struct tcase {
+ const char *input;
+ uint64_t expected_val;
+ enum cli_rc expected_rc;
+ } cases[] = {
+ {"420", 420, CLI_RC_OK},
+ {"4K", 4ull << 10, CLI_RC_OK},
+ {"8M", 8ull << 20, CLI_RC_OK},
+ {"12G", 12ull << 30, CLI_RC_OK},
+ {"1T", 1ull << 40, CLI_RC_OK},
+ {"1E", 0, CLI_RC_ERR},
+ {"6m", 6ull << 20, CLI_RC_OK},
+ {"16k", 16ull << 10, CLI_RC_OK},
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
+ opt.bytes = 0;
+ int rc = opt.opt.set(&opt.opt, cases[i].input);
+ asserteq(rc, cases[i].expected_rc);
+ asserteq(opt.bytes, cases[i].expected_val);
+ }
+ TEST_OUT
+}
+TEST_END
+
TEST_BEGIN(test_parse_long)
{
struct tcase {
@@ -441,4 +469,7 @@ TEST_BEGIN(test_parse_options)
}
TEST_END
-RUN_TESTS(test_parse_long, test_parse_short, test_parse_options)
+RUN_TESTS(test_cli_opt_data_size_set,
+ test_parse_long,
+ test_parse_short,
+ test_parse_options)
diff --git a/cli/cli.h b/cli/cli.h
index 07f0ab4..72b89c5 100644
--- a/cli/cli.h
+++ b/cli/cli.h
@@ -8,6 +8,8 @@
*
* Copyright (c) 2025 - Yaroslav de la Peña Smirnov
*/
+#ifndef CLI_H
+#define CLI_H
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
@@ -49,7 +51,7 @@ struct cli_ctx {
};
/**
- * enum cli_opt_type - The type of option.
+ * enum cli_opt_type - The type of option key.
* @__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.
@@ -61,17 +63,22 @@ enum cli_opt_type : uint8_t {
};
/**
- * struct cli_option - A flag or "option" for a command.
+ * struct cli_opt - A flag or "option" for a command; "abstract class".
* @type: the type of the option; see `enum cli_opt_type` for more info.
* @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.
+ * @set: setter method; parses and sets the actual value of the option.
+ *
+ * This is an abstract class. You can use the implementations below or make your
+ * own by embedding this struct in your own and setting the @set method.
*/
struct cli_opt {
enum cli_opt_type type;
char shor;
const char *lon;
const char *desc;
+ [[gnu::nonnull(1)]]
int (*set)(struct cli_opt *self, const char *val);
};
@@ -206,6 +213,7 @@ struct cli_cmd {
const char *usage;
const char *help;
struct cli_opt **opts;
+ [[gnu::nonnull]]
int (*run)(const struct cli_cmd *self, const struct cli_ctx *ctx);
};
@@ -225,11 +233,13 @@ struct cli {
struct cli_opt **opts;
};
+[[gnu::nonnull]]
static inline bool arg_is_help(const char *const arg)
{
return !strcmp(arg, "--help") || !strcmp(arg, "help") || !strcmp(arg, "-h");
}
+[[gnu::nonnull]]
static void list_options(struct cli_opt **opts)
{
for (size_t i = 0; opts[i]; i++) {
@@ -248,6 +258,7 @@ static void list_options(struct cli_opt **opts)
}
}
+[[gnu::nonnull]]
static void explain_program(const struct cli *const cli)
{
if (cli->header)
@@ -277,6 +288,7 @@ static void explain_program(const struct cli *const cli)
printf("%s\n", cli->footer);
}
+[[gnu::nonnull]]
static void explain_command(const struct cli *const cli,
const struct cli_cmd *cmd)
{
@@ -299,6 +311,7 @@ static void explain_command(const struct cli *const cli,
printf("%s\n", cli->footer);
}
+[[gnu::nonnull]]
static int parse_long(struct cli_ctx *ctx)
{
char *key = ctx->argv[0] + 2;
@@ -347,6 +360,7 @@ static int parse_long(struct cli_ctx *ctx)
return rc;
}
+[[gnu::nonnull]]
static int parse_short(struct cli_ctx *ctx)
{
for (const char *c = ctx->argv[0] + 1; *c != '\0'; c++) {
@@ -403,6 +417,7 @@ finish:
return CLI_RC_OK;
}
+[[gnu::nonnull]]
static int parse_options(struct cli_ctx *ctx)
{
while (ctx->argc) {
@@ -433,6 +448,7 @@ static int parse_options(struct cli_ctx *ctx)
return CLI_RC_OK;
}
+[[gnu::nonnull]]
static inline int cli_cmd_run(const struct cli *const cli,
const struct cli_cmd *cmd,
const struct cli_ctx *global_ctx)
@@ -473,7 +489,7 @@ out:
*
* Return: CLI_RC_OK on success, anything else on error.
*/
-[[maybe_unused]]
+[[maybe_unused, gnu::nonnull]]
static int cli_run(struct cli *const cli, int argc, char *argv[])
{
cli->binary = cli->binary ?: argv[0];
@@ -514,3 +530,4 @@ out:
explain_program(cli);
return rc;
}
+#endif /* CLI_H */