Merge pull request #11037 from poettering/json-table

optionally output tables as JSON
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2018-12-04 14:32:10 +01:00
committed by GitHub
14 changed files with 298 additions and 45 deletions
+2 -2
View File
@@ -1399,7 +1399,7 @@ static int assess(const struct security_info *info, Table *overview_table, Analy
int r;
if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
details_table = table_new("", "NAME", "DESCRIPTION", "WEIGHT", "BADNESS", "RANGE", "EXPOSURE");
details_table = table_new(" ", "name", "description", "weight", "badness", "range", "exposure");
if (!details_table)
return log_oom();
@@ -1973,7 +1973,7 @@ int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) {
assert(bus);
if (strv_length(units) != 1) {
overview_table = table_new("UNIT", "EXPOSURE", "PREDICATE", "HAPPY");
overview_table = table_new("unit", "exposure", "predicate", "happy");
if (!overview_table)
return log_oom();
}
+1 -1
View File
@@ -1939,7 +1939,7 @@ static void json_dump_with_flags(JsonVariant *v, FILE *f) {
json_variant_dump(v,
(arg_json == JSON_PRETTY ? JSON_FORMAT_PRETTY : JSON_FORMAT_NEWLINE) |
colors_enabled() * JSON_FORMAT_COLOR,
JSON_FORMAT_COLOR_AUTO,
f, NULL);
}
+1 -1
View File
@@ -83,7 +83,7 @@ static int print_inhibitors(sd_bus *bus) {
if (r < 0)
return log_error_errno(r, "Could not get active inhibitors: %s", bus_error_message(&error, r));
table = table_new("WHO", "UID", "USER", "PID", "COMM", "WHAT", "WHY", "MODE");
table = table_new("who", "uid", "user", "pid", "comm", "what", "why", "mode");
if (!table)
return log_oom();
+11 -4
View File
@@ -101,7 +101,10 @@ static int show_table(Table *table, const char *word) {
table_set_header(table, arg_legend);
r = table_print(table, NULL);
if (OUTPUT_MODE_IS_JSON(arg_output))
r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO);
else
r = table_print(table, NULL);
if (r < 0)
return log_error_errno(r, "Failed to show table: %m");
}
@@ -143,7 +146,7 @@ static int list_sessions(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
table = table_new("SESSION", "UID", "USER", "SEAT", "TTY");
table = table_new("session", "uid", "user", "seat", "tty");
if (!table)
return log_oom();
@@ -224,7 +227,7 @@ static int list_users(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
table = table_new("UID", "USER");
table = table_new("uid", "user");
if (!table)
return log_oom();
@@ -281,7 +284,7 @@ static int list_seats(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
table = table_new("SEAT");
table = table_new("seat");
if (!table)
return log_oom();
@@ -1434,6 +1437,10 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_output < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown output '%s'.", optarg);
if (OUTPUT_MODE_IS_JSON(arg_output))
arg_legend = false;
break;
case ARG_NO_PAGER:
+9 -3
View File
@@ -259,7 +259,10 @@ static int show_table(Table *table, const char *word) {
table_set_header(table, arg_legend);
r = table_print(table, NULL);
if (OUTPUT_MODE_IS_JSON(arg_output))
r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO);
else
r = table_print(table, NULL);
if (r < 0)
return log_error_errno(r, "Failed to show table: %m");
}
@@ -297,7 +300,7 @@ static int list_machines(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Could not get machines: %s", bus_error_message(&error, r));
table = table_new("MACHINE", "CLASS", "SERVICE", "OS", "VERSION", "ADDRESSES");
table = table_new("machine", "class", "service", "os", "version", "addresses");
if (!table)
return log_oom();
@@ -377,7 +380,7 @@ static int list_images(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r));
table = table_new("NAME", "TYPE", "RO", "USAGE", "CREATED", "MODIFIED");
table = table_new("name", "type", "ro", "usage", "created", "modified");
if (!table)
return log_oom();
@@ -2921,6 +2924,9 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_output < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown output '%s'.", optarg);
if (OUTPUT_MODE_IS_JSON(arg_output))
arg_legend = false;
break;
case ARG_NO_PAGER:
+1 -1
View File
@@ -500,7 +500,7 @@ static int list_images(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
table = table_new("NAME", "TYPE", "RO", "CRTIME", "MTIME", "USAGE", "STATE");
table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state");
if (!table)
return log_oom();
+222 -13
View File
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <ctype.h>
#include <stdio_ext.h>
#include "alloc-util.h"
@@ -58,6 +59,8 @@ typedef struct TableData {
unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
bool uppercase; /* Uppercase string on display */
const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
char *url; /* A URL to use for a clickable hyperlink */
char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
@@ -132,6 +135,7 @@ Table *table_new_raw(size_t n_columns) {
Table *table_new_internal(const char *first_header, ...) {
_cleanup_(table_unrefp) Table *t = NULL;
size_t n_columns = 1;
const char *h;
va_list ap;
int r;
@@ -139,8 +143,6 @@ Table *table_new_internal(const char *first_header, ...) {
va_start(ap, first_header);
for (;;) {
const char *h;
h = va_arg(ap, const char*);
if (!h)
break;
@@ -153,19 +155,18 @@ Table *table_new_internal(const char *first_header, ...) {
if (!t)
return NULL;
r = table_add_cell(t, NULL, TABLE_STRING, first_header);
if (r < 0)
return NULL;
va_start(ap, first_header);
for (;;) {
const char *h;
for (h = first_header; h; h = va_arg(ap, const char*)) {
TableCell *cell;
h = va_arg(ap, const char*);
if (!h)
break;
r = table_add_cell(t, &cell, TABLE_STRING, h);
if (r < 0) {
va_end(ap);
return NULL;
}
r = table_add_cell(t, NULL, TABLE_STRING, h);
/* Make the table header uppercase */
r = table_set_uppercase(t, cell, true);
if (r < 0) {
va_end(ap);
return NULL;
@@ -269,6 +270,14 @@ static bool table_data_matches(
if (d->ellipsize_percent != ellipsize_percent)
return false;
/* If a color/url/uppercase flag is set, refuse to merge */
if (d->color)
return false;
if (d->url)
return false;
if (d->uppercase)
return false;
k = table_data_size(type, data);
l = table_data_size(d->type, d->data);
@@ -427,6 +436,7 @@ static int table_dedup_cell(Table *t, TableCell *cell) {
nd->color = od->color;
nd->url = TAKE_PTR(curl);
nd->uppercase = od->uppercase;
table_data_unref(od);
t->data[i] = nd;
@@ -574,6 +584,27 @@ int table_set_url(Table *t, TableCell *cell, const char *url) {
return free_and_replace(table_get_data(t, cell)->url, copy);
}
int table_set_uppercase(Table *t, TableCell *cell, bool b) {
TableData *d;
int r;
assert(t);
assert(cell);
r = table_dedup_cell(t, cell);
if (r < 0)
return r;
assert_se(d = table_get_data(t, cell));
if (d->uppercase == b)
return 0;
d->formatted = mfree(d->formatted);
d->uppercase = b;
return 1;
}
int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
_cleanup_free_ char *curl = NULL;
TableData *nd, *od;
@@ -607,6 +638,7 @@ int table_update(Table *t, TableCell *cell, TableDataType type, const void *data
nd->color = od->color;
nd->url = TAKE_PTR(curl);
nd->uppercase = od->uppercase;
table_data_unref(od);
t->data[i] = nd;
@@ -858,6 +890,20 @@ static const char *table_data_format(TableData *d) {
return "";
case TABLE_STRING:
if (d->uppercase) {
char *p, *q;
d->formatted = new(char, strlen(d->string) + 1);
if (!d->formatted)
return NULL;
for (p = d->string, q = d->formatted; *p; p++, q++)
*q = (char) toupper((unsigned char) *p);
*q = 0;
return d->formatted;
}
return d->string;
case TABLE_BOOLEAN:
@@ -884,7 +930,7 @@ static const char *table_data_format(TableData *d) {
if (!p)
return NULL;
if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan, 0))
return "n/a";
d->formatted = TAKE_PTR(p);
@@ -1414,3 +1460,166 @@ const void* table_get_at(Table *t, size_t row, size_t column) {
return table_get(t, cell);
}
static int table_data_to_json(TableData *d, JsonVariant **ret) {
switch (d->type) {
case TABLE_EMPTY:
return json_variant_new_null(ret);
case TABLE_STRING:
return json_variant_new_string(ret, d->string);
case TABLE_BOOLEAN:
return json_variant_new_boolean(ret, d->boolean);
case TABLE_TIMESTAMP:
if (d->timestamp == USEC_INFINITY)
return json_variant_new_null(ret);
return json_variant_new_unsigned(ret, d->timestamp);
case TABLE_TIMESPAN:
if (d->timespan == USEC_INFINITY)
return json_variant_new_null(ret);
return json_variant_new_unsigned(ret, d->timespan);
case TABLE_SIZE:
if (d->size == (size_t) -1)
return json_variant_new_null(ret);
return json_variant_new_unsigned(ret, d->size);
case TABLE_UINT32:
return json_variant_new_unsigned(ret, d->uint32);
case TABLE_UINT64:
return json_variant_new_unsigned(ret, d->uint64);
case TABLE_PERCENT:
return json_variant_new_integer(ret, d->percent);
default:
return -EINVAL;
}
}
int table_to_json(Table *t, JsonVariant **ret) {
JsonVariant **rows = NULL, **elements = NULL;
_cleanup_free_ size_t *sorted = NULL;
size_t n_rows, i, j, display_columns;
int r;
assert(t);
/* Ensure we have no incomplete rows */
assert(t->n_cells % t->n_columns == 0);
n_rows = t->n_cells / t->n_columns;
assert(n_rows > 0); /* at least the header row must be complete */
if (t->sort_map) {
/* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
sorted = new(size_t, n_rows);
if (!sorted) {
r = -ENOMEM;
goto finish;
}
for (i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
}
if (t->display_map)
display_columns = t->n_display_map;
else
display_columns = t->n_columns;
assert(display_columns > 0);
elements = new0(JsonVariant*, display_columns * 2);
if (!elements) {
r = -ENOMEM;
goto finish;
}
for (j = 0; j < display_columns; j++) {
TableData *d;
assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
r = table_data_to_json(d, elements + j*2);
if (r < 0)
goto finish;
}
rows = new0(JsonVariant*, n_rows-1);
if (!rows) {
r = -ENOMEM;
goto finish;
}
for (i = 1; i < n_rows; i++) {
TableData **row;
if (sorted)
row = t->data + sorted[i];
else
row = t->data + i * t->n_columns;
for (j = 0; j < display_columns; j++) {
TableData *d;
size_t k;
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
k = j*2+1;
elements[k] = json_variant_unref(elements[k]);
r = table_data_to_json(d, elements + k);
if (r < 0)
goto finish;
}
r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
if (r < 0)
goto finish;
}
r = json_variant_new_array(ret, rows, n_rows - 1);
finish:
if (rows) {
json_variant_unref_many(rows, n_rows-1);
free(rows);
}
if (elements) {
json_variant_unref_many(elements, display_columns*2);
free(elements);
}
return r;
}
int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
int r;
assert(t);
if (!f)
f = stdout;
r = table_to_json(t, &v);
if (r < 0)
return r;
json_variant_dump(v, flags, f, NULL);
return fflush_and_check(f);
}
+5
View File
@@ -5,6 +5,7 @@
#include <stdio.h>
#include <sys/types.h>
#include "json.h"
#include "macro.h"
typedef enum TableDataType {
@@ -45,6 +46,7 @@ int table_set_align_percent(Table *t, TableCell *cell, unsigned percent);
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent);
int table_set_color(Table *t, TableCell *cell, const char *color);
int table_set_url(Table *t, TableCell *cell, const char *color);
int table_set_uppercase(Table *t, TableCell *cell, bool b);
int table_update(Table *t, TableCell *cell, TableDataType type, const void *data);
@@ -71,3 +73,6 @@ TableCell *table_get_cell(Table *t, size_t row, size_t column);
const void *table_get(Table *t, TableCell *cell);
const void *table_get_at(Table *t, size_t row, size_t column);
int table_to_json(Table *t, JsonVariant **ret);
int table_print_json(Table *t, FILE *f, unsigned json_flags);
+7 -4
View File
@@ -1236,7 +1236,7 @@ int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *r
return 0;
}
static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace) {
static int print_source(FILE *f, JsonVariant *v, JsonFormatFlags flags, bool whitespace) {
size_t w, k;
if (!FLAGS_SET(flags, JSON_FORMAT_SOURCE|JSON_FORMAT_PRETTY))
@@ -1289,7 +1289,7 @@ static int print_source(FILE *f, JsonVariant *v, unsigned flags, bool whitespace
return 0;
}
static int json_format(FILE *f, JsonVariant *v, unsigned flags, const char *prefix) {
static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const char *prefix) {
int r;
assert(f);
@@ -1546,7 +1546,7 @@ static int json_format(FILE *f, JsonVariant *v, unsigned flags, const char *pref
return 0;
}
int json_variant_format(JsonVariant *v, unsigned flags, char **ret) {
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
_cleanup_free_ char *s = NULL;
size_t sz = 0;
int r;
@@ -1576,7 +1576,7 @@ int json_variant_format(JsonVariant *v, unsigned flags, char **ret) {
return (int) sz;
}
void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix) {
void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const char *prefix) {
if (!v)
return;
@@ -1585,6 +1585,9 @@ void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *pref
print_source(f, v, flags, false);
if (((flags & (JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_COLOR)) == JSON_FORMAT_COLOR_AUTO) && colors_enabled())
flags |= JSON_FORMAT_COLOR;
if (flags & JSON_FORMAT_SSE)
fputs("data: ", f);
if (flags & JSON_FORMAT_SEQ)
+11 -10
View File
@@ -150,17 +150,18 @@ struct json_variant_foreach_state {
int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
enum {
JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
JSON_FORMAT_COLOR = 1 << 2, /* insert ANSI color sequences */
JSON_FORMAT_SOURCE = 1 << 3, /* prefix with source filename/line/column */
JSON_FORMAT_SSE = 1 << 4, /* prefix/suffix with W3C server-sent events */
JSON_FORMAT_SEQ = 1 << 5, /* prefix/suffix with RFC 7464 application/json-seq */
};
typedef enum JsonFormatFlags {
JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
JSON_FORMAT_COLOR = 1 << 2, /* insert ANSI color sequences */
JSON_FORMAT_COLOR_AUTO = 1 << 3, /* insetr ANSI color sequences if colors_enabled() says so */
JSON_FORMAT_SOURCE = 1 << 4, /* prefix with source filename/line/column */
JSON_FORMAT_SSE = 1 << 5, /* prefix/suffix with W3C server-sent events */
JSON_FORMAT_SEQ = 1 << 6, /* prefix/suffix with RFC 7464 application/json-seq */
} JsonFormatFlags;
int json_variant_format(JsonVariant *v, unsigned flags, char **ret);
void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix);
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const char *prefix);
int json_parse(const char *string, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
+1 -4
View File
@@ -946,10 +946,7 @@ static int output_json(
}
json_variant_dump(object,
(mode == OUTPUT_JSON_SSE ? JSON_FORMAT_SSE :
mode == OUTPUT_JSON_SEQ ? JSON_FORMAT_SEQ :
mode == OUTPUT_JSON_PRETTY ? JSON_FORMAT_PRETTY :
JSON_FORMAT_NEWLINE) |
output_mode_to_json_format_flags(mode) |
(FLAGS_SET(flags, OUTPUT_COLOR) ? JSON_FORMAT_COLOR : 0),
f, NULL);
+18
View File
@@ -3,6 +3,24 @@
#include "output-mode.h"
#include "string-table.h"
JsonFormatFlags output_mode_to_json_format_flags(OutputMode m) {
switch (m) {
case OUTPUT_JSON_SSE:
return JSON_FORMAT_SSE;
case OUTPUT_JSON_SEQ:
return JSON_FORMAT_SEQ;
case OUTPUT_JSON_PRETTY:
return JSON_FORMAT_PRETTY;
default:
return JSON_FORMAT_NEWLINE;
}
}
static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
[OUTPUT_SHORT] = "short",
[OUTPUT_SHORT_FULL] = "short-full",
+7
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "json.h"
#include "macro.h"
typedef enum OutputMode {
@@ -23,6 +24,10 @@ typedef enum OutputMode {
_OUTPUT_MODE_INVALID = -1
} OutputMode;
static inline bool OUTPUT_MODE_IS_JSON(OutputMode m) {
return IN_SET(m, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ);
}
/* The output flags definitions are shared by the logs and process tree output. Some apply to both, some only to the
* logs output, others only to the process tree output. */
@@ -39,5 +44,7 @@ typedef enum OutputFlags {
OUTPUT_NO_HOSTNAME = 1 << 9,
} OutputFlags;
JsonFormatFlags output_mode_to_json_format_flags(OutputMode m);
const char* output_mode_to_string(OutputMode m) _const_;
OutputMode output_mode_from_string(const char *s) _pure_;
+2 -2
View File
@@ -9,7 +9,7 @@ static void test_issue_9549(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
assert_se(table = table_new("NAME", "TYPE", "RO", "USAGE", "CREATED", "MODIFIED"));
assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0);
assert_se(table_add_many(table,
TABLE_STRING, "foooo",
@@ -36,7 +36,7 @@ int main(int argc, char *argv[]) {
assert_se(setenv("COLUMNS", "40", 1) >= 0);
assert_se(t = table_new("ONE", "TWO", "THREE"));
assert_se(t = table_new("one", "two", "three"));
assert_se(table_set_align_percent(t, TABLE_HEADER_CELL(2), 100) >= 0);