basic: improve command line parsing

- add qe_strtobool(str, def). Returns boolean value from str:
  y yes t true 1 evaluate to true
  any other string evaluates to false
  the empty string evaluates to def
- add bstr_t type:
  constant bounded string with pointer and length.
  bstr_make creates a bstr_t from a string pointer or NULL
  bstr_token creates a bstr_t from a string upto a separator
  bstr_get_nth creates a bstr_t from a the nth element of a string list
  bstr_equal compares bstr_t objects for equality
- use macros for type safe registation of command line options
- combine short, long, argument names and help string
- support command line options with optional arguments
This commit is contained in:
Charlie Gordon 2017-04-16 17:20:49 +00:00
commit 3dccd7f696
4 changed files with 199 additions and 97 deletions

169
qe.c
View file

@ -8176,21 +8176,19 @@ void do_load_qerc(EditState *e, const char *filename)
/******************************************************/
/* command line option handling */
static CmdOptionDef *first_cmd_options;
static CmdLineOptionDef *first_cmd_options;
void qe_register_cmd_line_options(CmdOptionDef *table)
void qe_register_cmd_line_options(CmdLineOptionDef *table)
{
CmdOptionDef **pp, *p;
CmdLineOptionDef **pp, *p;
/* link command line options table at end of list */
pp = &first_cmd_options;
while (*pp != NULL) {
for (pp = &first_cmd_options; *pp != NULL; pp = &p->u.next) {
p = *pp;
if (p == table)
return; /* already registered */
while (p->name != NULL)
if (p == table)
return; /* already registered */
while (p->desc != NULL)
p++;
pp = &p->u.next;
}
*pp = table;
}
@ -8213,8 +8211,7 @@ static void show_version(void)
static void show_usage(void)
{
CmdOptionDef *p;
int pos;
CmdLineOptionDef *p;
printf("Usage: qe [OPTIONS] [filename ...]\n"
"\n"
@ -8222,20 +8219,26 @@ static void show_usage(void)
"\n");
/* print all registered command line options */
p = first_cmd_options;
while (p != NULL) {
while (p->name != NULL) {
pos = printf("--%s", p->name);
if (p->shortname)
pos += printf(", -%s", p->shortname);
if (p->flags & CMD_OPT_ARG)
pos += printf(" %s", p->argname);
if (pos < 24)
printf("%*s", pos - 24, "");
printf("%s\n", p->help);
for (p = first_cmd_options; p != NULL; p = p->u.next) {
while (p->desc != NULL) {
const char *s = p->desc;
bstr_t shortname = bstr_token(s, '|', &s);
bstr_t name = bstr_token(s, '|', &s);
bstr_t argname = bstr_token(s, '|', &s);
bstr_t help = bstr_make(s);
int pos = printf(" ");
if (shortname.len)
pos += printf(" -%.*s", shortname.len, shortname.s);
if (name.len)
pos += printf(" --%.*s", name.len, name.s);
if (argname.len)
pos += printf(" %.*s", argname.len, argname.s);
if (pos < 22)
printf("%*s", pos - 22, "");
printf(" %.*s\n", help.len, help.s);
p++;
}
p = p->u.next;
}
printf("\n"
"Report bugs to bug@qemacs.org. First, please see the Bugs\n"
@ -8247,60 +8250,79 @@ static int parse_command_line(int argc, char **argv)
{
int _optind;
_optind = 1;
for (;;) {
const char *r, *r1, *r2, *_optarg;
CmdOptionDef *p;
for (_optind = 1; _optind < argc;) {
const char *arg, *r, *_optarg;
CmdLineOptionDef *p;
bstr_t opt1;
bstr_t opt2;
if (_optind >= argc)
break;
r = argv[_optind];
r = arg = argv[_optind];
/* stop before first non option */
if (r[0] != '-')
break;
_optind++;
r2 = r1 = r + 1;
if (r2[0] == '-') {
r2++;
opt1.s = opt2.s = r + 1;
if (r[1] == '-') {
opt2.s++;
/* stop after `--' marker */
if (r2[0] == '\0')
if (r[2] == '\0')
break;
}
/* parse optional argument specified with opt=arg or opt:arg syntax */
_optarg = NULL;
while (*r) {
if (*r == ':' || *r == '=') {
_optarg = r + 1;
break;
}
r++;
}
opt1.len = r - opt1.s;
opt2.len = r - opt2.s;
p = first_cmd_options;
while (p != NULL) {
while (p->name != NULL) {
if (strequal(p->name, r2) ||
(p->shortname && strequal(p->shortname, r1))) {
if (p->flags & CMD_OPT_ARG) {
for (p = first_cmd_options; p != NULL; p = p->u.next) {
while (p->desc != NULL) {
const char *s = p->desc;
bstr_t shortname = bstr_token(s, '|', &s);
bstr_t name = bstr_token(s, '|', &s);
bstr_t argname = bstr_token(s, '|', &s);
if (bstr_equal(opt1, shortname) || bstr_equal(opt2, name)) {
if (argname.len && _optarg == NULL) {
if (_optind >= argc) {
put_status(NULL,
"cmdline argument expected -- %s", r);
"cmdline argument %.*s expected for --%.*s",
argname.len, argname.s, name.len, name.s);
goto next_cmd;
}
_optarg = argv[_optind++];
} else {
_optarg = NULL;
}
if (p->flags & CMD_OPT_BOOL) {
*p->u.int_ptr = 1;
} else if (p->flags & CMD_OPT_STRING) {
*p->u.string_ptr = _optarg;
} else if (p->flags & CMD_OPT_INT) {
switch (p->type) {
case CMD_LINE_TYPE_BOOL:
*p->u.int_ptr = qe_strtobool(_optarg, 1);
break;
case CMD_LINE_TYPE_INT:
*p->u.int_ptr = strtol(_optarg, NULL, 0);
} else if (p->flags & CMD_OPT_ARG) {
p->u.func_arg(_optarg);
} else {
break;
case CMD_LINE_TYPE_STRING:
*p->u.string_ptr = _optarg;
break;
case CMD_LINE_TYPE_FVOID:
p->u.func_noarg();
break;
case CMD_LINE_TYPE_FARG:
p->u.func_arg(_optarg);
break;
case CMD_LINE_TYPE_NONE:
case CMD_LINE_TYPE_NEXT:
break;
}
goto next_cmd;
}
p++;
}
p = p->u.next;
}
put_status(NULL, "unknown cmdline option '%s'", r);
put_status(NULL, "unknown cmdline option '%s'", arg);
next_cmd: ;
}
@ -8365,28 +8387,29 @@ void set_tty_charset(const char *name)
qe_state.tty_charset = qe_strdup(name);
}
static CmdOptionDef cmd_options[] = {
{ "help", "h", NULL, 0, "display this help message and exit",
{ .func_noarg = show_usage }},
{ "no-init-file", "q", NULL, CMD_OPT_BOOL, "do not load config files",
{ .int_ptr = &no_init_file }},
{ "single-window", "1", NULL, CMD_OPT_BOOL, "keep a single window when loading multiple files",
{ .int_ptr = &single_window }},
{ "no-windows", "nw", NULL, CMD_OPT_BOOL, "force tty terminal usage",
{ .int_ptr = &force_tty }},
{ "ttycharset", "c", "CHARSET", CMD_OPT_ARG, "specify tty charset",
{ .func_arg = set_tty_charset }},
{ "use-session", "s", NULL, CMD_OPT_BOOL, "load and save session files",
{ .int_ptr = &use_session_file }},
{ "user", "u", "USER", CMD_OPT_ARG, "load ~USER/.qe/config instead of your own",
{ .func_arg = set_user_option }},
{ "version", "V", NULL, 0, "display version information and exit",
{ .func_noarg = show_version }},
static CmdLineOptionDef cmd_options[] = {
CMD_LINE_FVOID("h", "help", show_usage,
"display this help message and exit"),
CMD_LINE_FVOID("?", "", show_usage, ""),
CMD_LINE_BOOL("q", "no-init-file", &no_init_file,
"do not load config files"),
CMD_LINE_BOOL("1", "single-window", &single_window,
"keep a single window when loading multiple files"),
CMD_LINE_BOOL("nw", "no-windows", &force_tty,
"force tty terminal usage"),
CMD_LINE_FARG("c", "charset", "CHARSET", set_tty_charset,
"specify tty charset"),
CMD_LINE_BOOL("s", "use-session", &use_session_file,
"load and save session files"),
CMD_LINE_FARG("u", "user", "USER", set_user_option,
"load ~USER/.qe/config instead of your own"),
CMD_LINE_FVOID("V", "version", show_version,
"display version information and exit"),
#ifndef CONFIG_TINY
{ "free-all", NULL, NULL, CMD_OPT_BOOL, "free all structures upon exit",
{ .int_ptr = &free_everything }},
CMD_LINE_BOOL("", "free-all", &free_everything,
"free all structures upon exit"),
#endif
{ NULL, NULL, NULL, 0, NULL, { NULL }},
CMD_LINE_LINK()
};
/* default key bindings */

60
qe.h
View file

@ -434,6 +434,7 @@ static inline int check_fcall(const unsigned int *str, int i) {
}
int qe_strcollate(const char *s1, const char *s2);
int qe_strtobool(const char *s, int def);
void qe_strtolower(char *buf, int buf_size, const char *str);
void skip_spaces(const char **pp);
@ -579,32 +580,61 @@ static inline int buf_puts(buf_t *bp, const char *str) {
int buf_printf(buf_t *bp, const char *fmt, ...) qe__attr_printf(2,3);
int buf_putc_utf8(buf_t *bp, int c);
/* Bounded constant strings used in various parse functions */
typedef struct bstr_t {
const char *s;
int len;
} bstr_t;
static inline bstr_t bstr_make(const char *s) {
bstr_t bs = { s, s ? strlen(s) : 0 };
return bs;
}
bstr_t bstr_token(const char *s, int sep, const char **pp);
bstr_t bstr_get_nth(const char *s, int n);
static inline int bstr_equal(bstr_t s1, bstr_t s2) {
/* NULL and empty strings are equivalent */
return s1.len == s2.len && !memcmp(s1.s, s2.s, s1.len);
}
/* our own implementation of qsort_r() */
void qe_qsort_r(void *base, size_t nmemb, size_t size, void *thunk,
int (*compar)(void *, const void *, const void *));
/* command line option */
#define CMD_OPT_ARG 0x0001 /* argument */
#define CMD_OPT_STRING 0x0002 /* string */
#define CMD_OPT_BOOL 0x0004 /* boolean */
#define CMD_OPT_INT 0x0008 /* int */
/* Command line options */
enum CmdLineOptionType {
CMD_LINE_TYPE_NONE = 0, /* nothing */
CMD_LINE_TYPE_BOOL = 1, /* boolean ptr */
CMD_LINE_TYPE_INT = 2, /* int ptr */
CMD_LINE_TYPE_STRING = 3, /* string ptr */
CMD_LINE_TYPE_FVOID = 4, /* function() */
CMD_LINE_TYPE_FARG = 5, /* function(string) */
CMD_LINE_TYPE_NEXT = 6, /* next pointer */
};
typedef struct CmdOptionDef {
const char *name;
const char *shortname;
const char *argname;
int flags;
const char *help;
typedef struct CmdLineOptionDef {
const char *desc;
enum CmdLineOptionType type;
union {
const char **string_ptr;
int *int_ptr;
const char **string_ptr;
void (*func_noarg)(void);
void (*func_arg)(const char *);
struct CmdOptionDef *next;
struct CmdLineOptionDef *next;
} u;
} CmdOptionDef;
} CmdLineOptionDef;
void qe_register_cmd_line_options(CmdOptionDef *table);
#define CMD_LINE_NONE() { NULL, CMD_LINE_TYPE_NONE, { NULL }}
#define CMD_LINE_BOOL(s,n,p,h) { s "|" n "||" h, CMD_LINE_TYPE_BOOL, { .int_ptr = p }}
#define CMD_LINE_INT(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_INT, { .int_ptr = p }}
#define CMD_LINE_STRING(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_STRING, { .string_ptr = p }}
#define CMD_LINE_FVOID(s,n,p,h) { s "|" n "||" h, CMD_LINE_TYPE_FVOID, { .func_noarg = p }}
#define CMD_LINE_FARG(s,n,a,p,h) { s "|" n "|" a "|" h, CMD_LINE_TYPE_FARG, { .func_arg = p }}
#define CMD_LINE_LINK() { NULL, CMD_LINE_TYPE_NEXT, { NULL }}
void qe_register_cmd_line_options(CmdLineOptionDef *table);
int find_resource_file(char *path, int path_size, const char *pattern);

49
util.c
View file

@ -470,6 +470,14 @@ int qe_strcollate(const char *s1, const char *s2)
/* CG: need a local version of strcasecmp: qe_strcasecmp() */
int qe_strtobool(const char *s, int def) {
if (s && *s) {
return strxfind("1|y|yes|t|true", s) ? 1 : 0;
} else {
return def;
}
}
/* Should return int, length of converted string? */
void qe_strtolower(char *buf, int size, const char *str)
{
@ -2222,6 +2230,47 @@ void *qe_realloc(void *pp, size_t size)
return p;
}
/*---------------- bounded strings ----------------*/
/* get the n-th string from a `|` separated list */
bstr_t bstr_get_nth(const char *s, int n) {
bstr_t bs;
for (bs.s = s;; s++) {
if (*s == '\0' || *s == '|') {
if (n-- == 0) {
bs.len = s - bs.s;
break;
}
if (*s) {
bs.s = s + 1;
} else {
bs.len = 0;
bs.s = NULL;
break;
}
}
}
return bs;
}
/* get the first string from a list and push pointer */
bstr_t bstr_token(const char *s, int sep, const char **pp) {
bstr_t bs = { s, 0 };
if (s) {
/* XXX: should special case spaces? */
for (; s != '\0' && *s != sep; s++)
continue;
bs.len = s - bs.s;
if (pp) {
*pp = *s ? s + 1 : NULL;
}
}
return bs;
}
/*---------------- qe_qsort_r ----------------*/
/* Our own implementation of qsort_r() since it is not available

16
x11.c
View file

@ -1911,14 +1911,14 @@ static CmdDef x11_commands[] = {
CMD_DEF_END,
};
static CmdOptionDef cmd_options[] = {
{ "display", "d", "display", CMD_OPT_STRING | CMD_OPT_ARG, "set X11 display",
{ .string_ptr = &display_str }},
{ "geometry", "g", "WxH", CMD_OPT_STRING | CMD_OPT_ARG, "set X11 display size",
{ .string_ptr = &geometry_str }},
{ "font-size", "fs", "ptsize", CMD_OPT_INT | CMD_OPT_ARG, "set default font size",
{ .int_ptr = &font_ptsize }},
{ NULL, NULL, NULL, 0, NULL, { NULL }},
static CmdLineOptionDef cmd_options[] = {
CMD_LINE_STRING("d", "display", "DISPLAY", &display_str,
"set X11 display"),
CMD_LINE_STRING("g", "geometry", "WxH", &geometry_str,
"set X11 display size"),
CMD_LINE_INT("fs", "font-size", "ptsize", &font_ptsize,
"set default font size"),
CMD_LINE_LINK()
};
static int x11_init(void)