Browse Source

opt: add OPT_EARLY and opt_early_parse.

Parsing options like --verbose and --debug can be a pain.  You need to
have everything set up before invoking parse_args(), but that may be a
significant amount of work, for which you may want verbose or
debugging enabled.

Thus the concept of "early" args: you can nominate arguments to be
parse before anything else, using opt_early_parse().
Rusty Russell 14 years ago
parent
commit
50212d0d27
7 changed files with 244 additions and 20 deletions
  1. 25 2
      ccan/opt/opt.c
  2. 109 12
      ccan/opt/opt.h
  3. 8 5
      ccan/opt/parse.c
  4. 1 1
      ccan/opt/private.h
  5. 77 0
      ccan/opt/test/run-early.c
  6. 23 0
      ccan/opt/test/utils.c
  7. 1 0
      ccan/opt/test/utils.h

+ 25 - 2
ccan/opt/opt.c

@@ -107,7 +107,9 @@ static void check_opt(const struct opt_table *entry)
 	const char *p;
 	unsigned len;
 
-	if (entry->type != OPT_HASARG && entry->type != OPT_NOARG)
+	if (entry->type != OPT_HASARG && entry->type != OPT_NOARG
+	    && entry->type != (OPT_EARLY|OPT_HASARG)
+	    && entry->type != (OPT_EARLY|OPT_NOARG))
 		errx(1, "Option %s: unknown entry type %u",
 		     entry->names, entry->type);
 
@@ -196,7 +198,28 @@ bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...))
 	/* This helps opt_usage. */
 	opt_argv0 = argv[0];
 
