]> git.ozlabs.org Git - ccan/blobdiff - ccan/opt/parse.c
opt: wean off getopt_long, beef up tests.
[ccan] / ccan / opt / parse.c
diff --git a/ccan/opt/parse.c b/ccan/opt/parse.c
new file mode 100644 (file)
index 0000000..228808d
--- /dev/null
@@ -0,0 +1,130 @@
+/* Actual code to parse commandline. */
+#include <ccan/opt/opt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "private.h"
+
+/* glibc does this as:
+/tmp/opt-example: invalid option -- 'x'
+/tmp/opt-example: unrecognized option '--long'
+/tmp/opt-example: option '--someflag' doesn't allow an argument
+/tmp/opt-example: option '--s' is ambiguous
+/tmp/opt-example: option requires an argument -- 's'
+*/
+static int parse_err(void (*errlog)(const char *fmt, ...),
+                    const char *argv0, const char *arg, unsigned len,
+                    const char *problem)
+{
+       errlog("%s: %.*s: %s", argv0, len, arg, problem);
+       return -1;
+}
+
+static void consume_option(int *argc, char *argv[], unsigned optnum)
+{
+       memmove(&argv[optnum], &argv[optnum+1],
+               sizeof(argv[optnum]) * (*argc-optnum));
+       (*argc)--;
+}
+
+/* Returns 1 if argument consumed, 0 if all done, -1 on error. */
+int parse_one(int *argc, char *argv[], unsigned *offset,
+             void (*errlog)(const char *fmt, ...))
+{
+       unsigned i, arg, len;
+       const char *o, *optarg = NULL;
+       char *problem;
+
+       if (getenv("POSIXLY_CORRECT")) {
+               /* Don't find options after non-options. */
+               arg = 1;
+       } else {
+               for (arg = 1; argv[arg]; arg++) {
+                       if (argv[arg][0] == '-')
+                               break;
+               }
+       }
+
+       if (!argv[arg] || argv[arg][0] != '-')
+               return 0;
+
+       /* Special arg terminator option. */
+       if (strcmp(argv[arg], "--") == 0) {
+               consume_option(argc, argv, arg);
+               return 0;
+       }
+
+       /* Long options start with -- */
+       if (argv[arg][1] == '-') {
+               assert(*offset == 0);
+               for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) {
+                       if (strncmp(argv[arg] + 2, o, len) != 0)
+                               continue;
+                       if (argv[arg][2 + len] == '=')
+                               optarg = argv[arg] + 2 + len + 1;
+                       else if (argv[arg][2 + len] != '\0')
+                               continue;
+                       break;
+               }
+               if (!o)
+                       return parse_err(errlog, argv[0],
+                                        argv[arg], strlen(argv[arg]),
+                                        "unrecognized option");
+               /* For error messages, we include the leading '--' */
+               o -= 2;
+               len += 2;
+       } else {
+               /* offset allows us to handle -abc */
+               for (o = first_sopt(&i); o; o = next_sopt(o, &i)) {
+                       if (argv[arg][*offset + 1] != *o)
+                               continue;
+                       (*offset)++;
+                       break;
+               }
+               if (!o)
+                       return parse_err(errlog, argv[0],
+                                        argv[arg], strlen(argv[arg]),
+                                        "unrecognized option");
+               /* For error messages, we include the leading '-' */
+               o--;
+               len = 2;
+       }
+
+       if (opt_table[i].type == 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].arg);
+       } else {
+               if (!optarg) {
+                       /* Swallow any short options as optarg, eg -afile */
+                       if (*offset && argv[arg][*offset + 1]) {
+                               optarg = argv[arg] + *offset + 1;
+                               *offset = 0;
+                       } else
+                               optarg = argv[arg+1];
+               }
+               if (!optarg)
+                       return parse_err(errlog, argv[0], o, len,
+                                        "requires an argument");
+               problem = opt_table[i].cb_arg(optarg, opt_table[i].arg);
+       }
+
+       if (problem) {
+               parse_err(errlog, argv[0], o, len, problem);
+               free(problem);
+               return -1;
+       }
+
+       /* If no more letters in that short opt, reset offset. */
+       if (*offset && !argv[arg][*offset + 1])
+               *offset = 0;
+
+       /* All finished with that option? */
+       if (*offset == 0) {
+               consume_option(argc, argv, arg);
+               if (optarg && optarg == argv[arg])
+                       consume_option(argc, argv, arg);
+       }
+       return 1;
+}