aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2018-11-11 23:21:53 +0100
committerMattias Andrée <maandree@kth.se>2018-11-11 23:21:53 +0100
commitda5c0ef8dabe2fa0577bc0ccc56c5e2744a05851 (patch)
tree1b6300f5b4465abee480d07399401bccd14798f6
parentDocument most of libsimple-arg.h (diff)
downloadlibsimple-da5c0ef8dabe2fa0577bc0ccc56c5e2744a05851.tar.gz
libsimple-da5c0ef8dabe2fa0577bc0ccc56c5e2744a05851.tar.bz2
libsimple-da5c0ef8dabe2fa0577bc0ccc56c5e2744a05851.tar.xz
Document libsimple-arg.h and add support optional arguments on long options and add KEEP_DASHDASH to ARGBEGIN3 (now named ARGBEGIN4)
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--libsimple-arg.h102
-rw-r--r--man0/libsimple-arg.h.0226
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 <stdlib.h>
#include <string.h>
+/* 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 <libsimple-arg.h>
+#include <libsimple\-arg.h>
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 <libsimple-arg.h>
+.I <libsimple\-arg.h>
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