#include "command.h" #include #include #include #include #include #include "commands.h" #define CMD_ALIASES(...) (const char *[]){ __VA_ARGS__, NULL } struct l2_command_node l2_cmd_root = { .type = CMD_NODE_TYPE_NULL, .name = "", .cmd_proc = NULL, .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_LITERAL, .name = "instance", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_LITERAL, .name = "add", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_ARGUMENT, .name = "name", .cmd_proc = &cmd_instance_add, .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_LITERAL, .name = "at", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_ARGUMENT, .name = "location", .cmd_proc = &cmd_instance_add }, { 0 } } }, { 0 } } }, { 0 } } }, { .type = CMD_NODE_TYPE_LITERAL, .name = "remove", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_ARGUMENT, .name = "name", .cmd_proc = &cmd_instance_remove }, { 0 } } }, { .type = CMD_NODE_TYPE_LITERAL, .name = "list", .cmd_proc = &cmd_instance_list }, { .type = CMD_NODE_TYPE_LITERAL, .name = "rename", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_ARGUMENT, .name = "oldname", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_ARGUMENT, .name = "newname", .cmd_proc = &cmd_instance_rename }, { 0 } } }, { 0 } } }, { 0 } } }, { .type = CMD_NODE_TYPE_LITERAL, .name = "version", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_LITERAL, .name = "list", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_LITERAL, .name = "local", .cmd_proc = &cmd_version_list_local }, { .type = CMD_NODE_TYPE_LITERAL, .name = "remote", .cmd_proc = &cmd_version_list_remote }, { 0 } } }, { .type = CMD_NODE_TYPE_LITERAL, .name = "install", .children = (struct l2_command_node []) { { .type = CMD_NODE_TYPE_ARGUMENT, .name = "remotever", .cmd_proc = &cmd_version_install }, { 0 } } }, { 0 } } }, { 0 } } }; bool l2_cmd__parse_command_int(char **argvcur, struct l2_command_node *start, struct l2_context_node **ctx, struct l2_parseinfo *parseinfo); unsigned l2_cmd_parse_command(char **argv, struct l2_parseinfo *parseinfo) { if (!argv || !parseinfo) { return CMD_PARSE_EUSAGE; } l2_cmd__parse_command_int(argv, &l2_cmd_root, &parseinfo->ctx, parseinfo); return parseinfo->ctx ? CMD_PARSE_SUCCESS : CMD_PARSE_ESYNTAX; } bool l2_cmd__parse_command_int(char **argvcur, struct l2_command_node *start, struct l2_context_node **ctx, struct l2_parseinfo *parseinfo) { struct l2_command_node *curchild; struct l2_context_node *childctx = NULL; unsigned argresult; *ctx = NULL; if (!*argvcur) { return true; } switch (start->type) { case CMD_NODE_TYPE_NULL: curchild = start->children; while (curchild && curchild->name && !*ctx) { l2_cmd__parse_command_int(argvcur, curchild, ctx, parseinfo); ++curchild; } return true; case CMD_NODE_TYPE_LITERAL: if (strcmp(*argvcur, start->name)) { if (!start->extra.aliases) return true; /* no aliases */ bool found = false; for (const char **alias = start->extra.aliases; *alias; ++alias) { if (!strcmp(*argvcur, *alias)) { found = true; break; } } if (!found) return true; } break; case CMD_NODE_TYPE_ARGUMENT: argresult = start->extra.validate_proc ? (start->extra.validate_proc)(*argvcur) : CMD_VALIDATE_PASS; if (argresult != CMD_VALIDATE_PASS) return argresult == CMD_VALIDATE_FAIL_SKIP; break; default: abort(); } curchild = start->children; while (curchild && curchild->name && !childctx) { if (!l2_cmd__parse_command_int(argvcur + 1, curchild, &childctx, parseinfo)) break; ++curchild; } if (childctx || start->cmd_proc) { /* we've found a promising child node (or we're executable) */ size_t arglen = strlen(*argvcur); struct l2_context_node *mynode = calloc(1, sizeof(struct l2_context_node) + arglen); mynode->node = start; mynode->next = childctx; mynode->value = *argvcur; *ctx = mynode; if (!childctx) { /* we're the executable node */ parseinfo->argv = argvcur + 1; parseinfo->proc = start->cmd_proc; } } return true; } void l2_cmd_free_ctx(struct l2_context_node *ctx) { if (!ctx) return; struct l2_context_node *next; do { next = ctx->next; free(ctx); ctx = next; } while (ctx); } void l2_cmd_collect_args(struct l2_context_node *ctx, unsigned argc, ...) { va_list ap; if (argc == 0) return; struct tag_cmd_arg_t { const char *name; char **target; bool optional; } args[argc]; /* ooooo c99 VLA this is exciting */ va_start(ap, argc); for (unsigned i = 0; i < argc; ++i) { args[i].name = va_arg(ap, const char *); args[i].target = va_arg(ap, char **); if (*args[i].name == '#') { ++args[i].name; args[i].optional = true; } else { args[i].optional = false; } } va_end(ap); for (struct l2_context_node *cur = ctx; cur; cur = cur->next) { if (cur->node->type != CMD_NODE_TYPE_ARGUMENT) continue; for (unsigned i = 0; i < argc; ++i) { if (!strcmp(cur->node->name, args[i].name)) { *args[i].target = cur->value; args[i].optional = true; } } } for (unsigned i = 0; i < argc; ++i) { if (!args[i].optional) { CMD_FATAL(CMD_MSG_REQ_ARG_UNSPECIFIED, args[i].name); } } }