diff --git a/qe.c b/qe.c index f3ff40b..b4573ab 100644 --- a/qe.c +++ b/qe.c @@ -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 */ diff --git a/qe.h b/qe.h index 79b35d3..115c9be 100644 --- a/qe.h +++ b/qe.h @@ -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); diff --git a/util.c b/util.c index 5711d34..8d17204 100644 --- a/util.c +++ b/util.c @@ -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 diff --git a/x11.c b/x11.c index 069bd2e..3c0d91c 100644 --- a/x11.c +++ b/x11.c @@ -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)