mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-11 01:27:58 +01:00
lightning-cli: print notifications (with '# ' prefix) if received.
This can be suppressed with -N. Note that we wull get an error with older lightningd, but we ignore it anyway. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: cli: print notifications and progress bars if commands provide them.
This commit is contained in:
parent
806f208295
commit
41290a436f
7 changed files with 207 additions and 15 deletions
|
@ -10,6 +10,7 @@ LIGHTNING_CLI_COMMON_OBJS := \
|
|||
common/configdir.o \
|
||||
common/json.o \
|
||||
common/json_stream.o \
|
||||
common/status_levels.o \
|
||||
common/utils.o \
|
||||
common/version.o
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <common/json.h>
|
||||
#include <common/json_command.h>
|
||||
#include <common/memleak.h>
|
||||
#include <common/status_levels.h>
|
||||
#include <common/utils.h>
|
||||
#include <common/version.h>
|
||||
#include <libgen.h>
|
||||
|
@ -470,6 +471,135 @@ static enum format choose_format(const char *resp,
|
|||
return format;
|
||||
}
|
||||
|
||||
static bool handle_notify(const char *buf, jsmntok_t *toks,
|
||||
enum log_level notification_level,
|
||||
bool *last_was_progress)
|
||||
{
|
||||
const jsmntok_t *id, *method, *params;
|
||||
|
||||
if (toks->type != JSMN_OBJECT)
|
||||
return false;
|
||||
|
||||
id = json_get_member(buf, toks, "id");
|
||||
if (id)
|
||||
return false;
|
||||
|
||||
method = json_get_member(buf, toks, "method");
|
||||
if (!method)
|
||||
return false;
|
||||
|
||||
params = json_get_member(buf, toks, "params");
|
||||
if (!params)
|
||||
return false;
|
||||
|
||||
/* Print nothing if --notifications=none */
|
||||
if (notification_level == LOG_LEVEL_MAX + 1)
|
||||
return true;
|
||||
|
||||
/* We try to be robust if malformed */
|
||||
if (json_tok_streq(buf, method, "message")) {
|
||||
const jsmntok_t *message, *leveltok;
|
||||
enum log_level level;
|
||||
|
||||
leveltok = json_get_member(buf, params, "level");
|
||||
if (!leveltok
|
||||
|| !log_level_parse(buf + leveltok->start,
|
||||
leveltok->end - leveltok->start,
|
||||
&level)
|
||||
|| level < notification_level)
|
||||
return true;
|
||||
|
||||
if (*last_was_progress)
|
||||
printf("\n");
|
||||
*last_was_progress = false;
|
||||
message = json_get_member(buf, params, "message");
|
||||
if (!message)
|
||||
return true;
|
||||
|
||||
printf("# %.*s\n",
|
||||
message->end - message->start,
|
||||
buf + message->start);
|
||||
} else if (json_tok_streq(buf, method, "progress")) {
|
||||
const jsmntok_t *num, *total, *stage;
|
||||
u32 n, tot;
|
||||
char bar[60 + 1];
|
||||
char totstr[STR_MAX_CHARS(u32)];
|
||||
|
||||
num = json_get_member(buf, params, "num");
|
||||
total = json_get_member(buf, params, "total");
|
||||
if (!num || !total)
|
||||
return true;
|
||||
if (!json_to_u32(buf, num, &n)
|
||||
|| !json_to_u32(buf, total, &tot))
|
||||
return true;
|
||||
|
||||
/* Ph3ar my gui skillz! */
|
||||
printf("\r# ");
|
||||
stage = json_get_member(buf, params, "stage");
|
||||
if (stage) {
|
||||
u32 stage_num, stage_total;
|
||||
json_to_u32(buf, json_get_member(buf, stage, "num"),
|
||||
&stage_num);
|
||||
json_to_u32(buf, json_get_member(buf, stage, "total"),
|
||||
&stage_total);
|
||||
snprintf(totstr, sizeof(totstr), "%u", stage_total);
|
||||
printf("Stage %*u/%s ",
|
||||
(int)strlen(totstr), stage_num+1, totstr);
|
||||
}
|
||||
snprintf(totstr, sizeof(totstr), "%u", tot);
|
||||
printf("%*u/%s ", (int)strlen(totstr), n+1, totstr);
|
||||
memset(bar, ' ', sizeof(bar)-1);
|
||||
memset(bar, '=', (double)strlen(bar) / (tot-1) * n);
|
||||
bar[sizeof(bar)-1] = '\0';
|
||||
printf("|%s|", bar);
|
||||
/* Leave bar there if it's finished. */
|
||||
if (n+1 == tot) {
|
||||
printf("\n");
|
||||
*last_was_progress = false;
|
||||
} else {
|
||||
fflush(stdout);
|
||||
*last_was_progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void enable_notifications(int fd)
|
||||
{
|
||||
const char *enable = "{ \"jsonrpc\": \"2.0\", \"method\": \"notifications\", \"id\": 0, \"params\": { \"enable\": true } }";
|
||||
char rbuf[100];
|
||||
|
||||
if (!write_all(fd, enable, strlen(enable)))
|
||||
err(ERROR_TALKING_TO_LIGHTNINGD, "Writing enable command");
|
||||
|
||||
/* We get a very simple response, ending in \n\n. */
|
||||
memset(rbuf, 0, sizeof(rbuf));
|
||||
while (!strends(rbuf, "\n\n")) {
|
||||
size_t len = strlen(rbuf);
|
||||
if (read(fd, rbuf + len, sizeof(rbuf) - len) < 0)
|
||||
err(ERROR_TALKING_TO_LIGHTNINGD,
|
||||
"Reading enable response");
|
||||
}
|
||||
}
|
||||
|
||||
static char *opt_set_level(const char *arg, enum log_level *level)
|
||||
{
|
||||
if (streq(arg, "none"))
|
||||
*level = LOG_LEVEL_MAX + 1;
|
||||
else if (!log_level_parse(arg, strlen(arg), level))
|
||||
return "Invalid level";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void opt_show_level(char buf[OPT_SHOW_LEN], const enum log_level *level)
|
||||
{
|
||||
if (*level == LOG_LEVEL_MAX + 1)
|
||||
strncpy(buf, "none", OPT_SHOW_LEN-1);
|
||||
else
|
||||
strncpy(buf, log_level_name(*level), OPT_SHOW_LEN-1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
setup_locale();
|
||||
|
@ -487,6 +617,8 @@ int main(int argc, char *argv[])
|
|||
int parserr;
|
||||
enum format format = DEFAULT_FORMAT;
|
||||
enum input input = DEFAULT_INPUT;
|
||||
enum log_level notification_level = LOG_INFORM;
|
||||
bool last_was_progress = false;
|
||||
char *command = NULL;
|
||||
|
||||
err_set_progname(argv[0]);
|
||||
|
@ -514,6 +646,9 @@ int main(int argc, char *argv[])
|
|||
"Use format key=value for <params>");
|
||||
opt_register_noarg("-o|--order", opt_set_ordered, &input,
|
||||
"Use params in order for <params>");
|
||||
opt_register_arg("-N|--notifications", opt_set_level,
|
||||
opt_show_level, ¬ification_level,
|
||||
"Set notification level, or none");
|
||||
|
||||
opt_register_version();
|
||||
|
||||
|
@ -567,6 +702,10 @@ int main(int argc, char *argv[])
|
|||
"Connecting to '%s'", rpc_filename);
|
||||
|
||||
idstr = tal_fmt(ctx, "lightning-cli-%i", getpid());
|
||||
|
||||
if (notification_level <= LOG_LEVEL_MAX)
|
||||
enable_notifications(fd);
|
||||
|
||||
cmd = tal_fmt(ctx,
|
||||
"{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\", \"params\" :",
|
||||
json_escape(ctx, method)->s, idstr);
|
||||
|
@ -607,6 +746,7 @@ int main(int argc, char *argv[])
|
|||
/* Start with 1000 characters, 100 tokens. */
|
||||
resp = tal_arr(ctx, char, 1000);
|
||||
toks = tal_arr(ctx, jsmntok_t, 100);
|
||||
toks[0].type = JSMN_UNDEFINED;
|
||||
|
||||
off = 0;
|
||||
parserr = 0;
|
||||
|
@ -632,19 +772,34 @@ int main(int argc, char *argv[])
|
|||
case JSMN_ERROR_INVAL:
|
||||
errx(ERROR_TALKING_TO_LIGHTNINGD,
|
||||
"Malformed response '%s'", resp);
|
||||
case JSMN_ERROR_NOMEM: {
|
||||
case JSMN_ERROR_NOMEM:
|
||||
/* Need more tokens, double it */
|
||||
if (!tal_resize(&toks, tal_count(toks) * 2))
|
||||
oom_dump(fd, resp, off);
|
||||
break;
|
||||
}
|
||||
case JSMN_ERROR_PART:
|
||||
/* Need more data: make room if necessary */
|
||||
if (off == tal_bytelen(resp) - 1) {
|
||||
if (!tal_resize(&resp, tal_count(resp) * 2))
|
||||
oom_dump(fd, resp, off);
|
||||
/* We may actually have a complete token! */
|
||||
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
|
||||
/* Need more data: make room if necessary */
|
||||
if (off == tal_bytelen(resp) - 1) {
|
||||
if (!tal_resize(&resp, tal_count(resp) * 2))
|
||||
oom_dump(fd, resp, off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* Otherwise fall through... */
|
||||
default:
|
||||
if (handle_notify(resp, toks, notification_level,
|
||||
&last_was_progress)) {
|
||||
/* +2 for \n\n */
|
||||
size_t len = toks[0].end - toks[0].start + 2;
|
||||
memmove(resp, resp + len, off - len);
|
||||
off -= len;
|
||||
jsmn_init(&parser);
|
||||
toks[0].type = JSMN_UNDEFINED;
|
||||
/* Don't force another read! */
|
||||
parserr = JSMN_ERROR_NOMEM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -652,7 +807,10 @@ int main(int argc, char *argv[])
|
|||
errx(ERROR_TALKING_TO_LIGHTNINGD,
|
||||
"Non-object response '%s'", resp);
|
||||
|
||||
/* This can rellocate toks, so call before getting pointers to tokens */
|
||||
if (last_was_progress)
|
||||
printf("\n");
|
||||
|
||||
/* This can reallocate toks, so call before getting pointers to tokens */
|
||||
format = choose_format(resp, &toks, method, command, format);
|
||||
result = json_get_member(resp, toks, "result");
|
||||
error = json_get_member(resp, toks, "error");
|
||||
|
|
|
@ -90,6 +90,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
|
|||
char *json_member_direct(struct json_stream *js UNNEEDED,
|
||||
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
||||
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
|
||||
/* Generated stub for log_level_name */
|
||||
const char *log_level_name(enum log_level level UNNEEDED)
|
||||
{ fprintf(stderr, "log_level_name called!\n"); abort(); }
|
||||
/* Generated stub for log_level_parse */
|
||||
bool log_level_parse(const char *levelstr UNNEEDED, size_t len UNNEEDED,
|
||||
enum log_level *level UNNEEDED)
|
||||
{ fprintf(stderr, "log_level_parse called!\n"); abort(); }
|
||||
/* Generated stub for towire_amount_msat */
|
||||
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
||||
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
||||
|
@ -162,11 +169,11 @@ int main(int argc UNUSED, char *argv[])
|
|||
{
|
||||
setup_locale();
|
||||
|
||||
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "-H", "listconfigs", NULL };
|
||||
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "-H", "listconfigs", "-N", "none", NULL };
|
||||
|
||||
response_off = 0;
|
||||
max_read_return = -1;
|
||||
assert(test_main(4, fake_argv) == 0);
|
||||
assert(test_main(6, fake_argv) == 0);
|
||||
assert(!taken_any());
|
||||
take_cleanup();
|
||||
return 0;
|
||||
|
|
|
@ -90,6 +90,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
|
|||
char *json_member_direct(struct json_stream *js UNNEEDED,
|
||||
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
||||
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
|
||||
/* Generated stub for log_level_name */
|
||||
const char *log_level_name(enum log_level level UNNEEDED)
|
||||
{ fprintf(stderr, "log_level_name called!\n"); abort(); }
|
||||
/* Generated stub for log_level_parse */
|
||||
bool log_level_parse(const char *levelstr UNNEEDED, size_t len UNNEEDED,
|
||||
enum log_level *level UNNEEDED)
|
||||
{ fprintf(stderr, "log_level_parse called!\n"); abort(); }
|
||||
/* Generated stub for towire_amount_msat */
|
||||
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
||||
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
||||
|
@ -171,7 +178,7 @@ int main(int argc UNUSED, char *argv[])
|
|||
{
|
||||
setup_locale();
|
||||
|
||||
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "test", NULL };
|
||||
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "test", "-N", "none", NULL };
|
||||
|
||||
/* sizeof() is an overestimate, but we don't care. */
|
||||
response = tal_arr(NULL, char,
|
||||
|
@ -196,7 +203,7 @@ int main(int argc UNUSED, char *argv[])
|
|||
|
||||
response_off = 0;
|
||||
max_read_return = -1;
|
||||
assert(test_main(3, fake_argv) == 0);
|
||||
assert(test_main(5, fake_argv) == 0);
|
||||
tal_free(response);
|
||||
assert(!taken_any());
|
||||
take_cleanup();
|
||||
|
|
|
@ -93,6 +93,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
|
|||
char *json_member_direct(struct json_stream *js UNNEEDED,
|
||||
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
||||
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
|
||||
/* Generated stub for log_level_name */
|
||||
const char *log_level_name(enum log_level level UNNEEDED)
|
||||
{ fprintf(stderr, "log_level_name called!\n"); abort(); }
|
||||
/* Generated stub for log_level_parse */
|
||||
bool log_level_parse(const char *levelstr UNNEEDED, size_t len UNNEEDED,
|
||||
enum log_level *level UNNEEDED)
|
||||
{ fprintf(stderr, "log_level_parse called!\n"); abort(); }
|
||||
/* Generated stub for towire_amount_msat */
|
||||
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
||||
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
||||
|
@ -168,10 +175,10 @@ int main(int argc UNUSED, char *argv[])
|
|||
{
|
||||
setup_locale();
|
||||
|
||||
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "test", NULL };
|
||||
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "test", "-N", "none", NULL };
|
||||
|
||||
output = tal_strdup(NULL, "");
|
||||
assert(test_main(3, fake_argv) == 0);
|
||||
assert(test_main(5, fake_argv) == 0);
|
||||
|
||||
assert(streq(output, "channels=\n"
|
||||
"\n"
|
||||
|
|
|
@ -61,6 +61,12 @@ This is useful for simple scripts which want to find a specific output
|
|||
field without parsing JSON\.
|
||||
|
||||
|
||||
\fB--notifications\fR/\fB-N\fR=\fILEVEL\fR
|
||||
If \fILEVEL\fR is 'none', then never print out notifications\. Otherwise,
|
||||
print out notifications of \fILEVEL\fR or above (one of \fBio\fR, \fBdebug\fR,
|
||||
\fBinfo\fR (the default), \fBunusual\fR or \fBbroken\fR: they are prefixed with \fB#\fR\.
|
||||
|
||||
|
||||
\fB--help\fR/\fB-h\fR
|
||||
Pretty-print summary of options to standard output and exit\. The format can
|
||||
be changed using -F, -R, -J, -H etc\.
|
||||
|
@ -118,4 +124,4 @@ Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
|
|||
Note: the modules in the ccan/ directory have their own licenses, but
|
||||
the rest of the code is covered by the BSD-style MIT license\.
|
||||
|
||||
\" SHA256STAMP:0269a00171f6ff2bbcb772b9367e05787345faf6457e75141978999fc0103d4a
|
||||
\" SHA256STAMP:b626a2499bd231acc33bf1c279c62de2a7ad2046c6c63965b97f74ec4e861365
|
||||
|
|
|
@ -54,6 +54,12 @@ Return JSON result in flattened one-per-line output, e.g. `{ "help":
|
|||
This is useful for simple scripts which want to find a specific output
|
||||
field without parsing JSON.
|
||||
|
||||
**--notifications**/**-N**=*LEVEL*
|
||||
If *LEVEL* is 'none', then never print out notifications. Otherwise,
|
||||
print out notifications of *LEVEL* or above (one of `io`, `debug`,
|
||||
`info` (the default), `unusual` or `broken`: they are prefixed with `#
|
||||
`.
|
||||
|
||||
**--help**/**-h**
|
||||
Pretty-print summary of options to standard output and exit. The format can
|
||||
be changed using -F, -R, -J, -H etc.
|
||||
|
|
Loading…
Add table
Reference in a new issue