From ee74335c8440803006aeca6b1a427c0cf28b4a98 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Thu, 9 May 2024 03:22:37 +0200 Subject: Rename optparse.h to cli.h --- README | 8 +-- include/cli.h | 45 ++++++++++++++ include/optparse.h | 46 -------------- lib/cli/optparse.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/optparse/optparse.c | 159 ------------------------------------------------ 5 files changed, 208 insertions(+), 209 deletions(-) create mode 100644 include/cli.h delete mode 100644 include/optparse.h create mode 100644 lib/cli/optparse.c delete mode 100644 lib/optparse/optparse.c diff --git a/README b/README index 2feb1ba..c946ff6 100644 --- a/README +++ b/README @@ -12,12 +12,12 @@ library. It is a C23 library with no plans to support older standards. The headers as of now are: • alloc.h — allocators and memory allocation functions • bitset.h — bitset implementation + • cli.h — CLI-option parsing functions • dynarr.h — dynamic array implementation • errors.h — err.h-inspired diagnostics functions • macros.h — miscellaneous utility macros (MIN/MAX/lengthof/etc.) • mbio.h — multibyte file I/O • mbstring.h — multibyte-strings - • optparse.h — option parsing functions • rune.h — inttypes.h but for runes • unicode/prop.h — unicode character properties • unicode/string.h — unicode string functions @@ -72,6 +72,9 @@ FEATURES: memory-allocating functions • bitset.h • Bitset implementation and operations + • cli.h + • Thread-safe (non-global) UTF-8-aware option parsing with + support for short- and long options • dynarr.h • Dynamic-array implementation and operations • Macro for array iteration @@ -95,9 +98,6 @@ FEATURES: • UTF-8 validation • Various string.h analogues with UTF-8 support • Random utility macros and -constants - • optparse.h - • Thread-safe (non-global) UTF-8-aware option parsing with - support for short- and long options • rune.h • Format string macros for the printf() and scanf() families of functions diff --git a/include/cli.h b/include/cli.h new file mode 100644 index 0000000..7b5b611 --- /dev/null +++ b/include/cli.h @@ -0,0 +1,45 @@ +#ifndef MLIB_CLI_H +#define MLIB_CLI_H + +#include + +#include "_attrs.h" +#include "_rune.h" +#include "_u8view.h" + +struct optparser { + bool _b; + int _subopt; + char **_argv; + + int optind; + char errmsg[128]; + struct u8view optarg; +}; + +enum cliarg { + CLI_NONE, + CLI_OPT, + CLI_REQ, +}; + +struct cli_option { + rune shortopt; + struct u8view longopt; + enum cliarg argtype; +}; + +[[nodiscard]] rune optparse(struct optparser *, const struct cli_option *, + size_t); + +[[_mlib_inline]] +static inline struct optparser +mkoptparser(char **argv) +{ + return (struct optparser){ + ._argv = argv, + .optind = argv[0] != nullptr, + }; +} + +#endif /* !MLIB_CLI_H */ diff --git a/include/optparse.h b/include/optparse.h deleted file mode 100644 index 7e2c315..0000000 --- a/include/optparse.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef MLIB_OPTPARSE_H -#define MLIB_OPTPARSE_H - -#include - -#include "_attrs.h" -#include "_charN_t.h" -#include "_rune.h" -#include "_u8view.h" - -struct optparse { - bool _b; - int _subopt; - char **_argv; - - int optind; - char errmsg[128]; - struct u8view optarg; -}; - -enum op_argkind { - OPT_NONE, - OPT_OPT, - OPT_REQ, -}; - -struct op_option { - rune shortopt; - struct u8view longopt; - enum op_argkind argtype; -}; - -[[nodiscard]] rune optparse(struct optparse *, const struct op_option *, - size_t); - -[[_mlib_inline]] -static inline struct optparse -mkoptparser(char **argv) -{ - return (struct optparse){ - ._argv = argv, - .optind = argv[0] != nullptr, - }; -} - -#endif /* !MLIB_OPTPARSE_H */ diff --git a/lib/cli/optparse.c b/lib/cli/optparse.c new file mode 100644 index 0000000..ce688cf --- /dev/null +++ b/lib/cli/optparse.c @@ -0,0 +1,159 @@ +#include +#include + +#include "cli.h" +#include "macros.h" +#include "mbstring.h" + +#define CLI_MSG_INVALID "invalid option" +#define CLI_MSG_MISSING "option requires an argument" +#define CLI_MSG_TOOMANY "option takes no arguments" + +#define IS_DASHDASH(s) ((s).len == 2 && (s).p[0] == '-' && (s).p[1] == '-') +#define IS_LONGOPT(s) ((s).len >= 3 && (s).p[0] == '-' && (s).p[1] == '-') +#define IS_SHORTOPT(s) ((s).len >= 2 && (s).p[0] == '-' && (s).p[1] != '-') + +#define error(st, msg, x) \ + _Generic((x), struct u8view: error_s, rune: error_r)((st), (msg), (x)) + +static rune error_r(struct optparser *, const char *, rune); +static rune error_s(struct optparser *, const char *, struct u8view); +static rune shortopt(struct optparser *, const struct cli_option *, size_t); + +rune +optparse(struct optparser *st, const struct cli_option *opts, size_t nopts) +{ + st->errmsg[0] = '\0'; + st->optarg = (struct u8view){}; + + struct u8view opt = {.p = st->_argv[st->optind]}; + if (opt.p == nullptr) + return 0; + opt.len = strlen(opt.p); + + if (IS_DASHDASH(opt)) { + st->optind++; + return 0; + } + if (IS_SHORTOPT(opt)) + return shortopt(st, opts, nopts); + if (!IS_LONGOPT(opt)) + return 0; + + st->_subopt = 0; + st->optind++; + + /* Skip ‘--’ */ + VSHFT(&opt, 2); + + const struct cli_option *o = nullptr; + const char8_t *eq_p = u8chr(opt, '='); + struct u8view opt_no_eq = { + .p = opt.p, + .len = eq_p == nullptr ? opt.len : (size_t)(eq_p - opt.p), + }; + + for (size_t i = 0; i < nopts; i++) { + struct u8view lo = opts[i].longopt; + if (lo.p == nullptr || !u8haspfx(lo, opt_no_eq)) + continue; + if (o != nullptr) + return error(st, CLI_MSG_INVALID, opt_no_eq); + o = opts + i; + } + + if (o == nullptr) + return error(st, CLI_MSG_INVALID, opt_no_eq); + + switch (o->argtype) { + case CLI_NONE: + if (eq_p != nullptr) + return error(st, CLI_MSG_TOOMANY, opt); + break; + case CLI_OPT: + if (eq_p == nullptr) + st->optarg = (struct u8view){}; + else { + ASSUME(opt.len > opt_no_eq.len); + st->optarg = (struct u8view){ + .p = eq_p + 1, + .len = opt.len - opt_no_eq.len + 1, + }; + } + break; + case CLI_REQ: + if (eq_p == nullptr) { + if (st->_argv[st->optind] == nullptr) + return error(st, CLI_MSG_MISSING, opt); + st->optarg.p = st->_argv[st->optind++]; + st->optarg.len = strlen(st->optarg.p); + } else { + ASSUME(opt.len > opt_no_eq.len); + st->optarg = (struct u8view){ + .p = eq_p + 1, + .len = opt.len - opt_no_eq.len + 1, + }; + } + break; + } + + return o->shortopt; +} + +rune +shortopt(struct optparser *st, const struct cli_option *opts, size_t nopts) +{ + rune ch; + const char *opt = st->_argv[st->optind]; + st->_subopt += u8tor(&ch, opt + st->_subopt + 1); + if (ch == '\0') { + st->_subopt = 0; + st->optind++; + return optparse(st, opts, nopts); + } + for (size_t i = 0; i < nopts; i++) { + if (opts[i].shortopt != ch) + continue; + if (opts[i].argtype == CLI_NONE) + goto out; + if (opt[st->_subopt + 1] != '\0') { + st->optarg.p = opt + st->_subopt + 1; + st->optarg.len = strlen(st->optarg.p); + st->_subopt = 0; + st->optind++; + goto out; + } + if (opts[i].argtype == CLI_OPT) { + st->optarg = (struct u8view){}; + goto out; + } + if (st->_argv[st->optind + 1] == nullptr) { + st->optarg = (struct u8view){}; + return error(st, CLI_MSG_MISSING, ch); + } + st->optarg.p = st->_argv[st->optind + 1]; + st->optarg.len = strlen(st->optarg.p); + st->optind += 2; + goto out; + } + return error(st, CLI_MSG_INVALID, ch); +out: + return ch; +} + +rune +error_s(struct optparser *st, const char *msg, struct u8view s) +{ + snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg, + SV_PRI_ARGS(s)); + return -1; +} + +rune +error_r(struct optparser *st, const char *msg, rune ch) +{ + char buf[U8_LEN_MAX + 1] = {}; + snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg, + rtou8(buf, sizeof(buf), ch), buf); + return -1; +} diff --git a/lib/optparse/optparse.c b/lib/optparse/optparse.c deleted file mode 100644 index 757dd47..0000000 --- a/lib/optparse/optparse.c +++ /dev/null @@ -1,159 +0,0 @@ -#include -#include - -#include "macros.h" -#include "mbstring.h" -#include "optparse.h" - -#define OPT_MSG_INVALID "invalid option" -#define OPT_MSG_MISSING "option requires an argument" -#define OPT_MSG_TOOMANY "option takes no arguments" - -#define IS_DASHDASH(s) ((s).len == 2 && (s).p[0] == '-' && (s).p[1] == '-') -#define IS_LONGOPT(s) ((s).len >= 3 && (s).p[0] == '-' && (s).p[1] == '-') -#define IS_SHORTOPT(s) ((s).len >= 2 && (s).p[0] == '-' && (s).p[1] != '-') - -#define error(st, msg, x) \ - _Generic((x), struct u8view: error_s, rune: error_r)((st), (msg), (x)) - -static rune error_r(struct optparse *, const char *, rune); -static rune error_s(struct optparse *, const char *, struct u8view); -static rune shortopt(struct optparse *, const struct op_option *, size_t); - -rune -optparse(struct optparse *st, const struct op_option *opts, size_t nopts) -{ - st->errmsg[0] = '\0'; - st->optarg = (struct u8view){}; - - struct u8view opt = {.p = st->_argv[st->optind]}; - if (opt.p == nullptr) - return 0; - opt.len = strlen(opt.p); - - if (IS_DASHDASH(opt)) { - st->optind++; - return 0; - } - if (IS_SHORTOPT(opt)) - return shortopt(st, opts, nopts); - if (!IS_LONGOPT(opt)) - return 0; - - st->_subopt = 0; - st->optind++; - - /* Skip ‘--’ */ - VSHFT(&opt, 2); - - const struct op_option *o = nullptr; - const char8_t *eq_p = u8chr(opt, '='); - struct u8view opt_no_eq = { - .p = opt.p, - .len = eq_p == nullptr ? opt.len : (size_t)(eq_p - opt.p), - }; - - for (size_t i = 0; i < nopts; i++) { - struct u8view lo = opts[i].longopt; - if (lo.p == nullptr || !u8haspfx(lo, opt_no_eq)) - continue; - if (o != nullptr) - return error(st, OPT_MSG_INVALID, opt_no_eq); - o = opts + i; - } - - if (o == nullptr) - return error(st, OPT_MSG_INVALID, opt_no_eq); - - switch (o->argtype) { - case OPT_NONE: - if (eq_p != nullptr) - return error(st, OPT_MSG_TOOMANY, opt); - break; - case OPT_OPT: - if (eq_p == nullptr) - st->optarg = (struct u8view){}; - else { - ASSUME(opt.len > opt_no_eq.len); - st->optarg = (struct u8view){ - .p = eq_p + 1, - .len = opt.len - opt_no_eq.len + 1, - }; - } - break; - case OPT_REQ: - if (eq_p == nullptr) { - if (st->_argv[st->optind] == nullptr) - return error(st, OPT_MSG_MISSING, opt); - st->optarg.p = st->_argv[st->optind++]; - st->optarg.len = strlen(st->optarg.p); - } else { - ASSUME(opt.len > opt_no_eq.len); - st->optarg = (struct u8view){ - .p = eq_p + 1, - .len = opt.len - opt_no_eq.len + 1, - }; - } - break; - } - - return o->shortopt; -} - -rune -shortopt(struct optparse *st, const struct op_option *opts, size_t nopts) -{ - rune ch; - const char *opt = st->_argv[st->optind]; - st->_subopt += u8tor(&ch, opt + st->_subopt + 1); - if (ch == '\0') { - st->_subopt = 0; - st->optind++; - return optparse(st, opts, nopts); - } - for (size_t i = 0; i < nopts; i++) { - if (opts[i].shortopt != ch) - continue; - if (opts[i].argtype == OPT_NONE) - goto out; - if (opt[st->_subopt + 1] != '\0') { - st->optarg.p = opt + st->_subopt + 1; - st->optarg.len = strlen(st->optarg.p); - st->_subopt = 0; - st->optind++; - goto out; - } - if (opts[i].argtype == OPT_OPT) { - st->optarg = (struct u8view){}; - goto out; - } - if (st->_argv[st->optind + 1] == nullptr) { - st->optarg = (struct u8view){}; - return error(st, OPT_MSG_MISSING, ch); - } - st->optarg.p = st->_argv[st->optind + 1]; - st->optarg.len = strlen(st->optarg.p); - st->optind += 2; - goto out; - } - return error(st, OPT_MSG_INVALID, ch); -out: - return ch; -} - -rune -error_s(struct optparse *st, const char *msg, struct u8view s) -{ - snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg, - SV_PRI_ARGS(s)); - return -1; -} - -rune -error_r(struct optparse *st, const char *msg, rune ch) -{ - char buf[U8_LEN_MAX + 1] = {}; - snprintf(st->errmsg, sizeof(st->errmsg), u8"%s — ‘%.*s’", msg, - rtou8(buf, sizeof(buf), ch), buf); - return -1; -} -- cgit v1.2.3