-	while ((ret = parse_one(argc, argv, &offset, errlog)) == 1);
+	while ((ret = parse_one(argc, argv, 0, &offset, errlog)) == 1);
+
+	/* parse_one returns 0 on finish, -1 on error */
+	return (ret == 0);
+}
+
+bool opt_early_parse(int argc, char *argv[],
+		     void (*errlog)(const char *fmt, ...))
+{
+	int ret;
+	unsigned off = 0;
+	char **tmpargv = malloc(sizeof(argv[0]) * (argc + 1));
+
+	/* We could avoid a copy and skip instead, but this is simple. */
+	memcpy(tmpargv, argv, sizeof(argv[0]) * (argc + 1));
+
+	/* This helps opt_usage. */
+	opt_argv0 = argv[0];
+
+	while ((ret = parse_one(&argc, tmpargv, OPT_EARLY, &off, errlog)) == 1);
+
+	free(tmpargv);
 
 	/* parse_one returns 0 on finish, -1 on error */
 	return (ret == 0);

+ 109 - 12
ccan/opt/opt.h

@@ -31,7 +31,7 @@ struct opt_table;
  *	OPT_WITH_ARG()
  */
 #define OPT_WITHOUT_ARG(names, cb, arg, desc)	\
-	{ (names), OPT_CB_NOARG((cb), (arg)), { (arg) }, (desc) }
+	{ (names), OPT_CB_NOARG((cb), 0, (arg)), { (arg) }, (desc) }
 
 /**
  * OPT_WITH_ARG() - macro for initializing an opt_table entry (with arg)
@@ -66,7 +66,7 @@ struct opt_table;
  *	OPT_WITHOUT_ARG()
  */
 #define OPT_WITH_ARG(name, cb, show, arg, desc)	\
-	{ (name), OPT_CB_ARG((cb), (show), (arg)), { (arg) }, (desc) }
+	{ (name), OPT_CB_ARG((cb), 0, (show), (arg)), { (arg) }, (desc) }
 
 /**
  * OPT_SUBTABLE() - macro for including another table inside a table.
@@ -78,6 +78,39 @@ struct opt_table;
 	  sizeof(_check_is_entry(table)) ? NULL : NULL, NULL, NULL,	\
 	  { NULL }, (desc) }
 
+/**
+ * OPT_EARLY_WITHOUT_ARG() - macro for a early opt_table entry (without arg)
+ * @names: the names of the option eg. "--foo", "-f" or "--foo|-f|--foobar".
+ * @cb: the callback when the option is found.
+ * @arg: the argument to hand to @cb.
+ * @desc: the description for opt_usage(), or opt_hidden.
+ *
+ * This is the same as OPT_WITHOUT_ARG, but for opt_early_parse() instead of
+ * opt_parse().
+ *
+ * See Also:
+ *	OPT_EARLY_WITH_ARG(), opt_early_parse()
+ */
+#define OPT_EARLY_WITHOUT_ARG(names, cb, arg, desc)	\
+	{ (names), OPT_CB_NOARG((cb), OPT_EARLY, (arg)), { (arg) }, (desc) }
+
+/**
+ * OPT_EARLY_WITH_ARG() - macro for an early opt_table entry (with arg)
+ * @names: the option names eg. "--foo=<arg>", "-f" or "-f|--foo <arg>".
+ * @cb: the callback when the option is found (along with <arg>).
+ * @show: the callback to print the value in get_usage (or NULL)
+ * @arg: the argument to hand to @cb and @show
+ * @desc: the description for opt_usage(), or opt_hidden.
+ *
+ * This is the same as OPT_WITH_ARG, but for opt_early_parse() instead of
+ * opt_parse().
+ *
+ * See Also:
+ *	OPT_EARLY_WITHOUT_ARG(), opt_early_parse()
+ */
+#define OPT_EARLY_WITH_ARG(name, cb, show, arg, desc)	\
+	{ (name), OPT_CB_ARG((cb), OPT_EARLY, (show), (arg)), { (arg) }, (desc) }
+
 /**
  * OPT_ENDTABLE - macro to create final entry in table.
  *
@@ -129,7 +162,7 @@ void opt_register_table(const struct opt_table *table, const char *desc);
  * string and return false.
  */
 #define opt_register_noarg(names, cb, arg, desc)			\
-	_opt_register((names), OPT_CB_NOARG((cb), (arg)), (arg), (desc))
+	_opt_register((names), OPT_CB_NOARG((cb), 0, (arg)), (arg), (desc))
 
 /**
  * opt_register_arg - register an option with an arguments
@@ -160,7 +193,40 @@ void opt_register_table(const struct opt_table *table, const char *desc);
  *	opt_register_arg("--explode|--boom", explode, NULL, NULL, opt_hidden);
  */
 #define opt_register_arg(names, cb, show, arg, desc)			\
-	_opt_register((names), OPT_CB_ARG((cb), (show), (arg)), (arg), (desc))
+	_opt_register((names), OPT_CB_ARG((cb),0,(show), (arg)), (arg), (desc))
+
+/**
+ * opt_register_early_noarg - register an early option with no arguments
+ * @names: the names of the option eg. "--foo", "-f" or "--foo|-f|--foobar".
+ * @cb: the callback when the option is found.
+ * @arg: the argument to hand to @cb.
+ * @desc: the verbose description of the option (for opt_usage()), or NULL.
+ *
+ * This is the same as opt_register_noarg(), but for opt_early_parse().
+ *
+ * See Also:
+ *	opt_register_early_arg(), opt_early_parse()
+ */
+#define opt_register_early_noarg(names, cb, arg, desc)			\
+	_opt_register((names), OPT_CB_NOARG((cb), OPT_EARLY, (arg)),	\
+		      (arg), (desc))
+
+/**
+ * opt_register_early_arg - register an early option with an arguments
+ * @names: the names of the option eg. "--foo", "-f" or "--foo|-f|--foobar".
+ * @cb: the callback when the option is found.
+ * @show: the callback to print the value in get_usage (or NULL)
+ * @arg: the argument to hand to @cb.
+ * @desc: the verbose description of the option (for opt_usage()), or NULL.
+ *
+ * This is the same as opt_register_arg(), but for opt_early_parse().
+ *
+ * See Also:
+ *	opt_register_early_noarg(), opt_early_parse()
+ */
+#define opt_register_early_arg(names, cb, show, arg, desc)		\
+	_opt_register((names), OPT_CB_ARG((cb), OPT_EARLY, (show),(arg)), \
+		      (arg), (desc))
 
 /**
  * opt_parse - parse arguments.
@@ -169,8 +235,9 @@ void opt_register_table(const struct opt_table *table, const char *desc);
  * @errlog: the function to print errors
  *
  * This iterates through the command line and calls callbacks registered with
- * opt_register_table()/opt_register_arg()/opt_register_noarg().  As this
- * occurs successfully each option is removed from argc and argv.
+ * opt_register_arg()/opt_register_noarg() or OPT_WITHOUT_ARG/OPT_WITH_ARG
+ * entries in tables registered with opt_register_table().  As this occurs
+ * each option is removed from argc and argv.
  *
  * If there are unknown options, missing arguments or a callback
  * returns false, then an error message is printed and false is
@@ -186,10 +253,39 @@ void opt_register_table(const struct opt_table *table, const char *desc);
  *	}
  *
  * See Also:
- *	opt_log_stderr, opt_log_stderr_exit
+ *	opt_log_stderr, opt_log_stderr_exit, opt_early_parse()
  */
 bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...));
 
