summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2012-09-08 14:38:15 +0200
committerYorhel <git@yorhel.nl>2012-09-08 14:38:15 +0200
commit7feaeb1483b2c651898d179c6c51ea0b2a3ab590 (patch)
tree4deab6da0b275389c6f0f2faefa683deb26f1dc4
parenteeff908b0c01777657d70716771616d25bfee7a8 (diff)
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>".
-rw-r--r--Makefile.am3
-rw-r--r--src/main.c114
-rw-r--r--src/yopt.h192
3 files changed, 254 insertions, 55 deletions
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;
+}