Commit 55949277 authored by Jiří Klimeš's avatar Jiří Klimeš

cli: changing nmcli output to better fit both computer and human needs

The output is basically tabular with fields (columns) presenting specific pieces of info.
Each line represents a single object. It's possible to switch to multiline output using
'--multiline' option. In that mode single object is presented on more lines - each field
on its line.
Terse mode now uses ':' as field separator. It also escapes all occurences of ':' and '\'
inside field values to ease parsing. The escaping behaviour can be controlled through
'--escape' option. By default, escaping is switched on in tabular mode. When using terse
mode ('--terse'), '--fields' option is mandatory for specifying required fields. That helps
for flexibility and backwards compatibility.
Not all output is converted yet.
parent 15351042
This diff is collapsed.
This diff is collapsed.
......@@ -45,7 +45,7 @@
#include "devices.h"
#include "network-manager.h"
#define NMCLI_VERSION "0.1"
#define NMCLI_VERSION "0.2"
typedef struct {
......@@ -64,10 +64,13 @@ usage (const char *prog_name)
fprintf (stderr,
_("Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n\n"
"OPTIONS\n"
" -t[erse] terse output\n"
" -p[retty] pretty output\n"
" -v[ersion] show program version\n"
" -h[elp] print this help\n\n"
" -t[erse] terse output\n"
" -p[retty] pretty output\n"
" -m[ultiline] multiline output\n"
" -f[ields] <field1,field2,...>|all|common specify fields to output\n"
" -e[scape] yes|no escape columns separators in values\n"
" -v[ersion] show program version\n"
" -h[elp] print this help\n\n"
"OBJECT\n"
" nm NetworkManager status\n"
" con NetworkManager connections\n"
......@@ -132,9 +135,57 @@ parse_command_line (NmCli *nmc, int argc, char **argv)
if (opt[1] == '-')
opt++;
if (matches (opt, "-terse") == 0) {
nmc->print_output = NMC_PRINT_TERSE;
if (nmc->print_output == NMC_PRINT_TERSE) {
g_string_printf (nmc->return_text, _("Option '--terse' is specified the second time."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
else if (nmc->print_output == NMC_PRINT_PRETTY) {
g_string_printf (nmc->return_text, _("Option '--terse' is mutually exclusive with '--pretty'."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
else
nmc->print_output = NMC_PRINT_TERSE;
} else if (matches (opt, "-pretty") == 0) {
nmc->print_output = NMC_PRINT_PRETTY;
if (nmc->print_output == NMC_PRINT_PRETTY) {
g_string_printf (nmc->return_text, _("Option '--pretty' is specified the second time."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
else if (nmc->print_output == NMC_PRINT_TERSE) {
g_string_printf (nmc->return_text, _("Option '--pretty' is mutually exclusive with '--terse'."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
else
nmc->print_output = NMC_PRINT_PRETTY;
} else if (matches (opt, "-multiline") == 0) {
nmc->multiline_output = TRUE;
} else if (matches (opt, "-escape") == 0) {
next_arg (&argc, &argv);
if (argc <= 1) {
g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
if (!strcmp (argv[1], "yes"))
nmc->escape_values = TRUE;
else if (!strcmp (argv[1], "no"))
nmc->escape_values = FALSE;
else {
g_string_printf (nmc->return_text, _("Error: '%s' is not valid argument for '%s' option."), argv[1], opt);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
} else if (matches (opt, "-fields") == 0) {
next_arg (&argc, &argv);
if (argc <= 1) {
g_string_printf (nmc->return_text, _("Error: fields for '%s' options are missing."), opt);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
nmc->required_fields = g_strdup (argv[1]);
} else if (matches (opt, "-version") == 0) {
printf (_("nmcli tool, version %s\n"), NMCLI_VERSION);
return NMC_RESULT_SUCCESS;
......@@ -150,6 +201,20 @@ parse_command_line (NmCli *nmc, int argc, char **argv)
argv++;
}
/* Some validity options checks */
if (nmc->print_output == NMC_PRINT_TERSE) {
if (!nmc->required_fields) {
g_string_printf (nmc->return_text, _("Option '--terse' requires specifying '--fields'."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
} else if ( !strcasecmp (nmc->required_fields, "all")
|| !strcasecmp (nmc->required_fields, "common")) {
g_string_printf (nmc->return_text, _("Option '--terse' requires specific '--fields' option, not 'all' or 'common'."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
return nmc->return_value;
}
}
if (argc > 1)
return do_cmd (nmc, argv[1], argc-1, argv+1);
......@@ -217,6 +282,11 @@ nmc_init (NmCli *nmc)
nmc->should_wait = FALSE;
nmc->print_output = NMC_PRINT_NORMAL;
nmc->multiline_output = FALSE;
nmc->escape_values = TRUE;
nmc->required_fields = NULL;
nmc->allowed_fields = NULL;
memset (&nmc->print_fields, '\0', sizeof (NmcPrintFields));
}
static void
......@@ -231,6 +301,10 @@ nmc_cleanup (NmCli *nmc)
g_slist_free (nmc->system_connections);
g_slist_free (nmc->user_connections);
g_free (nmc->required_fields);
if (nmc->print_fields.indices)
g_array_free (nmc->print_fields.indices, TRUE);
}
static gboolean
......
......@@ -20,10 +20,12 @@
#ifndef NMC_NMCLI_H
#define NMC_NMCLI_H
#include <glib.h>
#include <nm-client.h>
#include <nm-remote-settings.h>
#include <nm-remote-settings-system.h>
/* nmcli exit codes */
typedef enum {
/* Indicates successful execution */
......@@ -51,27 +53,55 @@ typedef enum {
NMC_PRINT_PRETTY
} NMCPrintOutput;
/* === Output fields === */
typedef struct {
const char *name; /* Field's name */
const char *name_l10n; /* Field's name for translation */
int width; /* Width in screen columns */
const char *value; /* Value of current field */
guint32 flags; /* Flags */
} NmcOutputField;
/* Flags for NmcPrintFields */
#define NMC_PF_FLAG_MULTILINE 0x00000001 /* Multiline output instead of tabular*/
#define NMC_PF_FLAG_TERSE 0x00000002 /* Terse outpud mode */
#define NMC_PF_FLAG_PRETTY 0x00000004 /* Pretty output mode */
#define NMC_PF_FLAG_HEADER 0x00000008 /* Print headers instead of values */
#define NMC_PF_FLAG_ESCAPE 0x00000010 /* Escape column separator and '\' */
typedef struct {
GArray *indices; /* Array of field indices to the array of allowed fields */
char *header_name; /* Name of the output */
int indent; /* Indent by this number of spaces */
guint32 flags; /* Various flags for controlling output: see NMC_PF_FLAG_* values */
} NmcPrintFields;
/* NmCli - main structure */
typedef struct _NmCli {
NMClient *client;
NMClient *(*get_client) (struct _NmCli *nmc);
NMClient *client; /* Pointer to NMClient of libnm-glib */
NMClient *(*get_client) (struct _NmCli *nmc); /* Pointer to function for creating NMClient */
NMCResultCode return_value;
GString *return_text;
NMCResultCode return_value; /* Return code of nmcli */
GString *return_text; /* Reason text */
int timeout;
int timeout; /* Operation timeout */
NMRemoteSettingsSystem *system_settings;
NMRemoteSettings *user_settings;
NMRemoteSettingsSystem *system_settings; /* System settings */
NMRemoteSettings *user_settings; /* User settings */
gboolean system_settings_running;
gboolean user_settings_running;
gboolean system_settings_running; /* Is system settings service running? */
gboolean user_settings_running; /* Is user settings service running? */
GSList *system_connections;
GSList *user_connections;
GSList *system_connections; /* List of system connections */
GSList *user_connections; /* List of user connections */
gboolean should_wait;
NMCPrintOutput print_output;
gboolean should_wait; /* Indication that nmcli should not end yet */
NMCPrintOutput print_output; /* Output mode */
gboolean multiline_output; /* Multiline output instead of default tabular */
gboolean escape_values; /* Whether to escape ':' and '\' in terse tabular mode */
char *required_fields; /* Required fields in output: '--fields' option */
NmcOutputField *allowed_fields; /* Array of allowed fields for particular commands */
NmcPrintFields print_fields; /* Structure with field indices to print */
} NmCli;
#endif /* NMC_NMCLI_H */
......@@ -21,6 +21,7 @@
#include <string.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "utils.h"
......@@ -46,6 +47,171 @@ next_arg (int *argc, char ***argv)
return 0;
}
/*
* Parse comma separated fields in 'fields_str' according to 'fields_array'.
* IN: 'field_str': comma-separated fields names
* 'fields_array': array of allowed fields
* RETURN: GArray with indices representing fields in 'fields_array'.
*/
GArray *
parse_output_fields (const char *fields_str, const NmcOutputField fields_array[], GError **error)
{
char **fields, **iter;
GArray *array;
int i;
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
array = g_array_new (FALSE, FALSE, sizeof (int));
/* Split supplied fields string */
fields = g_strsplit_set (fields_str, ",", -1);
for (iter = fields; iter && *iter; iter++) {
for (i = 0; fields_array[i].name; i++) {
if (strcasecmp (*iter, fields_array[i].name) == 0) {
g_array_append_val (array, i);
break;
}
}
if (fields_array[i].name == NULL) {
if (!strcasecmp (*iter, "all") || !strcasecmp (*iter, "common"))
g_set_error (error, 0, 0, _("Error: 'con status': field '%s' has to be alone."), *iter);
else
g_set_error (error, 0, 0, _("Error: 'con status': invalid field '%s'."), *iter);
g_array_free (array, TRUE);
array = NULL;
goto done;
}
}
done:
return array;
}
void
print_fields (const NmcPrintFields fields, const NmcOutputField field_values[])
{
GString *str;
int width1, width2;
int table_width = 0;
char *line = NULL;
char *indent_str;
const char *value;
int i, idx;
gboolean multiline = fields.flags & NMC_PF_FLAG_MULTILINE;
gboolean terse = fields.flags & NMC_PF_FLAG_TERSE;
gboolean pretty = fields.flags & NMC_PF_FLAG_PRETTY;
gboolean header = fields.flags & NMC_PF_FLAG_HEADER;
gboolean escape = fields.flags & NMC_PF_FLAG_ESCAPE;
/* Headers are not printed in terse mode */
if (header && terse)
return;
if (multiline) {
/* --- Multiline mode --- */
if (header && pretty) {
/* Print the table header */
table_width = g_utf8_strlen (fields.header_name, -1) + 4;
line = g_strnfill (79, '=');
width1 = strlen (fields.header_name);
width2 = g_utf8_strlen (fields.header_name, -1);
printf ("%s\n", line);
printf ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name);
printf ("%s\n", line);
g_free (line);
}
/* Print values */
if (!header) {
for (i = 0; i < fields.indices->len; i++) {
char *tmp;
idx = g_array_index (fields.indices, int, i);
tmp = g_strdup_printf ("%s:", _(field_values[idx].name_l10n));
printf ("%-*s%s\n", terse ? 0 : 20, tmp, field_values[idx].value);
g_free (tmp);
}
if (pretty) {
line = g_strnfill (79, '-');
printf ("%s\n", line);
g_free (line);
}
}
return;
}
/* --- Tabular mode: each line = one object --- */
str = g_string_new (NULL);
for (i = 0; i < fields.indices->len; i++) {
idx = g_array_index (fields.indices, int, i);
if (header)
value = _(field_values[idx].name_l10n);
else
value = field_values[idx].value;
if (terse) {
if (escape) {
const char *p = value;
while (*p) {
if (*p == ':' || *p == '\\')
g_string_append_c (str, '\\'); /* Escaping by '\' */
g_string_append_c (str, *p);
p++;
}
}
else
g_string_append_printf (str, "%s", value);
g_string_append_c (str, ':'); /* Column separator */
} else {
width1 = strlen (value);
width2 = g_utf8_strlen (value, -1); /* Width of the string (in screen colums) */
if (strlen (value) == 0)
value = "--";
g_string_append_printf (str, "%-*s", field_values[idx].width + width1 - width2, value);
g_string_append_c (str, ' '); /* Column separator */
table_width += field_values[idx].width + width1 - width2 + 1;
}
}
if (table_width <= 0)
table_width = g_utf8_strlen (fields.header_name, -1) + 4;
if (header && pretty) {
/* Print the table header */
line = g_strnfill (table_width, '=');
width1 = strlen (fields.header_name);
width2 = g_utf8_strlen (fields.header_name, -1);
printf ("%s\n", line);
printf ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name);
printf ("%s\n", line);
g_free (line);
}
/* Print the line */
if (str->len > 0) {
g_string_truncate (str, str->len-1); /* Chop off last column separator */
if (fields.indent > 0) {
indent_str = g_strnfill (fields.indent, ' ');
g_string_prepend (str, indent_str);
g_free (indent_str);
}
printf ("%s\n", str->str);
}
if (header && pretty) {
if (str->len > 0) {
line = g_strnfill (table_width, '-');
printf ("%s\n", line);
g_free (line);
}
}
g_string_free (str, TRUE);
}
/*--- obsolete printing functions ---*/
void
print_table_header (const char *name, ...)
{
......
......@@ -20,8 +20,15 @@
#ifndef NMC_UTILS_H
#define NMC_UTILS_H
#include <glib.h>
#include "nmcli.h"
/* === Functions === */
int matches (const char *cmd, const char *pattern);
int next_arg (int *argc, char ***argv);
GArray *parse_output_fields (const char *fields_str, const NmcOutputField fields_array[], GError **error);
void print_fields (const NmcPrintFields fields, const NmcOutputField field_values[]);
void print_table_header (const char *name, ...);
void print_table_line (int indent, ...);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment