MEDIUM: cli: make the prompt mode configurable between n/i/p

Now the prompt mode can more finely be configured between non-interactive
(default), interactive without prompt, and interactive with prompt. This
will ease the usage from automated tools which are not necessarily
interested in having to consume '> ' after each command nor displaying
"+" on payload lines. This can also be convenient when coming from the
master CLI to keep the same output format.
This commit is contained in:
Willy Tarreau 2025-04-28 18:36:57 +02:00
parent f25b4abc9b
commit e5c255c4e5
2 changed files with 93 additions and 24 deletions

View File

@ -1511,9 +1511,10 @@ that the terminal is handled by the readline library which supports line
editing and history, which is very convenient when issuing repeated commands
(eg: watch a counter).
The socket supports two operation modes :
- interactive
- non-interactive
The socket supports three operation modes :
- non-interactive, silent
- interactive, silent
- interactive with prompt
The non-interactive mode is the default when socat connects to the socket. In
this mode, a single line may be sent. It is processed as a whole, responses are
@ -1527,12 +1528,25 @@ example :
If a command needs to use a semi-colon or a backslash (eg: in a value), it
must be preceded by a backslash ('\').
The interactive mode displays a prompt ('>') and waits for commands to be
entered on the line, then processes them, and displays the prompt again to wait
for a new command. This mode is entered via the "prompt" command which must be
sent on the first line in non-interactive mode. The mode is a flip switch, if
"prompt" is sent in interactive mode, it is disabled and the connection closes
after processing the last command of the same line.
The interactive mode allows new commands to be sent after the ones from the
previous lines finish. It exists in two variants, one silent, which works like
the non-interactive mode except that the socket waits for a new command instead
of closing, and one where a prompt is displayed ('>') at the beginning of the
line. The interactive mode is preferred for advanced tools while the prompt
mode is preferred for humans.
The mode can be changed using the "prompt" command. By default, it toggles the
interactive+prompt modes. Entering "prompt" in interactive mode will switch to
prompt mode. The command optionally takes a specific mode among which:
- "n" : non-interactive mode (single command and quits)
- "i" : interactive mode (multiple commands, no prompt)
- "p" : prompt mode (multiple commands with a prompt)
Since the default mode is non-interactive, "prompt" must be used as the first
command in order to switch it, otherwise the previous command will cause the
connection to be closed. Switching to non-interactive mode will result in the
connection to be closed after all the commands of the same line complete.
For this reason, when debugging by hand, it's quite common to start with the
"prompt" command :
@ -1543,6 +1557,9 @@ For this reason, when debugging by hand, it's quite common to start with the
...
>
Interactive tools might prefer starting with "prompt i" to switch to interactive
mode without the prompt.
Optionally the process' uptime may be displayed in the prompt. In order to
enable this, the "prompt timed" command will enable the prompt and toggle the
displaying of the time. The uptime is displayed in format "d:hh:mm:ss" where
@ -2369,15 +2386,26 @@ prepare map <map>
committed. Version numbers are unsigned 32-bit values which wrap at the end,
so care must be taken when comparing them in an external program.
prompt
Toggle the prompt at the beginning of the line and enter or leave interactive
mode. In interactive mode, the connection is not closed after a command
completes. Instead, the prompt will appear again, indicating the user that
the interpreter is waiting for a new command. The prompt consists in a right
angle bracket followed by a space "> ". This mode is particularly convenient
when one wants to periodically check information such as stats or errors.
It is also a good idea to enter interactive mode before issuing a "help"
command.
prompt [help | n | i | p | timed]*
Changes the behavior of the interactive mode and the prompt displayed at the
beginning of the line in interactive mode:
- "help" : displays the command's usage
- "n" : switches to non-interactive mode
- "i" : switches to interactive mode
- "p" : switches to interactive + prompt mode
- "timed" : toggles displaying the time in the prompt
Without any option, this will cycle through prompt mode then non-interactive
mode. In non-interactive mode, the connection is closed after the last
command of the current line compltes. In interactive mode, the connection is
not closed after a command completes, so that a new one can be entered. In
prompt mode, the interactive mode is still in use, and a prompt will appear
at the beginning of the line, indicating to the user that the interpreter is
waiting for a new command. The prompt consists in a right angle bracket
followed by a space "> ".
The prompt mode is more suited to human users, the interactive mode to
advanced scripts, and the non-interactive mode (default) to basic scripts.
quit
Close the connection when in interactive mode.

View File

@ -321,7 +321,7 @@ static char *cli_gen_usage_msg(struct appctx *appctx, char * const *args)
/* always show the prompt/help/quit commands */
chunk_strcat(tmp,
" help [<command>] : list matching or all commands\n"
" prompt [timed] : toggle interactive mode with prompt\n"
" prompt [help | n | i | p | timed ]* : toggle interactive mode with prompt\n"
" quit : disconnect\n");
chunk_init(&out, NULL, 0);
@ -2494,14 +2494,55 @@ static int cli_parse_simple(char **args, char *payload, struct appctx *appctx, v
if (*args[0] == 'h')
/* help */
cli_gen_usage_msg(appctx, args);
else if (*args[0] == 'p')
else if (*args[0] == 'p') {
/* prompt */
if (strcmp(args[1], "timed") == 0) {
appctx->st1 |= APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER;
appctx->st1 ^= APPCTX_CLI_ST1_TIMED;
int mode = 0; // 0=default, 1="n", 2="i", 3="p"
int timed = 0; // non-zero = "timed" present
int arg;
const char *usage =
"Usage: prompt [help | n | i | p | timed]*\n"
"Changes the default prompt and interactive mode. Available options:\n"
" help display this help\n"
" n set to single-command mode (no prompt, single command)\n"
" i set to interactive mode only (no prompt, multi-command)\n"
" p set to prompt+interactive mode (prompt + multi-command)\n"
" timed toggle displaying the current date in the prompt\n"
"Without any argument, toggles between n/i->p, p->n.\n"
;
for (arg = 1; *args[arg]; arg++) {
if (strcmp(args[arg], "n") == 0)
mode = 1;
else if (strcmp(args[arg], "i") == 0)
mode = 2;
else if (strcmp(args[arg], "p") == 0)
mode = 3;
else if (strcmp(args[arg], "timed") == 0)
timed = 1;
else if (strcmp(args[arg], "help") == 0)
return cli_msg(appctx, LOG_INFO, usage);
else
return cli_err(appctx, usage);
}
/* In timed mode, the default is to enable prompt+inter and toggle timed.
* In other mode, the default is to toggle prompt+inter (n/i->p, p->n).
*/
if (timed) {
appctx->st1 &= ~(APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER);
appctx->st1 ^= APPCTX_CLI_ST1_TIMED;
mode = mode ? mode : 3; // p by default
}
if (mode) {
/* force mode (n,i,p) */
appctx->st1 &= ~(APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER);
appctx->st1 |= ((mode >= 3) ? APPCTX_CLI_ST1_PROMPT : 0) | ((mode >= 2) ? APPCTX_CLI_ST1_INTER : 0);
}
else if (~appctx->st1 & (APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER))
appctx->st1 |= APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER; // p
else
appctx->st1 ^= APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER;
appctx->st1 &= ~(APPCTX_CLI_ST1_PROMPT | APPCTX_CLI_ST1_INTER); // n
}
else if (*args[0] == 'q') {
/* quit */
applet_set_eoi(appctx);