/** * @file src/cmd.c Command Interface * * Copyright (C) 2010 - 2016 Creytiv.com */ #include #include #include #include #include "core.h" enum { KEYCODE_DEL = 0x7f, LONG_PREFIX = '/' }; struct cmds { struct le le; const struct cmd *cmdv; size_t cmdc; }; struct cmd_ctx { struct mbuf *mb; const struct cmd *cmd; bool is_long; }; struct commands { struct list cmdl; /**< List of command blocks (struct cmds) */ }; static int cmd_print_all(struct re_printf *pf, const struct commands *commands, bool print_long, bool print_short, const char *match, size_t match_len); static void destructor(void *arg) { struct cmds *cmds = arg; list_unlink(&cmds->le); } static void ctx_destructor(void *arg) { struct cmd_ctx *ctx = arg; mem_deref(ctx->mb); } static void commands_destructor(void *data) { struct commands *commands = data; list_flush(&commands->cmdl); } static int ctx_alloc(struct cmd_ctx **ctxp, const struct cmd *cmd) { struct cmd_ctx *ctx; ctx = mem_zalloc(sizeof(*ctx), ctx_destructor); if (!ctx) return ENOMEM; ctx->mb = mbuf_alloc(32); if (!ctx->mb) { mem_deref(ctx); return ENOMEM; } ctx->cmd = cmd; *ctxp = ctx; return 0; } struct cmds *cmds_find(const struct commands *commands, const struct cmd *cmdv) { struct le *le; if (!commands || !cmdv) return NULL; for (le = commands->cmdl.head; le; le = le->next) { struct cmds *cmds = le->data; if (cmds->cmdv == cmdv) return cmds; } return NULL; } static const struct cmd *cmd_find_by_key(const struct commands *commands, char key) { struct le *le; if (!commands) return NULL; for (le = commands->cmdl.tail; le; le = le->prev) { struct cmds *cmds = le->data; size_t i; for (i=0; icmdc; i++) { const struct cmd *cmd = &cmds->cmdv[i]; if (cmd->key == key && cmd->h) return cmd; } } return NULL; } static const char *cmd_name(char *buf, size_t sz, const struct cmd *cmd) { switch (cmd->key) { case ' ': return "SPACE"; case '\n': return "ENTER"; case KEYCODE_ESC: return "ESC"; } buf[0] = cmd->key; buf[1] = '\0'; if (cmd->flags & CMD_PRM) strncat(buf, " ..", sz-1); return buf; } static size_t get_match_long(const struct commands *commands, const struct cmd **cmdp, const char *str, size_t len) { struct le *le; size_t nmatch = 0; if (!commands) return 0; for (le = commands->cmdl.head; le; le = le->next) { struct cmds *cmds = le->data; size_t i; for (i=0; icmdc; i++) { const struct cmd *cmd = &cmds->cmdv[i]; if (!str_isset(cmd->name)) continue; if (str_len(cmd->name) >= len && 0 == memcmp(cmd->name, str, len)) { ++nmatch; *cmdp = cmd; } } } return nmatch; } static int editor_input(struct commands *commands, struct mbuf *mb, char key, struct re_printf *pf, bool *del, bool is_long) { int err = 0; switch (key) { case KEYCODE_ESC: *del = true; return re_hprintf(pf, "\nCancel\n"); case KEYCODE_NONE: case KEYCODE_REL: break; case '\n': *del = true; return re_hprintf(pf, "\n"); case '\b': case KEYCODE_DEL: if (mb->pos > 0) { err |= re_hprintf(pf, "\b "); mb->pos = mb->end = (mb->pos - 1); } break; case '\t': if (is_long) { const struct cmd *cmd = NULL; size_t n; err = re_hprintf(pf, "TAB completion for \"%b\":\n", mb->buf, mb->end); if (err) return err; /* Find all long commands that matches the N * first characters of the input string. * * If the number of matches is exactly one, * we can regard it as TAB completion. */ err = cmd_print_all(pf, commands, true, false, (char *)mb->buf, mb->end); if (err) return err; n = get_match_long(commands, &cmd, (char *)mb->buf, mb->end); if (n == 1 && cmd) { mb->pos = 0; mbuf_write_str(mb, cmd->name); } else if (n == 0) { err = re_hprintf(pf, "(none)\n"); } } else { err = mbuf_write_u8(mb, key); } break; default: err = mbuf_write_u8(mb, key); break; } if (is_long) { err |= re_hprintf(pf, "\r/%b", mb->buf, mb->end); } else err |= re_hprintf(pf, "\r> %32b", mb->buf, mb->end); return err; } static int cmd_report(const struct cmd *cmd, struct re_printf *pf, struct mbuf *mb, bool compl, void *data) { struct cmd_arg arg; int err; memset(&arg, 0, sizeof(arg)); mb->pos = 0; err = mbuf_strdup(mb, &arg.prm, mb->end); if (err) return err; arg.key = cmd->key; arg.complete = compl; arg.data = data; err = cmd->h(pf, &arg); mem_deref(arg.prm); return err; } int cmd_process_long(struct commands *commands, const char *str, size_t len, struct re_printf *pf_resp, void *data) { struct cmd_arg arg; const struct cmd *cmd_long; char *name = NULL, *prm = NULL; struct pl pl_name, pl_prm; int err; if (!str || !len) return EINVAL; memset(&arg, 0, sizeof(arg)); err = re_regex(str, len, "[^ ]+[ ]*[~]*", &pl_name, NULL, &pl_prm); if (err) { return err; } err = pl_strdup(&name, &pl_name); if (pl_isset(&pl_prm)) err |= pl_strdup(&prm, &pl_prm); if (err) goto out; cmd_long = cmd_find_long(commands, name); if (cmd_long) { arg.key = LONG_PREFIX; arg.prm = prm; arg.complete = true; arg.data = data; if (cmd_long->h) err = cmd_long->h(pf_resp, &arg); } else { err = re_hprintf(pf_resp, "command not found (%s)\n", name); } out: mem_deref(name); mem_deref(prm); return err; } static int cmd_process_edit(struct commands *commands, struct cmd_ctx **ctxp, char key, struct re_printf *pf, void *data) { struct cmd_ctx *ctx; bool compl = (key == '\n'), del = false; int err; if (!ctxp) return EINVAL; ctx = *ctxp; err = editor_input(commands, ctx->mb, key, pf, &del, ctx->is_long); if (err) return err; if (ctx->is_long) { if (compl) { err = cmd_process_long(commands, (char *)ctx->mb->buf, ctx->mb->end, pf, data); } } else { if (compl || (ctx->cmd && ctx->cmd->flags & CMD_PROG)) err = cmd_report(ctx->cmd, pf, ctx->mb, compl, data); } if (del) *ctxp = mem_deref(*ctxp); return err; } /** * Register commands * * @param commands Commands container * @param cmdv Array of commands * @param cmdc Number of commands * * @return 0 if success, otherwise errorcode */ int cmd_register(struct commands *commands, const struct cmd *cmdv, size_t cmdc) { struct cmds *cmds; size_t i; if (!commands || !cmdv || !cmdc) return EINVAL; cmds = cmds_find(commands, cmdv); if (cmds) return EALREADY; /* verify that command is not registered */ for (i=0; ikey) { const struct cmd *x = cmd_find_by_key(commands, cmd->key); if (x) { warning("short command '%c' already" " registered as \"%s\"\n", x->key, x->desc); return EALREADY; } } if (cmd->key == LONG_PREFIX) { warning("cmd: cannot register command with" " short key '%c'\n", cmd->key); return EINVAL; } if (str_isset(cmd->name) && cmd_find_long(commands, cmd->name)) { warning("cmd: long command '%s' already registered\n", cmd->name); return EINVAL; } } cmds = mem_zalloc(sizeof(*cmds), destructor); if (!cmds) return ENOMEM; cmds->cmdv = cmdv; cmds->cmdc = cmdc; list_append(&commands->cmdl, &cmds->le, cmds); return 0; } /** * Unregister commands * * @param commands Commands container * @param cmdv Array of commands */ void cmd_unregister(struct commands *commands, const struct cmd *cmdv) { mem_deref(cmds_find(commands, cmdv)); } const struct cmd *cmd_find_long(const struct commands *commands, const char *name) { struct le *le; if (!commands || !name) return NULL; for (le = commands->cmdl.tail; le; le = le->prev) { struct cmds *cmds = le->data; size_t i; for (i=0; icmdc; i++) { const struct cmd *cmd = &cmds->cmdv[i]; if (0 == str_casecmp(name, cmd->name) && cmd->h) return cmd; } } return NULL; } /** * Process input characters to the command system * * @param commands Commands container * @param ctxp Pointer to context for editor (optional) * @param key Input character * @param pf Print function * @param data Application data * * @return 0 if success, otherwise errorcode */ int cmd_process(struct commands *commands, struct cmd_ctx **ctxp, char key, struct re_printf *pf, void *data) { const struct cmd *cmd; if (!commands) return EINVAL; if (key == KEYCODE_NONE) { warning("cmd: process: illegal keycode NONE\n"); return EINVAL; } /* are we in edit-mode? */ if (ctxp && *ctxp) { if (key == KEYCODE_REL) return 0; return cmd_process_edit(commands, ctxp, key, pf, data); } cmd = cmd_find_by_key(commands, key); if (cmd) { struct cmd_arg arg; /* check for parameters */ if (cmd->flags & CMD_PRM) { int err = 0; if (ctxp) { err = ctx_alloc(ctxp, cmd); if (err) return err; } key = isdigit(key) ? key : KEYCODE_REL; return cmd_process_edit(commands, ctxp, key, pf, data); } arg.key = key; arg.prm = NULL; arg.complete = true; arg.data = data; return cmd->h(pf, &arg); } else if (key == LONG_PREFIX) { int err; err = re_hprintf(pf, "%c", LONG_PREFIX); if (err) return err; if (!ctxp) { warning("cmd: ctxp is required\n"); return EINVAL; } err = ctx_alloc(ctxp, cmd); if (err) return err; (*ctxp)->is_long = true; return 0; } else if (key == '\t') { return cmd_print_all(pf, commands, false, true, NULL, 0); } if (key == KEYCODE_REL) return 0; return cmd_print(pf, commands); } struct cmd_sort { struct le le; const struct cmd *cmd; }; static bool sort_handler(struct le *le1, struct le *le2, void *arg) { struct cmd_sort *cs1 = le1->data; struct cmd_sort *cs2 = le2->data; const struct cmd *cmd1 = cs1->cmd; const struct cmd *cmd2 = cs2->cmd; bool print_long = *(bool *)arg; if (print_long) { return str_casecmp(cs2->cmd->name ? cs2->cmd->name : "", cs1->cmd->name ? cs1->cmd->name : "") >= 0; } else { return tolower(cmd2->key) >= tolower(cmd1->key); } } static int cmd_print_all(struct re_printf *pf, const struct commands *commands, bool print_long, bool print_short, const char *match, size_t match_len) { struct list sortedl = LIST_INIT; struct le *le; size_t width_long = 1; size_t width_short = 5; char fmt[64]; char buf[16]; int err = 0; if (!commands) return EINVAL; for (le = commands->cmdl.head; le; le = le->next) { struct cmds *cmds = le->data; size_t i; for (i=0; icmdc; i++) { const struct cmd *cmd = &cmds->cmdv[i]; struct cmd_sort *cs; if (match && match_len) { if (str_len(cmd->name) >= match_len && 0 == memcmp(cmd->name, match, match_len)) { /* Match */ } else { continue; } } if (!str_isset(cmd->desc)) continue; if (print_short && !print_long) { if (cmd->key == KEYCODE_NONE) continue; } cs = mem_zalloc(sizeof(*cs), NULL); if (!cs) { err = ENOMEM; goto out; } cs->cmd = cmd; list_append(&sortedl, &cs->le, cs); width_long = max(width_long, 1+str_len(cmd->name)+3); } } list_sort(&sortedl, sort_handler, &print_long); if (re_snprintf(fmt, sizeof(fmt), " %%-%zus %%-%zus %%s\n", width_long, width_short) < 0) { err = ENOMEM; goto out; } for (le = sortedl.head; le; le = le->next) { struct cmd_sort *cs = le->data; const struct cmd *cmd = cs->cmd; char namep[64] = ""; if (print_long && str_isset(cmd->name)) { re_snprintf(namep, sizeof(namep), "%c%s%s", LONG_PREFIX, cmd->name, (cmd->flags & CMD_PRM) ? " .." : ""); } err |= re_hprintf(pf, fmt, namep, (print_short && cmd->key) ? cmd_name(buf, sizeof(buf), cmd) : "", cmd->desc); } err |= re_hprintf(pf, "\n"); out: list_flush(&sortedl); return err; } /** * Print a list of available commands * * @param pf Print function * @param commands Commands container * * @return 0 if success, otherwise errorcode */ int cmd_print(struct re_printf *pf, const struct commands *commands) { int err = 0; if (!pf) return EINVAL; err |= re_hprintf(pf, "--- Help ---\n"); err |= cmd_print_all(pf, commands, true, true, NULL, 0); err |= re_hprintf(pf, "\n"); return err; } int cmd_init(struct commands **commandsp) { struct commands *commands; if (!commandsp) return EINVAL; commands = mem_zalloc(sizeof(*commands), commands_destructor); if (!commands) return ENOMEM; list_init(&commands->cmdl); *commandsp = commands; return 0; }