Skip to content

Commit

Permalink
Tweak help commands
Browse files Browse the repository at this point in the history
The output of the help and .help command have been handled differently.
The usage descriptions contain line breaks and tabs.

However, the output of the .help command must be machine parseable,
so these characters are removed during runtime.

For the api modes, these characters should also be removed.
Therefore the parameter "wrap" have been added to the
OUTPUT_FORMATTER::object_key_value function,
with following meanings:
  * wrap < 0: no modification
  * wrap = 0: single line
  * wrap > 0: wrap line after x characters (if api==0)
  • Loading branch information
joergsteffens authored and Marco van Wieringen committed May 28, 2015
1 parent db143d6 commit 9998d54
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 76 deletions.
101 changes: 45 additions & 56 deletions src/dird/ua_cmds.c
Expand Up @@ -139,23 +139,33 @@ static struct cmdstruct commands[] = {
{ NT_("label"), label_cmd, _("Label a tape"),
NT_("storage=<storage-name> volume=<volume-name> pool=<pool-name> slot=<slot> [ barcodes ] [ encrypt ]"), false, true },
{ NT_("list"), list_cmd, _("List objects from catalog"),
NT_("jobs | jobid=<jobid> | ujobid=<complete_name> | job=<job-name> | jobmedia jobid=<jobid> |\n"
"\tjobmedia ujobid=<complete_name> | joblog jobid=<jobid> | joblog ujobid=<complete_name> |\n"
"\tbasefiles jobid=<jobid> | basefiles ujobid=<complete_name> |\n"
"\tfiles jobid=<jobid> | files ujobid=<complete_name> |\n"
"\tfileset [ jobid=<jobid> ] | fileset [ ujobid=<complete_name> ] | filesets | pools | jobtotals |\n"
"\tvolumes [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] |\n"
"\tmedia [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] | clients |\n"
"\tnextvol job=<job-name> | nextvolume ujobid=<complete_name> | copies jobid=<jobid> [ limit=<num> ]"), true, true },
NT_("basefiles jobid=<jobid> | basefiles ujobid=<complete_name> |\n"
"clients | copies jobid=<jobid> |\n"
"files jobid=<jobid> | files ujobid=<complete_name> |\n"
"fileset [ jobid=<jobid> ] | fileset [ ujobid=<complete_name> ] | filesets |\n"
"jobs | jobid=<jobid> | ujobid=<complete_name> | job=<job-name> |\n"
"joblog jobid=<jobid> | joblog ujobid=<complete_name> |\n"
"jobmedia jobid=<jobid> | jobmedia ujobid=<complete_name> |\n"
"jobtotals |\n"
"media [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] |\n"
"nextvol job=<job-name> | nextvolume ujobid=<complete_name> |\n"
"pools |\n"
"volumes [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] |\n"
"[ limit=<num> ]"), true, true },
{ NT_("llist"), llist_cmd, _("Full or long list like list command"),
NT_("jobs | jobid=<jobid> | ujobid=<complete_name> | job=<job-name> | jobmedia jobid=<jobid> |\n"
"\tjobmedia ujobid=<complete_name> | joblog jobid=<jobid> | joblog ujobid=<complete_name> |\n"
"\tbasefiles jobid=<jobid> | basefiles ujobid=<complete_name> |\n"
"\tfiles jobid=<jobid> | files ujobid=<complete_name> |\n"
"\tfileset [ jobid=<jobid> ] | fileset [ ujobid=<complete_name> ] | filesets | pools | jobtotals |\n"
"\tvolumes [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] |\n"
"\tmedia [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] | clients |\n"
"\tnextvol job=<job-name> | nextvolume ujobid=<complete_name> | copies jobid=<jobid>"), true, true },
NT_("basefiles jobid=<jobid> | basefiles ujobid=<complete_name> |\n"
"clients | copies jobid=<jobid> |\n"
"files jobid=<jobid> | files ujobid=<complete_name> |\n"
"fileset [ jobid=<jobid> ] | fileset [ ujobid=<complete_name> ] | filesets |\n"
"jobs | jobid=<jobid> | ujobid=<complete_name> | job=<job-name> |\n"
"joblog jobid=<jobid> | joblog ujobid=<complete_name> |\n"
"jobmedia jobid=<jobid> | jobmedia ujobid=<complete_name> |\n"
"jobtotals |\n"
"media [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] |\n"
"nextvol job=<job-name> | nextvolume ujobid=<complete_name> |\n"
"pools |\n"
"volumes [ jobid=<jobid> ujobid=<complete_name> pool=<pool-name> ] |\n"
"[ limit=<num> ]"), true, true },
{ NT_("messages"), messages_cmd, _("Display pending messages"),
NT_(""), false, false },
{ NT_("memory"), memory_cmd, _("Print current memory usage"),
Expand Down Expand Up @@ -208,13 +218,13 @@ static struct cmdstruct commands[] = {
{ NT_("setbandwidth"), setbwlimit_cmd, _("Sets bandwidth"),
NT_("client=<client-name> | storage=<storage-name> | jobid=<jobid> |\n"
"\tjob=<job-name> | ujobid=<unique-jobid> state=<job_state> | all\n"
"\tlimit=<nn-kbs> yes"), true, true },
"\tlimit=<nn-kbs> [ yes ]"), true, true },
{ NT_("setdebug"), setdebug_cmd, _("Sets debug level"),
NT_("level=<nn> trace=0/1 timestamp=0/1 client=<client-name> | dir | storage=<storage-name> | all"), true, true },
{ NT_("setip"), setip_cmd, _("Sets new client address -- if authorized"),
NT_(""), false, true },
{ NT_("show"), show_cmd, _("Show resource records"),
NT_("jobdefs=<job-defaults>| job=<job-name> | pool=<pool-name> | fileset=<fileset-name> |\n"
NT_("jobdefs=<job-defaults> | job=<job-name> | pool=<pool-name> | fileset=<fileset-name> |\n"
"\tschedule=<schedule-name> | client=<client-name> | message=<message-resource-name> |\n"
"\tprofile=<profile-name> | jobdefs | jobs | pools | filesets | schedules | clients |\n"
"\tmessages | profiles | consoles | disabled | all"), true, true },
Expand Down Expand Up @@ -2400,15 +2410,15 @@ static int help_cmd(UAContext *ua, const char *cmd)
ua->send->object_start(commands[i].key);
ua->send->object_key_value("command", commands[i].key, " %-13s");
ua->send->object_key_value("description", commands[i].help, " %s\n\n");
ua->send->object_key_value("arguments", commands[i].usage, "Arguments:\n\t%s\n");
ua->send->object_key_value("arguments", "Arguments:\n\t", commands[i].usage, "%s\n", 40);
ua->send->object_end(commands[i].key);
break;
}
} else {
ua->send->object_start(commands[i].key);
ua->send->object_key_value("command", commands[i].key, " %-13s");
ua->send->object_key_value("description", commands[i].help, " %s\n");
ua->send->object_key_value("arguments", commands[i].usage);
ua->send->object_key_value("arguments", commands[i].usage, 0);
ua->send->object_end(commands[i].key);
}
}
Expand All @@ -2419,38 +2429,6 @@ static int help_cmd(UAContext *ua, const char *cmd)
return 1;
}

