mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-12 02:08:15 +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/configdir.o \
|
||||||
common/json.o \
|
common/json.o \
|
||||||
common/json_stream.o \
|
common/json_stream.o \
|
||||||
|
common/status_levels.o \
|
||||||
common/utils.o \
|
common/utils.o \
|
||||||
common/version.o
|
common/version.o
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <common/json.h>
|
#include <common/json.h>
|
||||||
#include <common/json_command.h>
|
#include <common/json_command.h>
|
||||||
#include <common/memleak.h>
|
#include <common/memleak.h>
|
||||||
|
#include <common/status_levels.h>
|
||||||
#include <common/utils.h>
|
#include <common/utils.h>
|
||||||
#include <common/version.h>
|
#include <common/version.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
|
@ -470,6 +471,135 @@ static enum format choose_format(const char *resp,
|
||||||
return format;
|
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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
setup_locale();
|
setup_locale();
|
||||||
|
@ -487,6 +617,8 @@ int main(int argc, char *argv[])
|
||||||
int parserr;
|
int parserr;
|
||||||
enum format format = DEFAULT_FORMAT;
|
enum format format = DEFAULT_FORMAT;
|
||||||
enum input input = DEFAULT_INPUT;
|
enum input input = DEFAULT_INPUT;
|
||||||
|
enum log_level notification_level = LOG_INFORM;
|
||||||
|
bool last_was_progress = false;
|
||||||
char *command = NULL;
|
char *command = NULL;
|
||||||
|
|
||||||
err_set_progname(argv[0]);
|
err_set_progname(argv[0]);
|
||||||
|
@ -514,6 +646,9 @@ int main(int argc, char *argv[])
|
||||||
"Use format key=value for <params>");
|
"Use format key=value for <params>");
|
||||||
opt_register_noarg("-o|--order", opt_set_ordered, &input,
|
opt_register_noarg("-o|--order", opt_set_ordered, &input,
|
||||||
"Use params in order for <params>");
|
"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();
|
opt_register_version();
|
||||||
|
|
||||||
|
@ -567,6 +702,10 @@ int main(int argc, char *argv[])
|
||||||
"Connecting to '%s'", rpc_filename);
|
"Connecting to '%s'", rpc_filename);
|
||||||
|
|
||||||
idstr = tal_fmt(ctx, "lightning-cli-%i", getpid());
|
idstr = tal_fmt(ctx, "lightning-cli-%i", getpid());
|
||||||
|
|
||||||
|
if (notification_level <= LOG_LEVEL_MAX)
|
||||||
|
enable_notifications(fd);
|
||||||
|
|
||||||
cmd = tal_fmt(ctx,
|
cmd = tal_fmt(ctx,
|
||||||
"{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\", \"params\" :",
|
"{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\", \"params\" :",
|
||||||
json_escape(ctx, method)->s, idstr);
|
json_escape(ctx, method)->s, idstr);
|
||||||
|
@ -607,6 +746,7 @@ int main(int argc, char *argv[])
|
||||||
/* Start with 1000 characters, 100 tokens. */
|
/* Start with 1000 characters, 100 tokens. */
|
||||||
resp = tal_arr(ctx, char, 1000);
|
resp = tal_arr(ctx, char, 1000);
|
||||||
toks = tal_arr(ctx, jsmntok_t, 100);
|
toks = tal_arr(ctx, jsmntok_t, 100);
|
||||||
|
toks[0].type = JSMN_UNDEFINED;
|
||||||
|
|
||||||
off = 0;
|
off = 0;
|
||||||
parserr = 0;
|
parserr = 0;
|
||||||
|
@ -632,19 +772,34 @@ int main(int argc, char *argv[])
|
||||||
case JSMN_ERROR_INVAL:
|
case JSMN_ERROR_INVAL:
|
||||||
errx(ERROR_TALKING_TO_LIGHTNINGD,
|
errx(ERROR_TALKING_TO_LIGHTNINGD,
|
||||||
"Malformed response '%s'", resp);
|
"Malformed response '%s'", resp);
|
||||||
case JSMN_ERROR_NOMEM: {
|
case JSMN_ERROR_NOMEM:
|
||||||
/* Need more tokens, double it */
|
/* Need more tokens, double it */
|
||||||
if (!tal_resize(&toks, tal_count(toks) * 2))
|
if (!tal_resize(&toks, tal_count(toks) * 2))
|
||||||
oom_dump(fd, resp, off);
|
oom_dump(fd, resp, off);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case JSMN_ERROR_PART:
|
case JSMN_ERROR_PART:
|
||||||
/* Need more data: make room if necessary */
|
/* We may actually have a complete token! */
|
||||||
if (off == tal_bytelen(resp) - 1) {
|
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
|
||||||
if (!tal_resize(&resp, tal_count(resp) * 2))
|
/* Need more data: make room if necessary */
|
||||||
oom_dump(fd, resp, off);
|
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,
|
errx(ERROR_TALKING_TO_LIGHTNINGD,
|
||||||
"Non-object response '%s'", resp);
|
"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);
|
format = choose_format(resp, &toks, method, command, format);
|
||||||
result = json_get_member(resp, toks, "result");
|
result = json_get_member(resp, toks, "result");
|
||||||
error = json_get_member(resp, toks, "error");
|
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,
|
char *json_member_direct(struct json_stream *js UNNEEDED,
|
||||||
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
||||||
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
|
{ 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 */
|
/* Generated stub for towire_amount_msat */
|
||||||
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
||||||
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
||||||
|
@ -162,11 +169,11 @@ int main(int argc UNUSED, char *argv[])
|
||||||
{
|
{
|
||||||
setup_locale();
|
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;
|
response_off = 0;
|
||||||
max_read_return = -1;
|
max_read_return = -1;
|
||||||
assert(test_main(4, fake_argv) == 0);
|
assert(test_main(6, fake_argv) == 0);
|
||||||
assert(!taken_any());
|
assert(!taken_any());
|
||||||
take_cleanup();
|
take_cleanup();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -90,6 +90,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
|
||||||
char *json_member_direct(struct json_stream *js UNNEEDED,
|
char *json_member_direct(struct json_stream *js UNNEEDED,
|
||||||
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
||||||
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
|
{ 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 */
|
/* Generated stub for towire_amount_msat */
|
||||||
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
||||||
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
||||||
|
@ -171,7 +178,7 @@ int main(int argc UNUSED, char *argv[])
|
||||||
{
|
{
|
||||||
setup_locale();
|
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. */
|
/* sizeof() is an overestimate, but we don't care. */
|
||||||
response = tal_arr(NULL, char,
|
response = tal_arr(NULL, char,
|
||||||
|
@ -196,7 +203,7 @@ int main(int argc UNUSED, char *argv[])
|
||||||
|
|
||||||
response_off = 0;
|
response_off = 0;
|
||||||
max_read_return = -1;
|
max_read_return = -1;
|
||||||
assert(test_main(3, fake_argv) == 0);
|
assert(test_main(5, fake_argv) == 0);
|
||||||
tal_free(response);
|
tal_free(response);
|
||||||
assert(!taken_any());
|
assert(!taken_any());
|
||||||
take_cleanup();
|
take_cleanup();
|
||||||
|
|
|
@ -93,6 +93,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
|
||||||
char *json_member_direct(struct json_stream *js UNNEEDED,
|
char *json_member_direct(struct json_stream *js UNNEEDED,
|
||||||
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
|
||||||
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
|
{ 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 */
|
/* Generated stub for towire_amount_msat */
|
||||||
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
|
||||||
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
|
||||||
|
@ -168,10 +175,10 @@ int main(int argc UNUSED, char *argv[])
|
||||||
{
|
{
|
||||||
setup_locale();
|
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, "");
|
output = tal_strdup(NULL, "");
|
||||||
assert(test_main(3, fake_argv) == 0);
|
assert(test_main(5, fake_argv) == 0);
|
||||||
|
|
||||||
assert(streq(output, "channels=\n"
|
assert(streq(output, "channels=\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
|
@ -61,6 +61,12 @@ This is useful for simple scripts which want to find a specific output
|
||||||
field without parsing JSON\.
|
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
|
\fB--help\fR/\fB-h\fR
|
||||||
Pretty-print summary of options to standard output and exit\. The format can
|
Pretty-print summary of options to standard output and exit\. The format can
|
||||||
be changed using -F, -R, -J, -H etc\.
|
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
|
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\.
|
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
|
This is useful for simple scripts which want to find a specific output
|
||||||
field without parsing JSON.
|
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**
|
**--help**/**-h**
|
||||||
Pretty-print summary of options to standard output and exit. The format can
|
Pretty-print summary of options to standard output and exit. The format can
|
||||||
be changed using -F, -R, -J, -H etc.
|
be changed using -F, -R, -J, -H etc.
|
||||||
|
|
Loading…
Add table
Reference in a new issue