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"