]> git.kaiwu.me - klib.git/commitdiff
portable getopt_long()
authorHeng Li <lh3@me.com>
Wed, 29 Aug 2018 10:08:28 +0000 (00:08 -1000)
committerHeng Li <lh3@me.com>
Wed, 29 Aug 2018 10:08:28 +0000 (00:08 -1000)
ketopt.h [new file with mode: 0644]
test/ketopt_test.c [new file with mode: 0644]

diff --git a/ketopt.h b/ketopt.h
new file mode 100644 (file)
index 0000000..1944b09
--- /dev/null
+++ b/ketopt.h
@@ -0,0 +1,95 @@
+#ifndef KETOPT_H
+#define KETOPT_H
+
+#include <string.h> // for strchr() and strncmp()
+
+#define ko_no_argument 0
+#define ko_required_argument 1
+#define ko_optional_argument 2
+
+typedef struct {
+       int ind;   // equivalent to optind
+       int opt;   // equivalent to optopt
+       char *arg; // equivalent to optarg
+       // private variables not intended for external uses
+       int i, pos, n_args;
+} ketopt_t;
+
+typedef struct {
+       char *name;
+       int has_arg;
+       int val;
+} ko_longopt_t;
+
+static ketopt_t KETOPT_INIT = { 1, 0, 0, 1, 0, 0 };
+
+static void ketopt_permute(char *argv[], int j, int n) // move argv[j] over n elements to the left
+{
+       int k;
+       char *p = argv[j];
+       for (k = 0; k < n; ++k)
+               argv[j - k] = argv[j - k - 1];
+       argv[j - k] = p;
+}
+
+static int ketopt(ketopt_t *s, int argc, char *argv[], int permute, const char *ostr, const ko_longopt_t *longopts)
+{
+       int opt = -1, i0, j;
+       if (permute) {
+               while (s->i < argc && (argv[s->i][0] != '-' || argv[s->i][1] == '\0'))
+                       ++s->i, ++s->n_args;
+       }
+       s->arg = 0, i0 = s->i;
+       if (s->i >= argc || argv[s->i][0] != '-' || argv[s->i][1] == '\0') {
+               s->ind = s->i - s->n_args;
+               return -1;
+       }
+       if (argv[s->i][0] == '-' && argv[s->i][1] == '-') { // "--" or a long option
+               if (argv[s->i][2] == '\0') { // a bare "--"
+                       ketopt_permute(argv, s->i, s->n_args);
+                       ++s->i, s->ind = s->i - s->n_args;
+                       return -1;
+               }
+               s->opt = 0, opt = '?', s->pos = -1;
+               if (longopts) { // parse long options
+                       int k, n_matches = 0, match = -1;
+                       const ko_longopt_t *o = 0;
+                       for (j = 2; argv[s->i][j] != '\0' && argv[s->i][j] != '='; ++j) {} // find the end of the option name
+                       for (k = 0; longopts[k].name != 0; ++k)
+                               if (strncmp(&argv[s->i][2], longopts[k].name, j - 2) == 0)
+                                       ++n_matches, o = &longopts[k];
+                       if (n_matches == 1) {
+                               s->opt = opt = o->val;
+                               if (argv[s->i][j] == '=') s->arg = &argv[s->i][j + 1];
+                               if (o->has_arg == 1 && argv[s->i][j] == '\0') {
+                                       if (s->i < argc - 1) s->arg = argv[++s->i];
+                                       else opt = ':'; // missing option argument
+                               }
+                       }
+               }
+       } else { // a short option
+               char *p;
+               if (s->pos == 0) s->pos = 1;
+               opt = s->opt = argv[s->i][s->pos++];
+               p = strchr(ostr, opt);
+               if (p == 0) {
+                       opt = '?'; // unknown option
+               } else if (p[1] == ':') {
+                       if (argv[s->i][s->pos] == 0) {
+                               if (s->i < argc - 1) s->arg = argv[++s->i];
+                               else opt = ':'; // missing option argument
+                       } else s->arg = &argv[s->i][s->pos];
+                       s->pos = -1;
+               }
+       }
+       if (s->pos < 0 || argv[s->i][s->pos] == 0) {
+               ++s->i, s->pos = 0;
+               if (s->n_args > 0) // permute
+                       for (j = i0; j < s->i; ++j)
+                               ketopt_permute(argv, j, s->n_args);
+       }
+       s->ind = s->i - s->n_args;
+       return opt;
+}
+
+#endif
diff --git a/test/ketopt_test.c b/test/ketopt_test.c
new file mode 100644 (file)
index 0000000..d984c21
--- /dev/null
@@ -0,0 +1,80 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include "ketopt.h"
+
+// -x xz -y -x -- xz -x
+// -xxy1 xz
+// -y -x xz
+
+static void test_opt(int c, int opt, const char *arg)
+{
+       if (c == 'x') fprintf(stderr, "-x\n");
+       else if (c == 'y') fprintf(stderr, "-y %s\n", arg);
+       else if (c == 'u') fprintf(stderr, "--foo\n");
+       else if (c == 'v') fprintf(stderr, "--bar=%s\n", arg);
+       else if (c == 'w') fprintf(stderr, "--opt=%s\n", arg? arg : "(null)");
+       else if (c == '?') fprintf(stderr, "unknown option -%c\n", opt? opt : ':');
+       else if (c == ':') fprintf(stderr, "missing option argument: -%c\n", opt? opt : ':');
+}
+
+static void print_cmd(int argc, char *argv[], int ind)
+{
+       int i;
+       fprintf(stderr, "CMD: %s", argv[0]);
+       if (ind > 1) {
+               fputs(" [", stderr);
+               for (i = 1; i < ind; ++i) {
+                       if (i != 1) fputc(' ', stderr);
+                       fputs(argv[i], stderr);
+               }
+               fputc(']', stderr);
+       }
+       for (i = ind; i < argc; ++i)
+               fprintf(stderr, " %s", argv[i]);
+       fputc('\n', stderr);
+}
+
+static void test_ketopt(int argc, char *argv[])
+{
+       static ko_longopt_t longopts[] = {
+               { "foo", ko_no_argument,       'u' },
+               { "bar", ko_required_argument, 'v' },
+               { "opt", ko_optional_argument, 'w' },
+               { NULL, 0, 0 }
+       };
+       ketopt_t opt = KETOPT_INIT;
+       int i, c;
+       fprintf(stderr, "===> ketopt() <===\n");
+       while ((c = ketopt(&opt, argc, argv, 1, "xy:", longopts)) >= 0)
+               test_opt(c, opt.opt, opt.arg);
+       print_cmd(argc, argv, opt.ind);
+}
+
+static void test_getopt(int argc, char *argv[])
+{
+       static struct option long_options[] = {
+               { "foo", no_argument,       0, 'u' },
+               { "bar", required_argument, 0, 'v' },
+               { "opt", optional_argument, 0, 'w' },
+               {0, 0, 0, 0}
+       };
+       int i, c, option_index;
+       fprintf(stderr, "===> getopt() <===\n");
+       opterr = 0;
+       while ((c = getopt_long(argc, argv, ":xy:", long_options, &option_index)) >= 0)
+               test_opt(c, optopt, optarg);
+       print_cmd(argc, argv, optind);
+}
+
+int main(int argc, char *argv[])
+{
+       int i;
+       char **argv2;
+       argv2 = (char**)malloc(sizeof(char*) * argc);
+       for (i = 0; i < argc; ++i) argv2[i] = argv[i];
+       test_ketopt(argc, argv);
+       test_getopt(argc, argv2);
+       free(argv2);
+       return 0;
+}