+/**
+ * opt_early_parse - parse early arguments.
+ * @argc: argc
+ * @argv: argv array.
+ * @errlog: the function to print errors
+ *
+ * There are times when you want to parse some arguments before any other
+ * arguments; this is especially important for debugging flags (eg. --verbose)
+ * when you have complicated callbacks in option processing.
+ *
+ * You can use opt_early_parse() to only parse options registered with
+ * opt_register_earlyarg()/opt_register_early_noarg() or
+ * OPT_EARLY_WITHOUT_ARG/OPT_EARLY_WITH_ARG entries in tables registered with
+ * opt_register_table().
+ *
+ * Note that unlike opt_parse(), argc and argv are not altered.
+ *
+ * Example:
+ *	if (!opt_early_parse(argc, argv, opt_log_stderr)) {
+ *		printf("You screwed up, aborting!\n");
+ *		exit(1);
+ *	}
+ *
+ * See Also:
+ *	opt_parse()
+ */
+bool opt_early_parse(int argc, char *argv[],
+		     void (*errlog)(const char *fmt, ...));
+
 /**
  * opt_free_table - reset the opt library.
  *
@@ -337,7 +433,8 @@ enum opt_type {
 	OPT_NOARG = 1,		/* -f|--foo */
 	OPT_HASARG = 2,		/* -f arg|--foo=arg|--foo arg */
 	OPT_SUBTABLE = 4,	/* Actually, longopt points to a subtable... */
