From da5c0ef8dabe2fa0577bc0ccc56c5e2744a05851 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sun, 11 Nov 2018 23:21:53 +0100 Subject: Document libsimple-arg.h and add support optional arguments on long options and add KEEP_DASHDASH to ARGBEGIN3 (now named ARGBEGIN4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- libsimple-arg.h | 102 ++++++++++++++++++---- man0/libsimple-arg.h.0 | 226 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 278 insertions(+), 50 deletions(-) diff --git a/libsimple-arg.h b/libsimple-arg.h index 5a05cc8..cb32001 100644 --- a/libsimple-arg.h +++ b/libsimple-arg.h @@ -6,6 +6,8 @@ #include #include +/* TODO add tests */ + /** * The zeroth command line argument, the name of the process, @@ -14,9 +16,34 @@ extern char *argv0; +/** + * Map from a long option to a short option + * + * NB! Long options with optional arguments should + * have to map entries, one where `.long_flag` ends + * with '=' and `.with_arg` is non-zero, and one + * where `.long_flag` does not end with '=' and + * `.with_arg` is zero. These *cannot* have the same + * `.short_flag` + */ struct longopt { + /** + * The long option, if the value must be attached + * to the flag, this must end with '=' + */ const char *long_flag; + + /** + * The equivalent short option + * + * The first symbol in the short option + * (normally '-') will be `.long_flag[0]` + */ char short_flag; + + /** + * Whether the option takes an argument + */ int with_arg; }; @@ -59,7 +86,7 @@ struct longopt { * usage(); * } ARGEND; */ -#define ARGBEGIN ARGBEGIN3(1, argc, argv) +#define ARGBEGIN ARGBEGIN4(1, 1, argc, argv) /** * `SUBARGBEGIN {} ARGEND;` is similar to @@ -67,23 +94,26 @@ struct longopt { * is not set to `argv[0]`, instead `argv[0]` * is handled like any other element in `argv` */ -#define SUBARGBEGIN ARGBEGIN3(0, argc, argv) +#define SUBARGBEGIN ARGBEGIN4(0, 1, argc, argv) /** * Flexible alternative to `ARGBEGIN` * - * @param WITH_ARGV0 If 0, behave like `SUBARGBEGIN`, - * otherwise, behave like `ARGBEGIN` - * @param argc:int The number of elements in `argv`, will be - * update to the number of arguments remaining - * after parsing stopped - * @param argv:char ** The command line arguments to parse, - * `argv[argc]` must be `NULL`; will be updated, - * via offseting, to the arguments remaining - * after parsing stopped, `argv[argc]` will - * still be `NULL` + * @param WITH_ARGV0 If 0, behave like `SUBARGBEGIN`, + * otherwise, behave like `ARGBEGIN` + * @param KEEP_DASHDASH If and only if 0, "--" is not removed + * `argv` before parsing is stopped when it + * is encountered + * @param argc:int variable The number of elements in `argv`, will be + * update to the number of arguments remaining + * after parsing stopped + * @param argv:char ** variable The command line arguments to parse, + * `argv[argc]` must be `NULL`; will be updated, + * via offseting, to the arguments remaining + * after parsing stopped, `argv[argc]` will + * still be `NULL` */ -#define ARGBEGIN3(WITH_ARGV0, argc, argv)\ +#define ARGBEGIN4(WITH_ARGV0, argc, argv)\ do {\ char flag_, *lflag_, *arg_;\ int brk_ = 0, again_;\ @@ -93,11 +123,15 @@ struct longopt { argv += !!argv0;\ argc -= !!argv0;\ }\ + (void) arg_;\ + (void) i_;\ + (void) n_;\ for (; argv[0] && argv[0][0] && argv[0][1]; argc--, argv++) {\ lflag_ = argv[0];\ if (argv[0][0] == '-') {\ if (argv[0][1] == '-' && !argv[0][2]) {\ - argv++, argc--;\ + if (!(KEEP_DASHDASH))\ + argv++, argc--;\ break;\ }\ for (argv[0]++; argv[0][0]; argv[0]++) {\ @@ -110,7 +144,18 @@ struct longopt { switch (flag_) { /** - * TODO doc ARGMAPLONG + * Test multiple long options and go to + * corresponding short option case + * + * If the long option is found (see documentation + * for `struct longopt` for details), the keyword + * `break` is used to break the `case` (or `default`), + * and at the next iteration of the parsing loop, the + * case will be `.short_flag` for the entry where the + * argument matched `.long_flag` and `.with_arg` + * + * @param LONGOPTS:struct longopt * The options, list shall end + * with `NULL` as `.long_flag` */ #define ARGMAPLONG(LONGOPTS)\ for (i_ = 0; (LONGOPTS)[i_].long_flag; i_++) {\ @@ -305,11 +350,34 @@ struct longopt { /** - * TODO doc NOFLAGS + * Test if the argument is a specific long option + * + * Example: + * + * ARGBEGIN { + * case 'x': + * handle_dash_x: + * // handle -x + * break; + * case '-': + * if (TESTLONG("--xdev", 0)) + * goto handle_dash_x; + * else + * usage(); + * break; + * default: + * usage(); + * } ARGEND; + * + * @param FLAG:const char * The flag, must end with '=' if the + * value must be attached to the flag, + * must not have side-effects + * @param WARG:int Whether the option takes an argument, + * should not have side-effects */ #define TESTLONG(FLG, WARG)\ ((WARG)\ - ? ((!strncmp(lflag_, (FLG), n_ = strlen(FLG)) && lflag_[n_] == '=')\ + ? ((!strncmp(lflag_, (FLG), (n_ = strlen(FLG), n_ -= ((FLG)[n_ - !!n_] == '='))) && lflag_[n_] == '=') \ ? (lflag_[n_] = '\0',\ (arg_ = &lflag_[n_ + 1]),\ (brk_ = 1))\ diff --git a/man0/libsimple-arg.h.0 b/man0/libsimple-arg.h.0 index d6cfd2e..842f816 100644 --- a/man0/libsimple-arg.h.0 +++ b/man0/libsimple-arg.h.0 @@ -1,9 +1,9 @@ -.TH LIBSIMPLE-ARG.H 0 2018-11-08 libsimple +.TH LIBSIMPLE\-ARG.H 0 2018-11-08 libsimple .SH NAME -libsimple-arg.h \- command line argument parsing header for libsimple +libsimple\-arg.h \- command line argument parsing header for libsimple .SH SYNOPSIS .nf -#include +#include extern char *\fIargv0\fP; @@ -13,25 +13,25 @@ struct longopt { int with_arg; }; -#define ARGBEGIN ARGBEGIN3(1, argc, argv) -#define SUBARGBEGIN ARGBEGIN3(0, argc, argv) -#define ARGBEGIN3(\fIWITH_ARGV0\fP, \fIargc\fP, \fIargv\fP) /* implementation omitted */ -#define ARGMAPLONG(\fILONGOPTS\fP) /* implementation omitted */ -#define ARGALT(\fISYMBOL\fP) /* implementation omitted */ -#define ARGEND /* implementation omitted */ -#define ARGNUM /* implementation omitted */ -#define FLAG() /* implementation omitted */ -#define LFLAG() /* implementation omitted */ -#define ARG() /* implementation omitted */ -#define ARGHERE() /* implementation omitted */ -#define NOFLAGS(...) /* implementation omitted */ -#define TESTLONG(\fIFLG\fP, \fIWARG\fP) /* implementation omitted */ -#define USAGE(\fISYNOPSIS\fP) /* implementation omitted */ -#define NUSAGE(\fISTATUS\fP, \fISYNOPSIS\fP) /* implementation omitted */ +#define ARGBEGIN ARGBEGIN4(1, 1, argc, argv) +#define SUBARGBEGIN ARGBEGIN4(0, 1, argc, argv) +#define ARGBEGIN4(\fIWITH_ARGV0\fP, \fIKEEP_DASHDASH\fP, \fIargc\fP, \fIargv\fP) /* implementation omitted */ +#define ARGMAPLONG(\fILONGOPTS\fP) /* implementation omitted */ +#define ARGALT(\fISYMBOL\fP) /* implementation omitted */ +#define ARGEND /* implementation omitted */ +#define ARGNUM /* implementation omitted */ +#define FLAG() /* implementation omitted */ +#define LFLAG() /* implementation omitted */ +#define ARG() /* implementation omitted */ +#define ARGHERE() /* implementation omitted */ +#define NOFLAGS(...) /* implementation omitted */ +#define TESTLONG(\fIFLG\fP, \fIWARG\fP) /* implementation omitted */ +#define USAGE(\fISYNOPSIS\fP) /* implementation omitted */ +#define NUSAGE(\fISTATUS\fP, \fISYNOPSIS\fP) /* implementation omitted */ .fi .SH DESCRIPTION The -.I +.I header define a collection of macros for parsing the command line arguments, thatis, the data in the two first parameters of the @@ -52,7 +52,7 @@ binary the process runs; one common exception to this is for login shells: when a user logs in, the shell that is started is started with the filename of the shell's binary prefix with a dash -.RB ( - ) +.RB ( \- ) as the zeroth command line argument. Assuming the second argument in the .IR main () @@ -157,13 +157,13 @@ These macros work similar to a switch statement in a loop: ARGBEGIN { case 'a': - /* handle -a */ + /* handle \-a */ break; case 'b': - /* handle -b */ + /* handle \-b */ break; case ARGNUM: - /* handle -0, -1, -2, ..., -9 */ + /* handle \-0, \-1, \-2, ..., \-9 */ break; default: /* print usage information for other flags */ @@ -179,19 +179,43 @@ and are set to only contain the remaining, unparsed, arguments. .PP The -.BR ARGBEGIN3 () +.BR ARGBEGIN4 () macro can be used instead of the .B ARGBEGIN and .B SUBARGBEGIN macros for greater flexibility. If .I WITH_ARGV0 -is non-zero it behaves like the +is non-zero, it behaves like the .B ARGBEGIN macro, otherwise it behaves like the .B SUBARGBEGIN -macro. The -.BR ARGBEGIN3 () +macro. If +.I KEEP_DASHDASH +is zero, +.B -- +is removed from +.I argv +and +.I argc +decreased by one +before the parsing is stopped when +.B -- +is encounted, this is normal behaviour, +the application code not need to handle +.B -- +especially; on the other hand if +.I KEEP_DASHDASH +is non-zero +.B -- +is not removed before parsing is stopped when +.B -- +is encounted, and it becomes visible to the application +code, implement GNU behaviour by running the parsing +in a loop and stop when the application code sees +.BR -- . +The +.BR ARGBEGIN4 () also lets the user choose which variables to use as .I argc and @@ -199,7 +223,7 @@ and .PP If the application should support flags starting with another symbol than a dash -.RB ( - ), +.RB ( \- ), the macro .BR ARGALT () can be used. Its argument, @@ -211,7 +235,7 @@ and it is used thusly: ARGBEGIN { case 'a': - /* handle -a */ + /* handle \-a */ break; default: usage(); @@ -273,23 +297,159 @@ multidigit number). It can also be used to parse options where the value is optional be must appear in the same argument, for example, if the current argument is -.BR -n1000 , +.BR \-n1000 , .BR ARGHERE () will return .B \(dqn1000\(dq when the -.B -n +.B \-n option is parsed, but if the argument is just -.BR -n , +.BR \-n , .BR ARGHERE () will return .BR \(dqn\(dq , even if the next argument is .BR 1000 . .PP +The +.BR TESTLONG () +macro can be used to implement support for long +options (for example +.B \-\-long +or +.BR \-long ). +.I FLG +shall be the full string of the flag, and +.I WARG +shall be non-zero if and only if the option should +have a value. If the value must be specified in the +same argument (that is for example +.B \-\-long=value +rather than +.BR \-\-long\ value ), +.I FLG +must end with an equals sign +.RB ( = ) +in addition to a non-zero value on +.IR WARG . +Example: +.nf + + ARGBEGIN { + case 'x': + handle_dash_x: + /* handle \-x */ + break; + case '\-': + if (TESTLONG(\(dq\-\-xdev\(dq, 0)) + goto handle_dash_x; + else + usage(); + break; + default: + usage(); + } ARGEND; + +.fi +.I FLG +must not have side-effects, +.I WARG +should not have side-effects. +.PP +If your application have many long options with short +option aliases, the +.BR ARGMAPLONG () +macro can be used. +.I LONGOPTS +shall be a +.B struct longopt * +with multiple +.BR "struct longopt" s, +the end of the last shall be marked with a +.B struct longopt +where +.I long_flag +is +.BR NULL . +The +.BR ARGMAPLONG () +macro will test each entry, in order, using the +.BR TESTLONG () +macro with +.I long_flag +as +.I FLG +and with +.I with_arg +as +.IR WARG . +If the test passes, the keyword +.B break +is used to break the +.B case +(so the +.BR ARGMAPLONG () +macro shall be used directly in a +.B case +(or +.BR default ) +for +.IR ARGBEGIN , +.IR SUBARGBEGIN , +or +.IR ARGBEGIN4 , +and not inside a loop or inner +.BR switch ), +and in the next iteration of the argument parsing loop, +the flag will be the short flag consisting of the two +characters +.B long_flag[0] +and +.B short_flag +(in that order). Example: +.nf + + struct longopt map[] = { + {\(dq--long-a\(dq, 'a', 0}, + {\(dq--long-b\(dq, 'b', 0}, + {NULL, 0, 0} + } + ARGBEGIN { + case 'a': + /* handle \-a and \-\-long\-a */ + break; + case 'b': + /* handle \-b and \-\-long\-b */ + break; + case '\-': + ARGMAPLONG(map); + usage(); + default: + usage(); + } ARGEND; +.fi +.PP +NB! Long options with optional arguments should +have to map entries, one where +.I long_flag +ends with +.RB ' = ' +and +.B with_arg +is non-zero, and one where +.B long_flag +does not end with +.RB ' = ' +and +.B with_arg +is zero. These +.I cannot +have the same +.IR short_flag . +.PP If your application, does not recognise any options, but should still have like normal programs and support -.BR -- , +.BR \-\- , the macro .BR NOFLAGS () can be used instead of the -- cgit v1.2.3-70-g09d2