input: add glue code for named arguments

Named arguments should make it easier to have long time compatibility,
even if command arguments get added or removed. They're also much nicer
for commands with a large number of arguments, especially if many
arguments are optional.

As of this commit, this can not be used, because there is no command yet
which supports them. See the following commit.
This commit is contained in:
wm4 2018-05-12 14:50:07 +02:00
parent 1157f07c5b
commit 1aae88b487
4 changed files with 253 additions and 52 deletions

View File

@ -78,6 +78,16 @@ that matches, and the multi-key command will never be called. Intermediate keys
can be remapped to ``ignore`` in order to avoid this issue. The maximum number
of (non-modifier) keys for combinations is currently 4.
Named arguments
---------------
Some commands support named arguments (most currently don't). Named arguments
cannot be used with the "flat" input.conf syntax shown above, but require using
e.g. ``mp.command_native()`` in Lua scripting, or e.g. ``mpv_command_node()``
with the libmpv API. Some commands ask you to only use named arguments (because
the command order is not guaranteed), which means you can't use them as
key bindings in input.conf at all.
List of Input Commands
----------------------
@ -283,6 +293,71 @@ List of Input Commands
execute arbitrary shell commands. It is recommended to write a small
shell script, and call that with ``run``.
``subprocess``
Similar to ``run``, but gives more control about process execution to the
caller, and does does not detach the process.
This has the following named arguments. The order of them is not guaranteed,
so you should always call them with named arguments, see `Named arguments`_.
``args`` (``MPV_FORMAT_NODE_ARRAY[MPV_FORMAT_STRING]``)
Array of strings with the command as first argument, and subsequent
command line arguments following. This is just like the ``run`` command
argument list.
``playback_only`` (``MPV_FORMAT_FLAG``)
Boolean indicating whether the process should be killed when playback
terminates (optional, default: yes). If enabled, stopping playback
will automatically kill the process, and you can't start it outside of
playback.
``capture_size`` (``MPV_FORMAT_INT64``)
Integer setting the maximum number of stdout plus stderr bytes that can
be captured (optional, default: 64MB). If the number of bytes exceeds
this, capturing is stopped. The limit is per captured stream.
``capture_stdout`` (``MPV_FORMAT_FLAG``)
Capture all data the process outputs to stdout and return it once the
process ends (optional, default: no).
``capture_stderr`` (``MPV_FORMAT_FLAG``)
Same as ``capture_stdout``, but for stderr.
The command returns the following result (as ``MPV_FORMAT_NODE_MAP``):
``status`` (``MPV_FORMAT_INT64``)
The raw exit status of the process. It will be negative on error. The
meaning of negative values is undefined, other than meaning error (and
does not necessarily correspond to OS low level exit status values).
On Windows, it can happen that a negative return value is returned
even if the process exits gracefully, because the win32 ``UINT`` exit
code is assigned to an ``int`` variable before being set as ``int64_t``
field in the result map. This might be fixed later.
``stdout`` (``MPV_FORMAT_BYTE_ARRAY``)
Captured stdout stream, limited to ``capture_size``.
``stderr`` (``MPV_FORMAT_BYTE_ARRAY``)
Same as ``stdout``, but for stderr.
``error_string`` (``MPV_FORMAT_STRING``)
Empty string if the process exited gracefully. The string ``killed`` if
the process was terminated in an unusual way. The string ``init`` if the
process could not be started.
On Windows, ``killed`` is only returned when the process has been
killed by mpv as a result of ``playback_only`` being set to ``yes``.
``killed_by_us`` (``MPV_FORMAT_FLAG``)
Set to ``yes`` if the process has been killed by mpv as a result
of ``playback_only`` being set to ``yes``.
Note that the command itself will always return success as long as the
parameters are correct. Whether the process could be spawned or whether
it was somehow killed or returned an error status has to be queried from
the result value.
``quit [<code>]``
Exit the player. If an argument is given, it's used as process exit code.

View File

@ -109,7 +109,17 @@ The ``mp`` module is preloaded, although it can be loaded manually with
``mp.command_native(table [,def])``
Similar to ``mp.commandv``, but pass the argument list as table. This has
the advantage that in at least some cases, arguments can be passed as
native types.
native types. It also allows you to use named argument.
If the table is an array, each array item is like an argument in
``mp.commandv()`` (but can be a native type instead of a string).
If the table contains string keys, it's interpreted as command with named
arguments. This requires at least an entry with the key ``name`` to be
present, which must be a string, and contains the command name. The special
entry ``_flags`` is optional, and if present, must be an array of
`Input Command Prefixes`_ to apply. All other entries are interpreted as
arguments.
Returns a result table on success (usually empty), or ``def, error`` on
error. ``def`` is the second parameter provided to the function, and is

View File

@ -18,6 +18,7 @@
#include <stddef.h>
#include "misc/bstr.h"
#include "misc/node.h"
#include "common/common.h"
#include "common/msg.h"
#include "options/m_option.h"
@ -30,8 +31,10 @@
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);
for (int n = 0; n < cmd->nargs; n++) {
if (cmd->args[n].type)
m_option_free(cmd->args[n].type, &cmd->args[n].v);
}
}
struct flag {
@ -111,22 +114,37 @@ static const struct m_option *get_arg_type(const struct mp_cmd_def *cmd, int i)
return opt && opt->type ? opt : NULL;
}
// Verify that there are missing args, fill in missing optional args.
// Return the name of the argument, possibly as stack allocated string (which is
// why this is a macro, and out of laziness). Otherwise as get_arg_type().
#define get_arg_name(cmd, i) \
((i) < MP_CMD_DEF_MAX_ARGS && (cmd)->args[(i)].name && \
(cmd)->args[(i)].name[0] \
? (cmd)->args[(i)].name : mp_tprintf(10, "%d", (i) + 1))
// Verify that there are no 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++) {
for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) {
// (type==NULL is used for yet unset arguments)
if (i < cmd->nargs && cmd->args[i].type)
continue;
const struct m_option *opt = get_arg_type(cmd->def, i);
if (!opt || is_vararg(cmd->def, i))
if (i >= cmd->nargs && (!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);
mp_err(log, "Command %s: required argument %s not set.\n",
cmd->name, get_arg_name(cmd->def, i));
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);
assert(i <= cmd->nargs);
if (i == cmd->nargs) {
MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg);
} else {
cmd->args[i] = arg;
}
}
if (!(cmd->flags & (MP_ASYNC_CMD | MP_SYNC_CMD)))
@ -135,14 +153,54 @@ static bool finish_cmd(struct mp_log *log, struct mp_cmd *cmd)
return true;
}
struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node)
static bool set_node_arg(struct mp_log *log, struct mp_cmd *cmd, int i,
mpv_node *val)
{
struct mp_cmd *cmd = talloc_ptrtype(NULL, cmd);
talloc_set_destructor(cmd, destroy_cmd);
*cmd = (struct mp_cmd) { .scale = 1, .scale_units = 1 };
const char *name = get_arg_name(cmd->def, i);
if (node->format != MPV_FORMAT_NODE_ARRAY)
goto error;
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);
return false;
}
if (i < cmd->nargs && cmd->args[i].type) {
mp_err(log, "Command %s: argument %s was already set.\n", cmd->name, name);
return false;
}
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 %s can't be parsed: %s.\n",
cmd->name, name, m_option_strerror(r));
return false;
}
} else {
int r = m_option_set_node(opt, dst, val);
if (r < 0) {
mp_err(log, "Command %s: argument %s has incompatible type.\n",
cmd->name, name);
return false;
}
}
// (leave unset arguments blank, to be set later or checked by finish_cmd())
while (i >= cmd->nargs) {
struct mp_cmd_arg t = {0};
MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, t);
}
cmd->args[i] = arg;
return true;
}
static bool cmd_node_array(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node)
{
assert(node->format == MPV_FORMAT_NODE_ARRAY);
mpv_node_list *args = node->u.list;
int cur = 0;
@ -158,46 +216,89 @@ struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node)
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;
return false;
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 (!set_node_arg(log, cmd, cmd->nargs, &args->values[cur++]))
return false;
}
if (!finish_cmd(log, cmd))
goto error;
return cmd;
error:
talloc_free(cmd);
return NULL;
return true;
}
static bool cmd_node_map(struct mp_log *log, struct mp_cmd *cmd, mpv_node *node)
{
assert(node->format == MPV_FORMAT_NODE_MAP);
mpv_node_list *args = node->u.list;
mpv_node *name = node_map_get(node, "name");
if (!name || name->format != MPV_FORMAT_STRING)
return false;
if (!find_cmd(log, cmd, bstr0(name->u.string)))
return false;
for (int n = 0; n < args->num; n++) {
const char *key = args->keys[n];
mpv_node *val = &args->values[n];
if (strcmp(key, "name") == 0) {
// already handled above
} else if (strcmp(key, "_flags") == 0) {
if (val->format != MPV_FORMAT_NODE_ARRAY)
return false;
mpv_node_list *flags = val->u.list;
for (int i = 0; i < flags->num; i++) {
if (flags->values[i].format != MPV_FORMAT_STRING)
return false;
if (!apply_flag(cmd, bstr0(flags->values[i].u.string)))
return false;
}
} else {
int arg = -1;
for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) {
const char *arg_name = cmd->def->args[i].name;
if (arg_name && arg_name[0] && strcmp(key, arg_name) == 0) {
arg = i;
break;
}
}
if (arg < 0) {
mp_err(log, "Command %s: no argument %s.\n", cmd->name, key);
return false;
}
if (!set_node_arg(log, cmd, arg, val))
return false;
}
}
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 };
bool res = false;
if (node->format == MPV_FORMAT_NODE_ARRAY) {
res = cmd_node_array(log, cmd, node);
} else if (node->format == MPV_FORMAT_NODE_MAP) {
res = cmd_node_map(log, cmd, node);
}
res = res && finish_cmd(log, cmd);
if (!res)
TA_FREEP(&cmd);
return cmd;
}
static bool read_token(bstr str, bstr *out_rest, bstr *out_token)
{

View File

@ -941,10 +941,25 @@ int mpv_command(mpv_handle *ctx, const char **args);
*
* Does not use OSD and string expansion by default.
*
* @param[in] args mpv_node with format set to MPV_FORMAT_NODE_ARRAY; each entry
* is an argument using an arbitrary format (the format must be
* compatible to the used command). Usually, the first item is
* the command name (as MPV_FORMAT_STRING).
* The args argument can have one of the following formats:
*
* MPV_FORMAT_NODE_ARRAY:
* Positional arguments. Each entry is an argument using an arbitrary
* format (the format must be compatible to the used command). Usually,
* the first item is the command name (as MPV_FORMAT_STRING). The order
* of arguments is as documented in each command description.
*
* MPV_FORMAT_NODE_MAP:
* Named arguments. This requires at least an entry with the key "name"
* to be present, which must be a string, and contains the command name.
* The special entry "_flags" is optional, and if present, must be an
* array of strings, each being a command prefix to apply. All other
* entries are interpreted as arguments. They must use the argument names
* as documented in each command description. Currently, most commands do
* not support named arguments at all.
*
* @param[in] args mpv_node with format set to one of the values documented
* above (see there for details)
* @param[out] result Optional, pass NULL if unused. If not NULL, and if the
* function succeeds, this is set to command-specific return
* data. You must call mpv_free_node_contents() to free it