opt: add OPT_EARLY and opt_early_parse.
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 1 Dec 2011 06:14:51 +0000 (16:44 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 1 Dec 2011 06:14:51 +0000 (16:44 +1030)
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().

ccan/opt/opt.c
ccan/opt/opt.h
ccan/opt/parse.c
ccan/opt/private.h
ccan/opt/test/run-early.c [new file with mode: 0644]
ccan/opt/test/utils.c
ccan/opt/test/utils.h

index db7686be10eba44a08cec27c945aff7002a5b0fb..094b15c7e1b48c25aed1256aa741921ad8091f52 100644 (file)
@@ -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);
index e7896340d32a5fbf56988ca13b456f6c71597860..8bbc8f51d162c9e19c7c9bc1c480002899922719 100644 (file)
@@ -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))*), \
index b92bcb14871eed40e4873e8473ac1c359e46d5e7..5b2cec8b3d284e0b6795c87355d6ae00cf1290b9 100644 (file)
@@ -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) {
index f2199e48dc90b3581e376d91323a64d843fa2e2b..e4ad24a2be9e7947f862d295bef76b330c735c90 100644 (file)
@@ -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 */
diff --git a/ccan/opt/test/run-early.c b/ccan/opt/test/run-early.c
new file mode 100644 (file)
index 0000000..439ccab
--- /dev/null
@@ -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();
+}
index 7378d844de74f4be986ca9ce43ea2f5fe91717b4..c2967fca173d51010b810fcaa626eb6ebff420df 100644 (file)
@@ -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"),
index 33a2dbdc5e5ec1dc16ef8f3e9d531ba1cc5704b5..1c3658d7c7f04ce495b2e80dfd77c926110a8a96 100644 (file)
@@ -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);