-	OPT_END = 8,		/* End of the table. */
+	OPT_EARLY = 8,		/* Parse this from opt_early_parse() only. */
+	OPT_END = 16,		/* End of the table. */
 };
 
 struct opt_table {
@@ -355,8 +452,8 @@ struct opt_table {
 };
 
 /* Resolves to the four parameters for non-arg callbacks. */
-#define OPT_CB_NOARG(cb, arg)				\
-	OPT_NOARG,					\
+#define OPT_CB_NOARG(cb, pre, arg)			\
+	OPT_NOARG|(pre),				\
 	typesafe_cb_cast3(char *(*)(void *),	\
 			  char *(*)(typeof(*(arg))*),	\
 			  char *(*)(const typeof(*(arg))*),	\
@@ -364,8 +461,8 @@ struct opt_table {
 	NULL, NULL
 
 /* Resolves to the four parameters for arg callbacks. */
-#define OPT_CB_ARG(cb, show, arg)					\
-	OPT_HASARG, NULL,						\
+#define OPT_CB_ARG(cb, pre, show, arg)					\
+	OPT_HASARG|(pre), NULL,						\
 	typesafe_cb_cast3(char *(*)(const char *,void *),	\
 			  char *(*)(const char *, typeof(*(arg))*),	\
 			  char *(*)(const char *, const typeof(*(arg))*), \

+ 8 - 5
ccan/opt/parse.c

@@ -29,12 +29,12 @@ static void consume_option(int *argc, char *argv[], unsigned optnum)
 }
 
 /* Returns 1 if argument consumed, 0 if all done, -1 on error. */
-int parse_one(int *argc, char *argv[], unsigned *offset,
+int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset,
 	      void (*errlog)(const char *fmt, ...))
 {
 	unsigned i, arg, len;
 	const char *o, *optarg = NULL;
-	char *problem;
+	char *problem = NULL;
 
 	if (getenv("POSIXLY_CORRECT")) {
 		/* Don't find options after non-options. */
@@ -91,11 +91,12 @@ int parse_one(int *argc, char *argv[], unsigned *offset,
 		len = 2;
 	}
 
-	if (opt_table[i].type == OPT_NOARG) {
+	if ((opt_table[i].type & ~OPT_EARLY) == OPT_NOARG) {
 		if (optarg)
 			return parse_err(errlog, argv[0], o, len,
 					 "doesn't allow an argument");
-		problem = opt_table[i].cb(opt_table[i].u.arg);
+		if ((opt_table[i].type & OPT_EARLY) == is_early)
+			problem = opt_table[i].cb(opt_table[i].u.arg);
 	} else {
 		if (!optarg) {
 			/* Swallow any short options as optarg, eg -afile */
@@ -108,7 +109,9 @@ int parse_one(int *argc, char *argv[], unsigned *offset,
 		if (!optarg)
 			return parse_err(errlog, argv[0], o, len,
 					 "requires an argument");
-		problem = opt_table[i].cb_arg(optarg, opt_table[i].u.arg);
+		if ((opt_table[i].type & OPT_EARLY) == is_early)
+			problem = opt_table[i].cb_arg(optarg,
+						      opt_table[i].u.arg);
 	}
 
 	if (problem) {

+ 1 - 1
ccan/opt/private.h

@@ -14,7 +14,7 @@ const char *next_sopt(const char *names, unsigned *i);
 const char *first_lopt(unsigned *i, unsigned *len);
 const char *next_lopt(const char *p, unsigned *i, unsigned *len);
 
-int parse_one(int *argc, char *argv[], unsigned *offset,
+int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset,
 	      void (*errlog)(const char *fmt, ...));
 
 #endif /* CCAN_OPT_PRIVATE_H */

+ 77 - 0
ccan/opt/test/run-early.c

@@ -0,0 +1,77 @@
+/* With errlog == NULL, we never get a "failure". */
+#include <ccan/tap/tap.h>
+#include <stdlib.h>
+#include <ccan/opt/opt.c>
+#include <ccan/opt/usage.c>
+#include <ccan/opt/helpers.c>
+#include <ccan/opt/parse.c>
+#include "utils.h"
+
+struct opt_table some_early_table[] = {
+	OPT_EARLY_WITHOUT_ARG("--verbose|-v", test_noarg,
+			      "vvv", "Description of verbose"),
+	OPT_EARLY_WITH_ARG("--debug|-d", test_arg, show_arg,
+			      "ddd", "Description of debug"),
+	OPT_WITHOUT_ARG("-h|--hhh", test_noarg, "hhh", "Description of hhh"),
+	OPT_ENDTABLE
+};
+
+int main(int argc, char *argv[])
+{
+	const char *myname = argv[0];
+
+	plan_tests(37);
+
+	/* Simple short arg.*/
+	opt_register_noarg("-a", test_noarg, NULL, "All");
+	opt_register_early_noarg("-b", test_noarg, NULL, "All");
+
+	/* Early parsing doesn't mangle. */
+	ok1(parse_early_args(&argc, &argv, "-a", NULL));
+	ok1(argc == 2);
+	ok1(argv[0] == myname);
+	ok1(strcmp(argv[1], "-a") == 0);
+	ok1(argv[2] == NULL);
+	ok1(test_cb_called == 0);
+
+	/* ... even if it processes arg. */
+	ok1(parse_early_args(&argc, &argv, "-b", NULL));
+	ok1(argc == 2);
+	ok1(argv[0] == myname);
+	ok1(strcmp(argv[1], "-b") == 0);
+	ok1(argv[2] == NULL);
+	ok1(test_cb_called == 1);
+
+	ok1(parse_early_args(&argc, &argv, "-ab", NULL));
+	ok1(argc == 2);
+	ok1(argv[0] == myname);
+	ok1(strcmp(argv[1], "-ab") == 0);
+	ok1(argv[2] == NULL);
+	ok1(test_cb_called == 2);
+
+	ok1(parse_args(&argc, &argv, "-ab", NULL));
+	ok1(argc == 1);
+	ok1(argv[0] == myname);
+	ok1(argv[1] == NULL);
+	ok1(test_cb_called == 3);
+
+	opt_register_table(some_early_table, "Some early args");
+	ok1(parse_early_args(&argc, &argv, "--verbose", "-dddd", "-h", NULL));
+	ok1(argc == 4);
+	ok1(argv[0] == myname);
+	ok1(strcmp(argv[1], "--verbose") == 0);
+	ok1(strcmp(argv[2], "-dddd") == 0);
+	ok1(strcmp(argv[3], "-h") == 0);
+	ok1(argv[4] == NULL);
+	ok1(test_cb_called == 5);
+
+	ok1(parse_args(&argc, &argv, "--verbose", "-d", "ddd", "-h", NULL));
+	ok1(argc == 1);
+	ok1(argv[0] == myname);
+	ok1(argv[1] == NULL);
+	ok1(test_cb_called == 6);
+
+	/* parse_args allocates argv */
+	free(argv);
+	return exit_status();
+}

+ 23 - 0
ccan/opt/test/utils.c

@@ -80,6 +80,29 @@ bool parse_args(int *argc, char ***argv, ...)
 	return opt_parse(argc, *argv, save_err_output);
 }
 
+bool parse_early_args(int *argc, char ***argv, ...)
+{
+	char **a;
+	va_list ap;
+
+	va_start(ap, argv);
+	*argc = 1;
+	a = malloc(sizeof(*a) * (*argc + 1));
+	a[0] = (*argv)[0];
+	while ((a[*argc] = va_arg(ap, char *)) != NULL) {
+		(*argc)++;
+		a = realloc(a, sizeof(*a) * (*argc + 1));
+	}
+
+	if (allocated)
+		free(*argv);
+
+	*argv = a;
+	allocated = true;
+
+	return opt_early_parse(*argc, *argv, save_err_output);
+}
+
 struct opt_table short_table[] = {
 	/* Short opts, different args. */
 	OPT_WITHOUT_ARG("-a", test_noarg, "a", "Description of a"),

+ 1 - 0
ccan/opt/test/utils.h

@@ -4,6 +4,7 @@
 #include <stdbool.h>
 
 bool parse_args(int *argc, char ***argv, ...);
+bool parse_early_args(int *argc, char ***argv, ...);
 extern char *err_output;
 void save_err_output(const char *fmt, ...);
 void reset_options(void);