diff options
author | Yorhel <git@yorhel.nl> | 2012-09-08 14:38:15 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2012-09-08 14:38:15 +0200 |
commit | 7feaeb1483b2c651898d179c6c51ea0b2a3ab590 (patch) | |
tree | 4deab6da0b275389c6f0f2faefa683deb26f1dc4 | |
parent | eeff908b0c01777657d70716771616d25bfee7a8 (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.am | 3 | ||||
-rw-r--r-- | src/main.c | 114 | ||||
-rw-r--r-- | src/yopt.h | 192 |
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 @@ -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; +} |