/*
* Output the usage string as a machine parseable string.
* e.g. remove newlines and replace tabs with a single space.
*/
static inline void usage_to_machine(UAContext *ua, struct cmdstruct *cmd)
{
char *p;
POOL_MEM usage(PM_MESSAGE);

pm_strcpy(usage, cmd->usage);
p = usage.c_str();
while (*p) {
switch (*p) {
case '\n':
/*
* Copy the rest of the string over the unwanted character.
* Use bstrinlinecpy as we are doing an inline copy which
* isn't officially supported by (b)strcpy.
*/
bstrinlinecpy(p, p + 1);
continue;
case '\t':
*p = ' ';
break;
default:
break;
}
p++;
}
ua->send_msg("%s\n", usage.c_str());
}

int qhelp_cmd(UAContext *ua, const char *cmd)
{
int i, j;
Expand All @@ -2461,7 +2439,11 @@ int qhelp_cmd(UAContext *ua, const char *cmd)
j = find_arg(ua, NT_("all"));
if (j >= 0) {
for (i = 0; i < comsize; i++) {
ua->send_msg("%s\n", commands[i].key);
ua->send->object_start(commands[i].key);
ua->send->object_key_value("command", commands[i].key, "%s\n");
ua->send->object_key_value("description", commands[i].help);
ua->send->object_key_value("arguments", commands[i].usage, NULL, 0);
ua->send->object_end(commands[i].key);
}
return 1;
}
Expand All @@ -2473,7 +2455,11 @@ int qhelp_cmd(UAContext *ua, const char *cmd)
if (j >= 0 && ua->argk[j]) {
for (i = 0; i < comsize; i++) {
if (bstrcmp(commands[i].key, ua->argv[j])) {
usage_to_machine(ua, &commands[i]);
ua->send->object_start(commands[i].key);
ua->send->object_key_value("command", commands[i].key);
ua->send->object_key_value("description", commands[i].help);
ua->send->object_key_value("arguments", commands[i].usage, "%s\n", 0);
ua->send->object_end(commands[i].key);
break;
}
}
Expand All @@ -2484,8 +2470,11 @@ int qhelp_cmd(UAContext *ua, const char *cmd)
* Want to display everything
*/
for (i = 0; i < comsize; i++) {
ua->send_msg("%s %s -- ", commands[i].key, commands[i].help);
usage_to_machine(ua, &commands[i]);
ua->send->object_start(commands[i].key);
ua->send->object_key_value("command", commands[i].key, "%s ");
ua->send->object_key_value("description", commands[i].help, "%s -- ");
ua->send->object_key_value("arguments", commands[i].usage, "%s\n", 0);
ua->send->object_end(commands[i].key);
}
return 1;
}
Expand Down
3 changes: 2 additions & 1 deletion src/dird/ua_dotcmds.c
Expand Up @@ -105,7 +105,8 @@ static struct cmdstruct commands[] = {
{ NT_(".dump"), admin_cmds, NULL, NULL, false, true },
{ NT_(".exit"), admin_cmds, NULL, NULL, false, false },
{ NT_(".filesets"), filesetscmd, NULL, NULL, false, false },
{ NT_(".help"), dot_help_cmd, NULL, NULL, false, false },
{ NT_(".help"), dot_help_cmd, _("Print parsable information about a command"),
NT_("[ all | item=cmd ]"), false, false },
{ NT_(".jobdefs"), jobdefscmd, NULL, NULL, true, false },
{ NT_(".jobs"), jobscmd, NULL,
NT_("type=<jobtype>"), true, false },
Expand Down
127 changes: 111 additions & 16 deletions src/lib/output_formatter.c
Expand Up @@ -31,7 +31,13 @@
#define NEED_JANSSON_NAMESPACE 1
#include "bareos.h"

OUTPUT_FORMATTER::OUTPUT_FORMATTER(SEND_HANDLER *send_func_arg, void *send_ctx_arg, int api_mode)
/*
* Extra bytes to allocate for the wrap buffer.
*/
#define WRAP_EXTRA_BYTES 50

OUTPUT_FORMATTER::OUTPUT_FORMATTER(SEND_HANDLER *send_func_arg, void *send_ctx_arg,
int api_mode)
{
initialize_json();

Expand Down Expand Up @@ -83,11 +89,13 @@ void OUTPUT_FORMATTER::object_start(const char *name)
json_object_existing = json_object_get(json_object_current, name);
if (json_object_existing) {
if (json_is_array(json_object_existing)) {
Dmsg2(800, "json object %s (stack size: %d) already exits and is an array: appending\n",
Dmsg2(800,
"json object %s (stack size: %d) already exits and is an array: appending\n",
name, result_stack_json->size());
json_array_append_new(json_object_existing, json_object_new);
} else {
Dmsg2(800, "json object %s (stack size: %d) already exits: converting to array and appending\n",
Dmsg2(800,
"json object %s (stack size: %d) already exits: converting to array and appending\n",
name, result_stack_json->size());
json_object_array = json_array();
json_array_append_new(json_object_array, json_object_existing);
Expand All @@ -96,7 +104,7 @@ void OUTPUT_FORMATTER::object_start(const char *name)
}
} else {
Dmsg2(800, "create new json object %s (stack size: %d)\n",
name, result_stack_json->size());
name, result_stack_json->size());
json_object_set(json_object_current, name, json_object_new);
}
result_stack_json->push(json_object_new);
Expand Down Expand Up @@ -148,12 +156,14 @@ void OUTPUT_FORMATTER::object_key_value(const char *key, uint64_t value)
object_key_value(key, NULL, value, NULL);
}

void OUTPUT_FORMATTER::object_key_value(const char *key, uint64_t value, const char *value_fmt)
void OUTPUT_FORMATTER::object_key_value(const char *key, uint64_t value,
const char *value_fmt)
{
object_key_value(key, NULL, value, value_fmt);
}

void OUTPUT_FORMATTER::object_key_value(const char *key, const char *key_fmt, uint64_t value, const char *value_fmt)
void OUTPUT_FORMATTER::object_key_value(const char *key, const char *key_fmt,
uint64_t value, const char *value_fmt)
{
POOL_MEM string;

Expand All @@ -175,24 +185,29 @@ void OUTPUT_FORMATTER::object_key_value(const char *key, const char *key_fmt, ui
}
}

void OUTPUT_FORMATTER::object_key_value(const char *key, const char *value)
void OUTPUT_FORMATTER::object_key_value(const char *key, const char *value, int wrap)
{
object_key_value(key, NULL, value, NULL);
object_key_value(key, NULL, value, NULL, wrap);
}

void OUTPUT_FORMATTER::object_key_value(const char *key, const char *value, const char *value_fmt)
void OUTPUT_FORMATTER::object_key_value(const char *key, const char *value,
const char *value_fmt, int wrap)
{
object_key_value(key, NULL, value, value_fmt);
object_key_value(key, NULL, value, value_fmt, wrap);
}

void OUTPUT_FORMATTER::object_key_value(const char *key, const char *key_fmt, const char *value, const char *value_fmt)
void OUTPUT_FORMATTER::object_key_value(const char *key, const char *key_fmt,
const char *value, const char *value_fmt,
int wrap)
{
POOL_MEM string;
POOL_MEM wvalue(value);
rewrap(wvalue, wrap);

switch (api) {
#if HAVE_JANSSON
case API_MODE_JSON:
json_key_value_add(key, value);
json_key_value_add(key, wvalue.c_str());
break;
#endif
default:
Expand All @@ -201,11 +216,91 @@ void OUTPUT_FORMATTER::object_key_value(const char *key, const char *key_fmt, co
result_message_plain->strcat(string);
}
if (value_fmt) {
string.bsprintf(value_fmt, value);
string.bsprintf(value_fmt, wvalue.c_str());
result_message_plain->strcat(string);
}
Dmsg2(800, "obj: %s:%s\n", key, value);
Dmsg2(800, "obj: %s:%s\n", key, wvalue.c_str());
}
}

void OUTPUT_FORMATTER::rewrap(POOL_MEM &string, int wrap)
{
char *p, *q;
int open = 0;
int charsinline = 0;
POOL_MEM rewrap_string(PM_MESSAGE);

/*
* wrap < 0: no modification
* wrap = 0: single line
* wrap > 0: wrap line after x characters (if api==0)
*/
if (wrap < 0) {
return;
}

/*
* Allocate a wrap buffer that is big enough to hold the original
* string + WRAP_EXTRA_BYTES. This means we can accommodate enough
* line breaks and spaces to wrap the original string.
*/
rewrap_string.check_size(string.max_size() + WRAP_EXTRA_BYTES);

/*
* Walk the input buffer and copy the data to the wrap buffer
* inserting line breaks as needed.
*/
q = rewrap_string.c_str();
p = string.c_str();
while (*p) {
charsinline++;
switch (*p) {
case ' ':
if (api == 0 && wrap > 0 && charsinline >= wrap && open <= 0 && *(p + 1) != '|') {
*q++ = '\n';
*q++ = '\t';
charsinline = 0;
} else {
if (charsinline > 1) {
*q++ = ' ';
}
}
break;
case '|':
rewrap_string.strcat("|");
if (api == 0 && wrap > 0 && open <= 0) {
*q++ = '\n';
*q++ = '\t';
charsinline = 0;
}
break;
case '[':
case '<':
open++;
*q++ = *p;
break;
case ']':
case '>':
open--;
*q++ = *p;
break;
case '\n':
case '\t':
if (charsinline > 1) {
if (*(p + 1) != '\n' && *(p + 1) != '\t' && *(p + 1) != ' ') {
*q++ = ' ';
}
}
break;
default:
*q++ = *p;
break;
}
p++;
}
*q = '\0';

string.strcpy(rewrap_string);
}

void OUTPUT_FORMATTER::process_text_buffer()
Expand All @@ -218,7 +313,7 @@ void OUTPUT_FORMATTER::process_text_buffer()

void OUTPUT_FORMATTER::finalize_result(bool result)
{
switch (api) {
switch (api) {
#if HAVE_JANSSON
case API_MODE_JSON:
json_finalize_result(result);
Expand Down Expand Up @@ -276,7 +371,7 @@ void OUTPUT_FORMATTER::json_finalize_result(bool result)
if (result) {
json_object_set(msg_obj, "result", result_array_json);
} else {
error_obj=json_object();
error_obj = json_object();
json_object_set_new(error_obj, "code", json_integer(1));
json_object_set_new(error_obj, "message", json_string("failed"));
json_object_set(error_obj, "data", result_array_json);
Expand Down

0 comments on commit 9998d54

Please sign in to comment.