From a0641cb945ecdbe31364377c4535de3eda0a23e0 Mon Sep 17 00:00:00 2001 From: Marc Rittinghaus Date: Wed, 28 Dec 2022 14:56:12 +0100 Subject: [PATCH] Add positional options support Currently, positional options are not explicitly supported but just remain as unparsed arguments. This commit adds support for positional arguments so that these are treated like other options including parsing and listing in the help output. Signed-off-by: Marc Rittinghaus --- argparse.c | 48 ++++++++++++++++++++++++++++++++++++++++++++---- argparse.h | 2 ++ tests/basic.c | 11 ++++++++++- tests/basic.sh | 14 +++++++++++--- 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/argparse.c b/argparse.c index 9eec9ab94..fbdab9aef 100644 --- a/argparse.c +++ b/argparse.c @@ -14,6 +14,7 @@ #define OPT_UNSET 1 #define OPT_LONG (1 << 1) +#define OPT_POSI (1 << 2) // Positional argument static const char * prefix_skip(const char *str, const char *prefix) @@ -38,7 +39,9 @@ argparse_error(struct argparse *self, const struct argparse_option *opt, const char *reason, int flags) { (void)self; - if (flags & OPT_LONG) { + if (flags & OPT_POSI) { + fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason); + } else if (flags & OPT_LONG) { fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason); } else { fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason); @@ -145,10 +148,29 @@ argparse_options_check(const struct argparse_option *options) } } +static int +argparse_pos_opt(struct argparse *self, const struct argparse_option *options) +{ + options += self->posidx; + for (; options->type != ARGPARSE_OPT_END; options++, self->posidx++) { + if (!(options->flags & OPT_POSITIONAL)) { + continue; + } + + self->posidx++; + return argparse_getvalue(self, options, OPT_POSI); + } + return -2; +} + static int argparse_short_opt(struct argparse *self, const struct argparse_option *options) { for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->flags & OPT_POSITIONAL) { + continue; + } + if (options->short_name == *self->optvalue) { self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL; return argparse_getvalue(self, options, 0); @@ -163,6 +185,10 @@ argparse_long_opt(struct argparse *self, const struct argparse_option *options) for (; options->type != ARGPARSE_OPT_END; options++) { const char *rest; int opt_flags = 0; + if (options->flags & OPT_POSITIONAL) { + continue; + } + if (!options->long_name) continue; @@ -229,6 +255,14 @@ argparse_parse(struct argparse *self, int argc, const char **argv) for (; self->argc; self->argc--, self->argv++) { const char *arg = self->argv[0]; if (arg[0] != '-' || !arg[1]) { + self->optvalue = arg; + switch (argparse_pos_opt(self, self->options)) { + case 0: + continue; + case -1: + break; + } + if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) { goto end; } @@ -318,7 +352,10 @@ argparse_usage(struct argparse *self) len += 2; // separator ", " } if ((options)->long_name) { - len += strlen((options)->long_name) + 2; + len += strlen((options)->long_name); + if (!(options->flags & OPT_POSITIONAL)) { + len += 2; + } } if (options->type == ARGPARSE_OPT_INTEGER) { len += strlen("="); @@ -353,7 +390,11 @@ argparse_usage(struct argparse *self) pos += fprintf(stdout, ", "); } if (options->long_name) { - pos += fprintf(stdout, "--%s", options->long_name); + if (options->flags & OPT_POSITIONAL) { + pos += fprintf(stdout, "%s", options->long_name); + } else { + pos += fprintf(stdout, "--%s", options->long_name); + } } if (options->type == ARGPARSE_OPT_INTEGER) { pos += fprintf(stdout, "="); @@ -391,4 +432,3 @@ argparse_help_cb(struct argparse *self, const struct argparse_option *option) argparse_help_cb_no_exit(self, option); exit(EXIT_SUCCESS); } - diff --git a/argparse.h b/argparse.h index fd1ddfca3..1ac2f9816 100644 --- a/argparse.h +++ b/argparse.h @@ -41,6 +41,7 @@ enum argparse_option_type { enum argparse_option_flags { OPT_NONEG = 1, /* disable negation */ + OPT_POSITIONAL = 2, /* option is positional */ }; /** @@ -98,6 +99,7 @@ struct argparse { const char **argv; const char **out; int cpidx; + int posidx; // index of next option to check if positional const char *optvalue; // current option value }; diff --git a/tests/basic.c b/tests/basic.c index fcd4e34ff..10dfdb427 100644 --- a/tests/basic.c +++ b/tests/basic.c @@ -4,7 +4,7 @@ #include "argparse.h" static const char *const usages[] = { - "basic [options] [[--] args]", + "basic [options] posi poss [[--] args]", "basic [options]", NULL, }; @@ -21,6 +21,8 @@ main(int argc, const char **argv) int int_num = 0; float flt_num = 0.f; const char *path = NULL; + int posi = 0; + const char *poss = NULL; int perms = 0; struct argparse_option options[] = { OPT_HELP(), @@ -34,6 +36,9 @@ main(int argc, const char **argv) OPT_BIT(0, "read", &perms, "read perm", NULL, PERM_READ, OPT_NONEG), OPT_BIT(0, "write", &perms, "write perm", NULL, PERM_WRITE, 0), OPT_BIT(0, "exec", &perms, "exec perm", NULL, PERM_EXEC, 0), + OPT_GROUP("Positional options"), + OPT_INTEGER(0, "posi", &posi, "positional integer", NULL, 0, OPT_POSITIONAL), + OPT_STRING(0, "poss", &poss, "positional string", NULL, 0, OPT_POSITIONAL), OPT_END(), }; @@ -51,6 +56,10 @@ main(int argc, const char **argv) printf("int_num: %d\n", int_num); if (flt_num != 0) printf("flt_num: %g\n", flt_num); + if (posi != 0) + printf("posi: %d\n", posi); + if (poss != NULL) + printf("poss: %s\n", poss); if (argc != 0) { printf("argc: %d\n", argc); int i; diff --git a/tests/basic.sh b/tests/basic.sh index c72c772c1..777d76cb5 100755 --- a/tests/basic.sh +++ b/tests/basic.sh @@ -3,10 +3,12 @@ . $(dirname ${BASH_SOURCE[0]})/tap-functions plan_no_plan -is "$(./basic -f --path=/path/to/file a 2>&1)" 'force: 1 +is "$(./basic -f 42 --path=/path/to/file a b 2>&1)" 'force: 1 path: /path/to/file +posi: 42 +poss: a argc: 1 -argv[0]: a' +argv[0]: b' is "$(./basic -f -f --force --no-force 2>&1)" 'force: 2' @@ -18,6 +20,8 @@ is "$(./basic -i2 2>&1)" 'int_num: 2' is "$(./basic -ia 2>&1)" 'error: option `-i` expects an integer value' +is "$(./basic a 2>&1)" 'error: option `posi` expects an integer value' + is "$(./basic -i 0xFFFFFFFFFFFFFFFFF 2>&1)" \ 'error: option `-i` numerical result out of range' @@ -41,7 +45,7 @@ test: 1' is "$(./basic --read --write 2>&1)" 'perms: 3' -help_usage='Usage: basic [options] [[--] args] +help_usage='Usage: basic [options] posi poss [[--] args] or: basic [options] A brief description of what the program does and how it works. @@ -60,6 +64,10 @@ Bits options --write write perm --exec exec perm +Positional options + posi= positional integer + poss= positional string + Additional description of the program after the description of the arguments.' is "$(./basic -h)" "$help_usage"