From e5f884e68cc58913a4d8f6409c474f917e2fd975 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 1 May 2018 03:16:03 +0200 Subject: input: rename cmd_parse.c to cmd.c Done separately from the cmd.h rename to avoid issues with git being bad at tracking mixed content and filename changes. --- input/cmd.c | 473 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 input/cmd.c (limited to 'input/cmd.c') diff --git a/input/cmd.c b/input/cmd.c new file mode 100644 index 0000000000..95f9f9ac50 --- /dev/null +++ b/input/cmd.c @@ -0,0 +1,473 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#include + +#include "misc/bstr.h" +#include "common/common.h" +#include "common/msg.h" +#include "options/m_option.h" + +#include "cmd.h" +#include "cmd_list.h" +#include "input.h" + +#include "libmpv/client.h" + +const struct mp_cmd_def mp_cmd_list = { + .name = "list", +}; + +static void destroy_cmd(void *ptr) +{ + struct mp_cmd *cmd = ptr; + for (int n = 0; n < cmd->nargs; n++) + m_option_free(cmd->args[n].type, &cmd->args[n].v); +} + +struct flag { + const char *name; + unsigned int remove, add; +}; + +static const struct flag cmd_flags[] = { + {"no-osd", MP_ON_OSD_FLAGS, MP_ON_OSD_NO}, + {"osd-bar", MP_ON_OSD_FLAGS, MP_ON_OSD_BAR}, + {"osd-msg", MP_ON_OSD_FLAGS, MP_ON_OSD_MSG}, + {"osd-msg-bar", MP_ON_OSD_FLAGS, MP_ON_OSD_MSG | MP_ON_OSD_BAR}, + {"osd-auto", MP_ON_OSD_FLAGS, MP_ON_OSD_AUTO}, + {"expand-properties", 0, MP_EXPAND_PROPERTIES}, + {"raw", MP_EXPAND_PROPERTIES, 0}, + {"repeatable", 0, MP_ALLOW_REPEAT}, + {"async", 0, MP_ASYNC_CMD}, + {0} +}; + +static bool apply_flag(struct mp_cmd *cmd, bstr str) +{ + for (int n = 0; cmd_flags[n].name; n++) { + if (bstr_equals0(str, cmd_flags[n].name)) { + cmd->flags = (cmd->flags & ~cmd_flags[n].remove) | cmd_flags[n].add; + return true; + } + } + return false; +} + +static bool find_cmd(struct mp_log *log, struct mp_cmd *cmd, bstr name) +{ + if (name.len == 0) { + mp_err(log, "Command name missing.\n"); + return false; + } + + char nname[80]; + snprintf(nname, sizeof(nname), "%.*s", BSTR_P(name)); + for (int n = 0; nname[n]; n++) { + if (nname[n] == '_') + nname[n] = '-'; + } + + for (int n = 0; mp_cmds[n].name; n++) { + if (strcmp(nname, mp_cmds[n].name) == 0) { + cmd->def = &mp_cmds[n]; + cmd->name = (char *)cmd->def->name; + return true; + } + } + mp_err(log, "Command '%.*s' not found.\n", BSTR_P(name)); + return false; +} + +static bool is_vararg(const struct mp_cmd_def *m, int i) +{ + return m->vararg && (i + 1 >= MP_CMD_DEF_MAX_ARGS || !m->args[i + 1].type); +} + +static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i) +{ + const struct m_option *opt = NULL; + if (is_vararg(cmd, i)) { + // The last arg in a vararg command sets all vararg types. + for (int n = MPMIN(i, MP_CMD_DEF_MAX_ARGS - 1); n >= 0; n--) { + if (cmd->args[n].type) { + opt = &cmd->args[n]; + break; + } + } + } else if (i < MP_CMD_DEF_MAX_ARGS) { + opt = &cmd->args[i]; + } + return opt && opt->type ? opt : NULL; +} + +// Verify that there are missing args, fill in missing optional args. +static bool finish_cmd(struct mp_log *log, struct mp_cmd *cmd) +{ + for (int i = cmd->nargs; i < MP_CMD_DEF_MAX_ARGS; i++) { + const struct m_option *opt = get_arg_type(cmd->def, i); + if (!opt || is_vararg(cmd->def, i)) + break; + if (!opt->defval && !(opt->flags & MP_CMD_OPT_ARG)) { + mp_err(log, "Command %s: more than %d arguments required.\n", + cmd->name, cmd->nargs); + return false; + } + struct mp_cmd_arg arg = {.type = opt}; + if (opt->defval) + m_option_copy(opt, &arg.v, opt->defval); + MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); + } + return true; +} + +struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node) +{ + struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); + talloc_set_destructor(cmd, destroy_cmd); + *cmd = (struct mp_cmd) { .scale = 1, .scale_units = 1 }; + + if (node->format != MPV_FORMAT_NODE_ARRAY) + goto error; + mpv_node_list *args = node->u.list; + int cur = 0; + + while (cur < args->num) { + if (args->values[cur].format != MPV_FORMAT_STRING) + break; + if (!apply_flag(cmd, bstr0(args->values[cur].u.string))) + break; + cur++; + } + + bstr cmd_name = {0}; + if (cur < args->num && args->values[cur].format == MPV_FORMAT_STRING) + cmd_name = bstr0(args->values[cur++].u.string); + if (!find_cmd(log, cmd, cmd_name)) + goto error; + + int first = cur; + for (int i = 0; i < args->num - first; i++) { + const struct m_option *opt = get_arg_type(cmd->def, i); + if (!opt) { + mp_err(log, "Command %s: has only %d arguments.\n", cmd->name, i); + goto error; + } + mpv_node *val = &args->values[cur++]; + struct mp_cmd_arg arg = {.type = opt}; + void *dst = &arg.v; + if (val->format == MPV_FORMAT_STRING) { + int r = m_option_parse(log, opt, bstr0(cmd->name), + bstr0(val->u.string), dst); + if (r < 0) { + mp_err(log, "Command %s: argument %d can't be parsed: %s.\n", + cmd->name, i + 1, m_option_strerror(r)); + goto error; + } + } else { + int r = m_option_set_node(opt, dst, val); + if (r < 0) { + mp_err(log, "Command %s: argument %d has incompatible type.\n", + cmd->name, i + 1); + goto error; + } + } + MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); + } + + if (!finish_cmd(log, cmd)) + goto error; + + return cmd; +error: + talloc_free(cmd); + return NULL; +} + + +static bool read_token(bstr str, bstr *out_rest, bstr *out_token) +{ + bstr t = bstr_lstrip(str); + int next = bstrcspn(t, WHITESPACE "#;"); + if (!next) + return false; + *out_token = bstr_splice(t, 0, next); + *out_rest = bstr_cut(t, next); + return true; +} + +struct parse_ctx { + struct mp_log *log; + void *tmp; + bstr start, str; +}; + +static int pctx_read_token(struct parse_ctx *ctx, bstr *out) +{ + *out = (bstr){0}; + ctx->str = bstr_lstrip(ctx->str); + bstr start = ctx->str; + if (bstr_eatstart0(&ctx->str, "\"")) { + if (!mp_append_escaped_string_noalloc(ctx->tmp, out, &ctx->str)) { + MP_ERR(ctx, "Broken string escapes: ...>%.*s<.\n", BSTR_P(start)); + return -1; + } + if (!bstr_eatstart0(&ctx->str, "\"")) { + MP_ERR(ctx, "Unterminated quotes: ...>%.*s<.\n", BSTR_P(start)); + return -1; + } + return 1; + } + return read_token(ctx->str, &ctx->str, out) ? 1 : 0; +} + +static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp, + bstr *str, const char *loc) +{ + struct parse_ctx *ctx = &(struct parse_ctx){ + .log = log, + .tmp = tmp, + .str = *str, + .start = *str, + }; + + struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd); + talloc_set_destructor(cmd, destroy_cmd); + *cmd = (struct mp_cmd) { + .flags = MP_ON_OSD_AUTO | MP_EXPAND_PROPERTIES, + .scale = 1, + .scale_units = 1, + }; + + ctx->str = bstr_lstrip(ctx->str); + + bstr cur_token; + if (pctx_read_token(ctx, &cur_token) < 0) + goto error; + + while (1) { + if (!apply_flag(cmd, cur_token)) + break; + if (pctx_read_token(ctx, &cur_token) < 0) + goto error; + } + + if (!find_cmd(ctx->log, cmd, cur_token)) + goto error; + + for (int i = 0; i < MP_CMD_MAX_ARGS; i++) { + const struct m_option *opt = get_arg_type(cmd->def, i); + if (!opt) + break; + + int r = pctx_read_token(ctx, &cur_token); + if (r < 0) { + MP_ERR(ctx, "Command %s: error in argument %d.\n", cmd->name, i + 1); + goto error; + } + if (r < 1) + break; + + struct mp_cmd_arg arg = {.type = opt}; + r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &arg.v); + if (r < 0) { + MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n", + cmd->name, i + 1, m_option_strerror(r)); + goto error; + } + + MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg); + } + + if (!finish_cmd(ctx->log, cmd)) + goto error; + + bstr dummy; + if (read_token(ctx->str, &dummy, &dummy) && ctx->str.len) { + MP_ERR(ctx, "Command %s has trailing unused arguments: '%.*s'.\n", + cmd->name, BSTR_P(ctx->str)); + // Better make it fatal to make it clear something is wrong. + goto error; + } + + bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start}; + cmd->original = bstrdup(cmd, bstr_strip(orig)); + + *str = ctx->str; + return cmd; + +error: + MP_ERR(ctx, "Command was defined at %s.\n", loc); + talloc_free(cmd); + *str = ctx->str; + return NULL; +} + +mp_cmd_t *mp_input_parse_cmd_(struct mp_log *log, bstr str, const char *loc) +{ + void *tmp = talloc_new(NULL); + bstr original = str; + struct mp_cmd *cmd = parse_cmd_str(log, tmp, &str, loc); + if (!cmd) + goto done; + + // Handle "multi" commands + struct mp_cmd **p_prev = NULL; + while (1) { + str = bstr_lstrip(str); + // read_token just to check whether it's trailing whitespace only + bstr u1, u2; + if (!bstr_eatstart0(&str, ";") || !read_token(str, &u1, &u2)) + break; + // Multi-command. Since other input.c code uses queue_next for its + // own purposes, a pseudo-command is used to wrap the command list. + if (!p_prev) { + struct mp_cmd *list = talloc_ptrtype(NULL, list); + talloc_set_destructor(list, destroy_cmd); + *list = (struct mp_cmd) { + .name = (char *)mp_cmd_list.name, + .def = &mp_cmd_list, + .original = bstrdup(list, original), + }; + talloc_steal(list, cmd); + struct mp_cmd_arg arg = {0}; + arg.v.p = cmd; + list->args = talloc_dup(list, &arg); + p_prev = &cmd->queue_next; + cmd = list; + } + struct mp_cmd *sub = parse_cmd_str(log, tmp, &str, loc); + if (!sub) { + talloc_free(cmd); + cmd = NULL; + goto done; + } + talloc_steal(cmd, sub); + *p_prev = sub; + p_prev = &sub->queue_next; + } + +done: + talloc_free(tmp); + return cmd; +} + +struct mp_cmd *mp_input_parse_cmd_strv(struct mp_log *log, const char **argv) +{ + int count = 0; + while (argv[count]) + count++; + mpv_node *items = talloc_zero_array(NULL, mpv_node, count); + mpv_node_list list = {.values = items, .num = count}; + mpv_node node = {.format = MPV_FORMAT_NODE_ARRAY, .u = {.list = &list}}; + for (int n = 0; n < count; n++) { + items[n] = (mpv_node){.format = MPV_FORMAT_STRING, + .u = {.string = (char *)argv[n]}}; + } + struct mp_cmd *res = mp_input_parse_cmd_node(log, &node); + talloc_free(items); + return res; +} + +void mp_cmd_free(mp_cmd_t *cmd) +{ + talloc_free(cmd); +} + +mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd) +{ + if (!cmd) + return NULL; + + mp_cmd_t *ret = talloc_dup(NULL, cmd); + talloc_set_destructor(ret, destroy_cmd); + ret->name = talloc_strdup(ret, cmd->name); + ret->args = talloc_zero_array(ret, struct mp_cmd_arg, ret->nargs); + for (int i = 0; i < ret->nargs; i++) { + ret->args[i].type = cmd->args[i].type; + m_option_copy(ret->args[i].type, &ret->args[i].v, &cmd->args[i].v); + } + ret->original = bstrdup(ret, cmd->original); + ret->key_name = talloc_strdup(ret, ret->key_name); + + if (cmd->def == &mp_cmd_list) { + struct mp_cmd *prev = NULL; + for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) { + sub = mp_cmd_clone(sub); + talloc_steal(ret, sub); + if (prev) { + prev->queue_next = sub; + } else { + struct mp_cmd_arg arg = {0}; + arg.v.p = sub; + ret->args = talloc_dup(ret, &arg); + } + prev = sub; + } + } + + return ret; +} + +void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd) +{ + if (!mp_msg_test(log, msgl)) + return; + if (header) + mp_msg(log, msgl, "%s ", header); + if (!cmd) { + mp_msg(log, msgl, "(NULL)\n"); + return; + } + mp_msg(log, msgl, "%s, flags=%d, args=[", cmd->name, cmd->flags); + for (int n = 0; n < cmd->nargs; n++) { + char *s = m_option_print(cmd->args[n].type, &cmd->args[n].v); + if (n) + mp_msg(log, msgl, ", "); + mp_msg(log, msgl, "%s", s ? s : "(NULL)"); + talloc_free(s); + } + mp_msg(log, msgl, "]\n"); +} + +static int parse_cycle_dir(struct mp_log *log, const struct m_option *opt, + struct bstr name, struct bstr param, void *dst) +{ + double val; + if (bstrcmp0(param, "up") == 0) { + val = +1; + } else if (bstrcmp0(param, "down") == 0) { + val = -1; + } else { + return m_option_type_double.parse(log, opt, name, param, dst); + } + *(double *)dst = val; + return 1; +} + +static void copy_opt(const m_option_t *opt, void *dst, const void *src) +{ + if (dst && src) + memcpy(dst, src, opt->type->size); +} + +const struct m_option_type m_option_type_cycle_dir = { + .name = "up|down", + .parse = parse_cycle_dir, + .copy = copy_opt, + .size = sizeof(double), +}; -- cgit v1.2.3