X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=src%2Fshared%2Fformat-table.c;h=6de7da0df0c97d42fc41f0678fb0c6be4034a206;hb=a7a257cdda6ac94dc26b58460b648c8d206c82ee;hp=959bfc53c4f1d441bd64ed22c4112f72c500125a;hpb=a22318e55492af721879d8692ed039144696bb08;p=thirdparty%2Fsystemd.git diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 959bfc53c4f..6de7da0df0c 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -1,16 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ -#include +#include +#include #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" +#include "format-util.h" #include "gunicode.h" +#include "in-addr-util.h" +#include "memory-util.h" #include "pager.h" #include "parse-util.h" #include "pretty-print.h" +#include "sort-util.h" #include "string-util.h" +#include "strxcpyx.h" #include "terminal-util.h" #include "time-util.h" #include "utf8.h" @@ -58,6 +64,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 */ @@ -69,7 +77,15 @@ typedef struct TableData { usec_t timespan; uint64_t size; char string[0]; + int int_val; + int32_t int32; + int64_t int64; + unsigned uint_val; uint32_t uint32; + uint64_t uint64; + int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */ + int ifindex; + union in_addr_union address; /* … add more here as we start supporting more cell data types … */ }; } TableData; @@ -105,6 +121,8 @@ struct Table { size_t *sort_map; /* The columns to order rows by, in order of preference. */ size_t n_sort_map; + + bool *reverse_map; }; Table *table_new_raw(size_t n_columns) { @@ -128,6 +146,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; @@ -135,8 +154,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; @@ -149,19 +166,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; @@ -197,6 +213,7 @@ Table *table_unref(Table *t) { free(t->data); free(t->display_map); free(t->sort_map); + free(t->reverse_map); return mfree(t); } @@ -215,15 +232,34 @@ static size_t table_data_size(TableDataType type, const void *data) { return sizeof(bool); case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: return sizeof(usec_t); case TABLE_SIZE: + case TABLE_INT64: + case TABLE_UINT64: + case TABLE_BPS: return sizeof(uint64_t); + case TABLE_INT32: case TABLE_UINT32: return sizeof(uint32_t); + case TABLE_INT: + case TABLE_UINT: + case TABLE_PERCENT: + case TABLE_IFINDEX: + return sizeof(int); + + case TABLE_IN_ADDR: + return sizeof(struct in_addr); + + case TABLE_IN6_ADDR: + return sizeof(struct in6_addr); + default: assert_not_reached("Uh? Unexpected cell type"); } @@ -260,13 +296,21 @@ 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); if (k != l) return false; - return memcmp(data, d->data, l) == 0; + return memcmp_safe(data, d->data, l) == 0; } static TableData *table_data_new( @@ -361,6 +405,20 @@ int table_add_cell_full( return 0; } +int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) { + _cleanup_free_ char *buffer = NULL; + va_list ap; + int r; + + va_start(ap, format); + r = vasprintf(&buffer, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return table_add_cell(t, ret_cell, TABLE_STRING, buffer); +} + int table_dup_cell(Table *t, TableCell *cell) { size_t i; @@ -418,6 +476,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; @@ -565,9 +624,72 @@ 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; + size_t i; + + assert(t); + assert(cell); + + i = TABLE_CELL_TO_INDEX(cell); + if (i >= t->n_cells) + return -ENXIO; + + assert_se(od = t->data[i]); + + if (od->url) { + curl = strdup(od->url); + if (!curl) + return -ENOMEM; + } + + nd = table_data_new( + type, + data, + od->minimum_width, + od->maximum_width, + od->weight, + od->align_percent, + od->ellipsize_percent); + if (!nd) + return -ENOMEM; + + nd->color = od->color; + nd->url = TAKE_PTR(curl); + nd->uppercase = od->uppercase; + + table_data_unref(od); + t->data[i] = nd; + + return 0; +} + int table_add_many_internal(Table *t, TableDataType first_type, ...) { TableDataType type; va_list ap; + TableCell *last_cell = NULL; int r; assert(t); @@ -582,8 +704,16 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { union { uint64_t size; usec_t usec; + int int_val; + int32_t int32; + int64_t int64; + unsigned uint_val; uint32_t uint32; + uint64_t uint64; + int percent; + int ifindex; bool b; + union in_addr_union address; } buffer; switch (type) { @@ -602,21 +732,119 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { break; case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: buffer.usec = va_arg(ap, usec_t); data = &buffer.usec; break; case TABLE_SIZE: + case TABLE_BPS: buffer.size = va_arg(ap, uint64_t); data = &buffer.size; break; + case TABLE_INT: + buffer.int_val = va_arg(ap, int); + data = &buffer.int_val; + break; + + case TABLE_INT32: + buffer.int32 = va_arg(ap, int32_t); + data = &buffer.int32; + break; + + case TABLE_INT64: + buffer.int64 = va_arg(ap, int64_t); + data = &buffer.int64; + break; + + case TABLE_UINT: + buffer.uint_val = va_arg(ap, unsigned); + data = &buffer.uint_val; + break; + case TABLE_UINT32: buffer.uint32 = va_arg(ap, uint32_t); data = &buffer.uint32; break; + case TABLE_UINT64: + buffer.uint64 = va_arg(ap, uint64_t); + data = &buffer.uint64; + break; + + case TABLE_PERCENT: + buffer.percent = va_arg(ap, int); + data = &buffer.percent; + break; + + case TABLE_IFINDEX: + buffer.ifindex = va_arg(ap, int); + data = &buffer.ifindex; + break; + + case TABLE_IN_ADDR: + buffer.address = *va_arg(ap, union in_addr_union *); + data = &buffer.address.in; + break; + + case TABLE_IN6_ADDR: + buffer.address = *va_arg(ap, union in_addr_union *); + data = &buffer.address.in6; + break; + + case TABLE_SET_MINIMUM_WIDTH: { + size_t w = va_arg(ap, size_t); + + r = table_set_minimum_width(t, last_cell, w); + break; + } + + case TABLE_SET_MAXIMUM_WIDTH: { + size_t w = va_arg(ap, size_t); + r = table_set_maximum_width(t, last_cell, w); + break; + } + + case TABLE_SET_WEIGHT: { + unsigned w = va_arg(ap, unsigned); + r = table_set_weight(t, last_cell, w); + break; + } + + case TABLE_SET_ALIGN_PERCENT: { + unsigned p = va_arg(ap, unsigned); + r = table_set_align_percent(t, last_cell, p); + break; + } + + case TABLE_SET_ELLIPSIZE_PERCENT: { + unsigned p = va_arg(ap, unsigned); + r = table_set_ellipsize_percent(t, last_cell, p); + break; + } + + case TABLE_SET_COLOR: { + const char *c = va_arg(ap, const char*); + r = table_set_color(t, last_cell, c); + break; + } + + case TABLE_SET_URL: { + const char *u = va_arg(ap, const char*); + r = table_set_url(t, last_cell, u); + break; + } + + case TABLE_SET_UPPERCASE: { + int u = va_arg(ap, int); + r = table_set_uppercase(t, last_cell, u); + break; + } + case _TABLE_DATA_TYPE_MAX: /* Used as end marker */ va_end(ap); @@ -626,7 +854,9 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { assert_not_reached("Uh? Unexpected data type."); } - r = table_add_cell(t, NULL, type, data); + if (type < _TABLE_DATA_TYPE_MAX) + r = table_add_cell(t, &last_cell, type, data); + if (r < 0) { va_end(ap); return r; @@ -729,23 +959,54 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t return 0; case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: return CMP(a->timestamp, b->timestamp); case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: return CMP(a->timespan, b->timespan); case TABLE_SIZE: + case TABLE_BPS: return CMP(a->size, b->size); + case TABLE_INT: + return CMP(a->int_val, b->int_val); + + case TABLE_INT32: + return CMP(a->int32, b->int32); + + case TABLE_INT64: + return CMP(a->int64, b->int64); + + case TABLE_UINT: + return CMP(a->uint_val, b->uint_val); + case TABLE_UINT32: return CMP(a->uint32, b->uint32); + case TABLE_UINT64: + return CMP(a->uint64, b->uint64); + + case TABLE_PERCENT: + return CMP(a->percent, b->percent); + + case TABLE_IFINDEX: + return CMP(a->ifindex, b->ifindex); + + case TABLE_IN_ADDR: + return CMP(a->address.in.s_addr, b->address.in.s_addr); + + case TABLE_IN6_ADDR: + return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6)); + default: ; } } - /* Generic fallback using the orginal order in which the cells where added. */ + /* Generic fallback using the original order in which the cells where added. */ return CMP(index_a, index_b); } @@ -773,7 +1034,7 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) { r = cell_data_compare(d, *a, dd, *b); if (r != 0) - return r; + return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r; } /* Order identical lines by the order there were originally added in */ @@ -791,33 +1052,58 @@ 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: return yes_no(d->boolean); - case TABLE_TIMESTAMP: { + case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: { _cleanup_free_ char *p; + char *ret; p = new(char, FORMAT_TIMESTAMP_MAX); if (!p) return NULL; - if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp)) + if (d->type == TABLE_TIMESTAMP) + ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp); + else if (d->type == TABLE_TIMESTAMP_UTC) + ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp); + else + ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp); + if (!ret) return "n/a"; d->formatted = TAKE_PTR(p); break; } - case TABLE_TIMESPAN: { + case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: { _cleanup_free_ char *p; p = new(char, FORMAT_TIMESPAN_MAX); if (!p) return NULL; - if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0)) + if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan, + d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC)) return "n/a"; d->formatted = TAKE_PTR(p); @@ -838,6 +1124,72 @@ static const char *table_data_format(TableData *d) { break; } + case TABLE_BPS: { + _cleanup_free_ char *p; + size_t n; + + p = new(char, FORMAT_BYTES_MAX+2); + if (!p) + return NULL; + + if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0)) + return "n/a"; + + n = strlen(p); + strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps"); + + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_INT: { + _cleanup_free_ char *p; + + p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1); + if (!p) + return NULL; + + sprintf(p, "%i", d->int_val); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_INT32: { + _cleanup_free_ char *p; + + p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1); + if (!p) + return NULL; + + sprintf(p, "%" PRIi32, d->int32); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_INT64: { + _cleanup_free_ char *p; + + p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1); + if (!p) + return NULL; + + sprintf(p, "%" PRIi64, d->int64); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_UINT: { + _cleanup_free_ char *p; + + p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1); + if (!p) + return NULL; + + sprintf(p, "%u", d->uint_val); + d->formatted = TAKE_PTR(p); + break; + } + case TABLE_UINT32: { _cleanup_free_ char *p; @@ -850,6 +1202,59 @@ static const char *table_data_format(TableData *d) { break; } + case TABLE_UINT64: { + _cleanup_free_ char *p; + + p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1); + if (!p) + return NULL; + + sprintf(p, "%" PRIu64, d->uint64); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_PERCENT: { + _cleanup_free_ char *p; + + p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2); + if (!p) + return NULL; + + sprintf(p, "%i%%" , d->percent); + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_IFINDEX: { + _cleanup_free_ char *p; + char name[IF_NAMESIZE + 1]; + + if (format_ifname(d->ifindex, name)) { + p = strdup(name); + if (!p) + return NULL; + } else { + if (asprintf(&p, "%i" , d->ifindex) < 0) + return NULL; + } + + d->formatted = TAKE_PTR(p); + break; + } + + case TABLE_IN_ADDR: + case TABLE_IN6_ADDR: { + _cleanup_free_ char *p = NULL; + + if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6, + &d->address, &p) < 0) + return NULL; + + d->formatted = TAKE_PTR(p); + break; + } + default: assert_not_reached("Unexpected type?"); } @@ -1206,15 +1611,22 @@ int table_print(Table *t, FILE *f) { field = buffer; } + if (row == t->data) /* underline header line fully, including the column separator */ + fputs(ansi_underline(), f); + if (j > 0) fputc(' ', f); /* column separator */ - if (d->color && colors_enabled()) + if (d->color && colors_enabled()) { + if (row == t->data) /* first undo header underliner */ + fputs(ANSI_NORMAL, f); + fputs(d->color, f); + } fputs(field, f); - if (d->color && colors_enabled()) + if (colors_enabled() && (d->color || row == t->data)) fputs(ANSI_NORMAL, f); } @@ -1230,12 +1642,10 @@ int table_format(Table *t, char **ret) { size_t sz = 0; int r; - f = open_memstream(&buf, &sz); + f = open_memstream_unlocked(&buf, &sz); if (!f) return -ENOMEM; - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - r = table_print(t, f); if (r < 0) return r; @@ -1262,3 +1672,245 @@ size_t table_get_columns(Table *t) { assert(t->n_columns > 0); return t->n_columns; } + +int table_set_reverse(Table *t, size_t column, bool b) { + assert(t); + assert(column < t->n_columns); + + if (!t->reverse_map) { + if (!b) + return 0; + + t->reverse_map = new0(bool, t->n_columns); + if (!t->reverse_map) + return -ENOMEM; + } + + t->reverse_map[column] = b; + return 0; +} + +TableCell *table_get_cell(Table *t, size_t row, size_t column) { + size_t i; + + assert(t); + + if (column >= t->n_columns) + return NULL; + + i = row * t->n_columns + column; + if (i >= t->n_cells) + return NULL; + + return TABLE_INDEX_TO_CELL(i); +} + +const void *table_get(Table *t, TableCell *cell) { + TableData *d; + + assert(t); + + d = table_get_data(t, cell); + if (!d) + return NULL; + + return d->data; +} + +const void* table_get_at(Table *t, size_t row, size_t column) { + TableCell *cell; + + cell = table_get_cell(t, row, column); + if (!cell) + return NULL; + + 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: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: + if (d->timestamp == USEC_INFINITY) + return json_variant_new_null(ret); + + return json_variant_new_unsigned(ret, d->timestamp); + + case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: + if (d->timespan == USEC_INFINITY) + return json_variant_new_null(ret); + + return json_variant_new_unsigned(ret, d->timespan); + + case TABLE_SIZE: + case TABLE_BPS: + if (d->size == (size_t) -1) + return json_variant_new_null(ret); + + return json_variant_new_unsigned(ret, d->size); + + case TABLE_INT: + return json_variant_new_integer(ret, d->int_val); + + case TABLE_INT32: + return json_variant_new_integer(ret, d->int32); + + case TABLE_INT64: + return json_variant_new_integer(ret, d->int64); + + case TABLE_UINT: + return json_variant_new_unsigned(ret, d->uint_val); + + 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); + + case TABLE_IFINDEX: + return json_variant_new_integer(ret, d->ifindex); + + case TABLE_IN_ADDR: + return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET)); + + case TABLE_IN6_ADDR: + return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6)); + + 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); +}