Skip to content

Commit 4e82d26

Browse files
committed
input: add a way to pipe multiple commands
Very similar to typical shell semantics. If a command writes output to a result, the next command can now read those results and use it. The current implementation assumes that the only command results will always be strings and does not attempt to deal with any other type. This can always be expanded in the future if a need arises.
1 parent f9271fb commit 4e82d26

File tree

6 files changed

+65
-8
lines changed

6 files changed

+65
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
commands can now pipe output into each other e.g. `expand-text "foo" | show-text -`

DOCS/man/input.rst

+10
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ You can bind multiple commands to one key. For example:
6969

7070
| a show-text "command 1" ; show-text "command 2"
7171
72+
Some commands also write output to a result. You can pipe the result to the next
73+
command in a way similar to the shell.
74+
75+
| a expand-text "text" | show-text -
76+
77+
``|`` Is the special character that designates the pipe. It is functionally
78+
similar to using ``;``. Specifying ``-`` indicates that the argument will
79+
receive the output of the previous run command. It is possible to specify ``-``
80+
multiple times.
81+
7282
It's also possible to bind a command to a sequence of keys:
7383

7484
| a-b-c show-text "command run after a, b, c have been pressed"

input/cmd.c

+13-7
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ struct mp_cmd *mp_input_parse_cmd_node(struct mp_log *log, mpv_node *node)
313313
static bool read_token(bstr str, bstr *out_rest, bstr *out_token)
314314
{
315315
bstr t = bstr_lstrip(str);
316-
int next = bstrcspn(t, WHITESPACE "#;");
316+
int next = bstrcspn(t, WHITESPACE "#;|");
317317
if (!next)
318318
return false;
319319
*out_token = bstr_splice(t, 0, next);
@@ -417,11 +417,15 @@ static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
417417
break;
418418

419419
struct mp_cmd_arg arg = {.type = opt};
420-
r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &arg.v);
421-
if (r < 0) {
422-
MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n",
423-
cmd->name, i + 1, m_option_strerror(r));
424-
goto error;
420+
if (bstrcmp0(cur_token, "-") == 0) {
421+
arg.substitute = true;
422+
} else {
423+
r = m_option_parse(ctx->log, opt, bstr0(cmd->name), cur_token, &arg.v);
424+
if (r < 0) {
425+
MP_ERR(ctx, "Command %s: argument %d can't be parsed: %s.\n",
426+
cmd->name, i + 1, m_option_strerror(r));
427+
goto error;
428+
}
425429
}
426430

427431
MP_TARRAY_APPEND(cmd, cmd->args, cmd->nargs, arg);
@@ -465,7 +469,8 @@ mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc)
465469
str = bstr_lstrip(str);
466470
// read_token just to check whether it's trailing whitespace only
467471
bstr u1, u2;
468-
if (!bstr_eatstart0(&str, ";") || !read_token(str, &u1, &u2))
472+
bool receive_pipe = bstr_startswith(str, bstr0("|"));
473+
if ((!bstr_eatstart0(&str, ";") && !bstr_eatstart0(&str, "|")) || !read_token(str, &u1, &u2))
469474
break;
470475
// Multi-command. Since other input.c code uses queue_next for its
471476
// own purposes, a pseudo-command is used to wrap the command list.
@@ -489,6 +494,7 @@ mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc)
489494
cmd = NULL;
490495
goto done;
491496
}
497+
sub->receive_pipe = receive_pipe;
492498
talloc_steal(cmd, sub);
493499
*p_prev = sub;
494500
p_prev = &sub->queue_next;

input/cmd.h

+2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ struct mp_cmd_arg {
9797
char **str_list;
9898
void *p;
9999
} v;
100+
bool substitute;
100101
};
101102

