From 7feaeb1483b2c651898d179c6c51ea0b2a3ab590 Mon Sep 17 00:00:00 2001
From: Yorhel <git@yorhel.nl>
Date: Sat, 8 Sep 2012 14:38:15 +0200
Subject: [PATCH] Abstracted option parsing from option handling

This also adds the possibility to combine short options that expect an
argument, e.g. "-xo <file>" or even "-xo<file>".
---
 Makefile.am |   3 +-
 src/main.c  | 114 ++++++++++++++++---------------
 src/yopt.h  | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 254 insertions(+), 55 deletions(-)
 create mode 100644 src/yopt.h

diff --git a/Makefile.am b/Makefile.am
index 4463395..efb65b9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -25,7 +25,8 @@ noinst_HEADERS=\
 	src/help.h\
 	src/khash.h\
 	src/path.h\
-	src/util.h
+	src/util.h\
+	src/yopt.h
 
 
 man_MANS=ncdu.1
diff --git a/src/main.c b/src/main.c
index 3aed380..71b7546 100644
--- a/src/main.c
+++ b/src/main.c
@@ -34,6 +34,8 @@
 #include <sys/time.h>
 #include <locale.h>
 
+#include "yopt.h"
+
 
 int pstate;
 int read_only = 0;
@@ -103,66 +105,70 @@ int input_handle(int wait) {
 
 /* parse command line */
 static void argv_parse(int argc, char **argv) {
-  int i, j, len;
+  yopt_t yopt;
+  int v;
+  char *val;
   char *export = NULL;
   char *import = NULL;
   char *dir = NULL;
+
+  static yopt_opt_t opts[] = {
+    { 'h', 0, "-h,-?" },
+    { 'q', 0, "-q" },
+    { 'v', 0, "-v" },
+    { 'x', 0, "-x" },
+    { 'r', 0, "-r" },
+    { 'o', 1, "-o" },
+    { 'f', 1, "-f" },
+    { '0', 0, "-0" },
+    { '1', 0, "-1" },
+    { '2', 0, "-2" },
+    {  1,  1, "--exclude" },
+    { 'X', 1, "-X,--exclude-from" },
+    {0,0,NULL}
+  };
+
   dir_ui = -1;
 
-  /* read from commandline */
-  for(i=1; i<argc; i++) {
-    if(argv[i][0] == '-') {
-      /* flags requiring arguments */
-      if(!strcmp(argv[i], "-X") || !strcmp(argv[i], "-o") || !strcmp(argv[i], "-f")
-          || !strcmp(argv[i], "--exclude-from") || !strcmp(argv[i], "--exclude")) {
-        if(i+1 >= argc) {
-          printf("Option %s requires an argument\n", argv[i]);
-          exit(1);
-        } else if(strcmp(argv[i], "-o") == 0)
-          export = argv[++i];
-        else if(strcmp(argv[i], "-f") == 0)
-          import = argv[++i];
-        else if(strcmp(argv[i], "--exclude") == 0)
-          exclude_add(argv[++i]);
-        else if(exclude_addfile(argv[++i])) {
-          printf("Can't open %s: %s\n", argv[i], strerror(errno));
-          exit(1);
-        }
-        continue;
+  yopt_init(&yopt, argc, argv, opts);
+  while((v = yopt_next(&yopt, &val)) != -1) {
+    switch(v) {
+    case  0 : dir = val; break;
+    case 'h':
+      printf("ncdu <options> <directory>\n\n");
+      printf("  -h                         This help message\n");
+      printf("  -q                         Quiet mode, refresh interval 2 seconds\n");
+      printf("  -v                         Print version\n");
+      printf("  -x                         Same filesystem\n");
+      printf("  -r                         Read only\n");
+      printf("  -o FILE                    Export scanned directory to FILE\n");
+      printf("  -f FILE                    Import scanned directory from FILE\n");
+      printf("  -0,-1,-2                   UI to use when scanning (0=none,2=full ncurses)\n");
+      printf("  --exclude PATTERN          Exclude files that match PATTERN\n");
+      printf("  -X, --exclude-from FILE    Exclude files that match any pattern in FILE\n");
+      exit(0);
+    case 'q': update_delay = 2000; break;
+    case 'v':
+      printf("ncdu %s\n", PACKAGE_VERSION);
+      exit(0);
+    case 'x': dir_scan_smfs = 1; break;
+    case 'r': read_only = 1; break;
+    case 'o': export = val; break;
+    case 'f': import = val; break;
+    case '0': dir_ui = 0; break;
+    case '1': dir_ui = 1; break;
+    case '2': dir_ui = 2; break;
+    case  1 : exclude_add(val); break; /* --exclude */
+    case 'X':
+      if(exclude_addfile(val)) {
+        printf("Can't open %s: %s\n", val, strerror(errno));
+        exit(1);
       }
-      /* short flags */
-      len = strlen(argv[i]);
-      for(j=1; j<len; j++)
-        switch(argv[i][j]) {
-          case '0': dir_ui = 0; break;
-          case '1': dir_ui = 1; break;
-          case '2': dir_ui = 2; break;
-          case 'x': dir_scan_smfs = 1; break;
-          case 'r': read_only = 1; break;
-          case 'q': update_delay = 2000;     break;
-          case '?':
-          case 'h':
-            printf("ncdu <options> <directory>\n\n");
-            printf("  -h                         This help message\n");
-            printf("  -q                         Quiet mode, refresh interval 2 seconds\n");
-            printf("  -v                         Print version\n");
-            printf("  -x                         Same filesystem\n");
-            printf("  -r                         Read only\n");
-            printf("  -o FILE                    Export scanned directory to FILE\n");
-            printf("  -f FILE                    Import scanned directory from FILE\n");
-            printf("  -0,-1,-2                   UI to use when scanning (0=none,2=full ncurses)\n");
-            printf("  --exclude PATTERN          Exclude files that match PATTERN\n");
-            printf("  -X, --exclude-from FILE    Exclude files that match any pattern in FILE\n");
-            exit(0);
-          case 'v':
-            printf("ncdu %s\n", PACKAGE_VERSION);
-            exit(0);
-          default:
-            printf("Unknown option: -%c\nSee '%s -h' for more information.\n", argv[i][j], argv[0]);
-            exit(1);
-        }
-    } else
-      dir = argv[i];
+      break;
+    case -2:
+      printf("ncdu: %s.\n", val);
+      exit(1);
+    }
   }
 
   if(export) {
diff --git a/src/yopt.h b/src/yopt.h
new file mode 100644
index 0000000..86ee1b9
--- /dev/null
+++ b/src/yopt.h
@@ -0,0 +1,192 @@
+/* ncdu - NCurses Disk Usage
+
+  Copyright (c) 2007-2012 Yoran Heling
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+/* This is a simple command-line option parser. Operation is similar to
+ * getopt_long(), except with a cleaner API.
+ *
+ * This is implemented in a single header file, as it's pretty small and you
+ * generally only use an option parser in a single .c file in your program.
+ *
+ * Supports (examples from GNU tar(1)):
+ *   "--gzip"
+ *   "--file <arg>"
+ *   "--file=<arg>"
+ *   "-z"
+ *   "-f <arg>"
+ *   "-f<arg>"
+ *   "-zf <arg>"
+ *   "-zf<arg>"
+ *   "--" (To stop looking for futher options)
+ *   "<arg>" (Non-option arguments)
+ *
+ * Issues/non-features:
+ * - An option either requires an argument or it doesn't.
+ * - No way to specify how often an option can/should be used.
+ * - No way to specify the type of an argument (filename/integer/enum/whatever)
+ */
+
+
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+
+typedef struct {
+  /* Value yopt_next() will return for this option */
+  int val;
+  /* Whether this option needs an argument */
+  int needarg;
+  /* Name(s) of this option, prefixed with '-' or '--' and separated by a
+   * comma. E.g. "-z", "--gzip", "-z,--gzip".
+   * An option can have any number of aliasses.
+   */
+  const char *name;
+} yopt_opt_t;
+
+
+typedef struct {
+  int argc;
+  int cur;
+  int argsep; /* '--' found */
+  char **argv;
+  char *sh;
+  yopt_opt_t *opts;
+  char errbuf[128];
+} yopt_t;
+
+
+/* opts must be an array of options, terminated with an option with val=0 */
+static inline void yopt_init(yopt_t *o, int argc, char **argv, yopt_opt_t *opts) {
+  o->argc = argc;
+  o->argv = argv;
+  o->opts = opts;
+  o->cur = 0;
+  o->argsep = 0;
+  o->sh = NULL;
+}
+
+
+static inline yopt_opt_t *_yopt_find(yopt_opt_t *o, const char *v) {
+  const char *tn, *tv;
+
+  for(; o->val; o++) {
+    tn = o->name;
+    while(*tn) {
+      tv = v;
+      while(*tn && *tn != ',' && *tv && *tv != '=' && *tn == *tv) {
+        tn++;
+        tv++;
+      }
+      if(!(*tn && *tn != ',') && !(*tv && *tv != '='))
+        return o;
+      while(*tn && *tn != ',')
+        tn++;
+      while(*tn == ',')
+        tn++;
+    }
+  }
+
+  return NULL;
+}
+
+
+static inline int _yopt_err(yopt_t *o, char **val, const char *fmt, ...) {
+  va_list va;
+  va_start(va, fmt);
+  vsnprintf(o->errbuf, sizeof(o->errbuf), fmt, va);
+  va_end(va);
+  *val = o->errbuf;
+  return -2;
+}
+
+
+/* Return values:
+ *  0 -> Non-option argument, val is its value
+ * -1 -> Last argument has been processed
+ * -2 -> Error, val will contain the error message.
+ *  x -> Option with val = x found. If the option requires an argument, its
+ *       value will be in val.
+ */
+static inline int yopt_next(yopt_t *o, char **val) {
+  yopt_opt_t *opt;
+  char sh[3];
+
+  *val = NULL;
+  if(o->sh)
+    goto inshort;
+
+  if(++o->cur >= o->argc)
+    return -1;
+
+  if(!o->argsep && o->argv[o->cur][0] == '-' && o->argv[o->cur][1] == '-' && o->argv[o->cur][2] == 0) {
+    o->argsep = 1;
+    if(++o->cur >= o->argc)
+      return -1;
+  }
+
+  if(o->argsep || *o->argv[o->cur] != '-') {
+    *val = o->argv[o->cur];
+    return 0;
+  }
+
+  if(o->argv[o->cur][1] != '-') {
+    o->sh = o->argv[o->cur]+1;
+    goto inshort;
+  }
+
+  /* Now we're supposed to have a long option */
+  if(!(opt = _yopt_find(o->opts, o->argv[o->cur])))
+    return _yopt_err(o, val, "Unknown option '%s'", o->argv[o->cur]);
+  if((*val = strchr(o->argv[o->cur], '=')) != NULL)
+    (*val)++;
+  if(!opt->needarg && *val)
+    return _yopt_err(o, val, "Option '%s' does not accept an argument", o->argv[o->cur]);
+  if(opt->needarg && !*val) {
+    if(o->cur+1 >= o->argc)
+      return _yopt_err(o, val, "Option '%s' requires an argument", o->argv[o->cur]);
+    *val = o->argv[++o->cur];
+  }
+  return opt->val;
+
+  /* And here we're supposed to have a short option */
+inshort:
+  sh[0] = '-';
+  sh[1] = *o->sh;
+  sh[2] = 0;
+  if(!(opt = _yopt_find(o->opts, sh)))
+    return _yopt_err(o, val, "Unknown option '%s'", sh);
+  o->sh++;
+  if(opt->needarg && *o->sh)
+    *val = o->sh;
+  else if(opt->needarg) {
+    if(++o->cur >= o->argc)
+      return _yopt_err(o, val, "Option '%s' requires an argument", sh);
+    *val = o->argv[o->cur];
+  }
+  if(!*o->sh || opt->needarg)
+    o->sh = NULL;
+  return opt->val;
+}
-- 
GitLab