102103
typedef struct mp_cmd {
@@ -114,6 +115,7 @@ typedef struct mp_cmd {
114115
bool repeated : 1;
115116
bool mouse_move : 1;
116117
bool canceled : 1;
118+
bool receive_pipe : 1; // can read output from previous command
117119
int mouse_x, mouse_y;
118120
struct mp_cmd *queue_next;
119121
double scale; // for scaling numeric arguments

player/command.c

+38-1
Original file line numberDiff line numberDiff line change
@@ -5296,11 +5296,37 @@ struct cmd_list_ctx {
52965296
int num_sub;
52975297
};
52985298

5299+
static bool substitute_args(struct MPContext *ctx, struct mp_cmd *cmd,
5300+
struct mpv_node *replace)
5301+
{
5302+
if (!cmd->receive_pipe)
5303+
return true;
5304+
5305+
// TODO: allow more types?
5306+
if (replace->format != MPV_FORMAT_STRING)
5307+
return false;
5308+
5309+
for (int i = 0; i < cmd->nargs; ++i) {
5310+
if (!cmd->args[i].substitute)
5311+
continue;
5312+
int r = m_option_parse(ctx->log, cmd->args[i].type, bstr0(cmd->name),
5313+
bstr0(replace->u.string), &cmd->args[i].v);
5314+
if (r < 0)
5315+
return false;
5316+
}
5317+
return true;
5318+
}
5319+
52995320
static void continue_cmd_list(struct cmd_list_ctx *list);
53005321

53015322
static void on_cmd_list_sub_completion(struct mp_cmd_ctx *cmd)
53025323
{
53035324
struct cmd_list_ctx *list = cmd->on_completion_priv;
5325+
if (cmd->cmd->receive_pipe)
5326+
mpv_free_node_contents(&list->parent->previous_result);
5327+
5328+
if (cmd->cmd->queue_next && cmd->cmd->queue_next->receive_pipe)
5329+
list->parent->previous_result = cmd->result;
53045330

53055331
if (list->current_valid && mp_thread_id_equal(list->current_tid, mp_thread_current_id())) {
53065332
list->completed_recursive = true;
@@ -5327,6 +5353,11 @@ static void continue_cmd_list(struct cmd_list_ctx *list)
53275353
list->current_valid = true;
53285354
list->current_tid = mp_thread_current_id();
53295355

5356+
if (!substitute_args(list->mpctx, sub, &list->parent->previous_result)) {
5357+
MP_ERR(list->mpctx, "Pipe input for command '%s' could not be parse.\n", sub->name);
5358+
talloc_free(sub);
5359+
continue;
5360+
}
53305361
run_command(list->mpctx, sub, NULL, on_cmd_list_sub_completion, list);
53315362

53325363
list->current_valid = false;
@@ -5376,7 +5407,8 @@ void mp_cmd_ctx_complete(struct mp_cmd_ctx *cmd)
53765407
cmd->on_completion(cmd);
53775408
if (cmd->abort)
53785409
mp_abort_remove(cmd->mpctx, cmd->abort);
5379-
mpv_free_node_contents(&cmd->result);
5410+
if (!cmd->cmd->queue_next || !cmd->cmd->queue_next->receive_pipe)
5411+
mpv_free_node_contents(&cmd->result);
53805412
talloc_free(cmd);
53815413
}
53825414

@@ -5440,6 +5472,11 @@ void run_command(struct MPContext *mpctx, struct mp_cmd *cmd,
54405472
mp_abort_add(mpctx, ctx->abort);
54415473
}
54425474

5475+
if (ctx->on_completion_priv) {
5476+
struct cmd_list_ctx *list = ctx->on_completion_priv;
5477+
ctx->previous_result = list->parent->previous_result;
5478+
}
5479+
54435480
struct MPOpts *opts = mpctx->opts;
54445481
ctx->on_osd = cmd->flags & MP_ON_OSD_FLAGS;
54455482
bool auto_osd = ctx->on_osd == MP_ON_OSD_AUTO;

player/command.h

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ struct mp_cmd_ctx {
5252
// completion callback).
5353
bool success; // true by default
5454
struct mpv_node result;
55+
struct mpv_node previous_result; // result from the previously run command
5556
// Command handlers can set this to false if returning from the command
5657
// handler does not complete the command. It stops the common command code
5758
// from signaling the completion automatically, and you can call

0 commit comments

Comments
 (0)