From 897f099bb0adf69d2b9332fe84d115f12a34da46 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:27:55 +0100 Subject: [PATCH 01/11] json: introduce explicit type for json formatting flags --- src/shared/json.c | 8 ++++---- src/shared/json.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/shared/json.c b/src/shared/json.c index 420555ae54..75795c2850 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -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; diff --git a/src/shared/json.h b/src/shared/json.h index 4a21a411ac..04687d4135 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -150,17 +150,17 @@ struct json_variant_foreach_state { int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); -enum { +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_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 */ -}; +} 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); From ab91733c7e463c657358536540ca91d54521f3e6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:33:02 +0100 Subject: [PATCH 02/11] json: add new display flag JSON_FORMAT_COLOR_AUTO Typically we want to enable color when colors_enabled() says so, hence let's automatize this. --- src/busctl/busctl.c | 2 +- src/shared/json.c | 3 +++ src/shared/json.h | 13 +++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 6188216d28..0bcc89f6f2 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -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); } diff --git a/src/shared/json.c b/src/shared/json.c index 75795c2850..c410b62008 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -1585,6 +1585,9 @@ void json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const cha 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) diff --git a/src/shared/json.h b/src/shared/json.h index 04687d4135..4eba91c272 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -151,12 +151,13 @@ struct json_variant_foreach_state { int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); 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_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 */ + 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, JsonFormatFlags flags, char **ret); From 9e964bb8e472174fe680a478167ed20a8f2515a4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:40:02 +0100 Subject: [PATCH 03/11] output-mode: add generic helper to pick right JsonFormatFlags for given OutputMode --- src/shared/logs-show.c | 5 +---- src/shared/output-mode.c | 18 ++++++++++++++++++ src/shared/output-mode.h | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 41d484fa16..76457bb475 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -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); diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c index 9463d185f0..107b345538 100644 --- a/src/shared/output-mode.c +++ b/src/shared/output-mode.c @@ -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", diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index 3cbaeadde6..a3be8d668e 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once +#include "json.h" #include "macro.h" typedef enum OutputMode { @@ -39,5 +40,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_; From e4cbdffe282ab9b3bb346a963726484f1ed71b08 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:43:07 +0100 Subject: [PATCH 04/11] output-mode: add new helper OUTPUT_MODE_IS_JSON() Just a pretty way to determine whether the selected output mode is a JSON output mode. --- src/shared/output-mode.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index a3be8d668e..00b6032056 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -24,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. */ From c93d372d7ceda0b080487fa35d3638ef3d8507cb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:28:15 +0100 Subject: [PATCH 05/11] format-table: use right field for display Since .timespan and .timestamp are unionized on top of each other this doesn't actually matter, but it is still more correct to address it under it's correct name. --- src/shared/format-table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index e685efea0e..f3cc9b9185 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -884,7 +884,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); From 31ac235771cfb8ff9ce0e62a325e271d20d3c2e2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 16:25:34 +0100 Subject: [PATCH 06/11] format-table: optionally covert/output table to JSON --- src/shared/format-table.c | 163 ++++++++++++++++++++++++++++++++++++++ src/shared/format-table.h | 4 + 2 files changed, 167 insertions(+) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index f3cc9b9185..55767580ca 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -1414,3 +1414,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); +} diff --git a/src/shared/format-table.h b/src/shared/format-table.h index a2bb2e0846..0f13f5bbfb 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -5,6 +5,7 @@ #include #include +#include "json.h" #include "macro.h" typedef enum TableDataType { @@ -71,3 +72,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); From e3483674ec0c9507f2439a070bebaf1cead62e5d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:46:33 +0100 Subject: [PATCH 07/11] loginctl: optionally output table in json This works now: $ loginctl list-session --output=json-pretty --- src/login/loginctl.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/login/loginctl.c b/src/login/loginctl.c index fb5240d7d3..d0987fcd1a 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -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"); } @@ -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: From ad9d139e023eb08144b79ba89914a91b8463b2ec Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 20:58:32 +0100 Subject: [PATCH 08/11] machinectl: optionally output table in JSON --- src/machine/machinectl.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 5023acb5e2..b7a604a55c 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -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"); } @@ -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: From 359abf6dd05aa6bca3438e9c969ed904bd3d447d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 21:36:26 +0100 Subject: [PATCH 09/11] format-table: add option to uppercase cells on display This adds a per-cell option for uppercasing displayed strings. Implicitly turn this on for the header row. The fact that we format the table header in uppercase is a formatting thing after all, hence should be applied by the formatter, i.e. the table display code. Moreover, this provides us with the benefit that we can more nicely reuse the specified table headers as JSON field names, like we already do: json field names are usually not uppercase. --- src/shared/format-table.c | 62 +++++++++++++++++++++++++++++++-------- src/shared/format-table.h | 1 + 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 55767580ca..6fdb583a72 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ +#include #include #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; @@ -427,6 +428,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 +576,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 +630,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 +882,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: diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 0f13f5bbfb..5ff247953b 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -46,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); From 9969b5427967dc7bcbd9a14ea4d6ae8133c5fec0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 21:39:12 +0100 Subject: [PATCH 10/11] tree-wide: specify all table headers in lower-case --- src/analyze/analyze-security.c | 4 ++-- src/login/inhibit.c | 2 +- src/login/loginctl.c | 6 +++--- src/machine/machinectl.c | 4 ++-- src/portable/portablectl.c | 2 +- src/test/test-format-table.c | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 6402d4504a..9f327d365e 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -1393,7 +1393,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(); @@ -1967,7 +1967,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(); } diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 2394c5d937..03bbf3b826 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -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(); diff --git a/src/login/loginctl.c b/src/login/loginctl.c index d0987fcd1a..ab1b56201a 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -146,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(); @@ -227,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(); @@ -284,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(); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index b7a604a55c..c5fe01406e 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -300,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(); @@ -380,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(); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 02d3954897..4763c0d7d0 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -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(); diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 2527cacbbd..5bede5c75b 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -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); From 94a80afed42a8b36d69a229bf44ba690f8f59a78 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 3 Dec 2018 21:39:39 +0100 Subject: [PATCH 11/11] format-table: never try to reuse cells that have color/url/uppercase set The table cell reusing code is supposed to be an internal memory optimization, and not more. This means behaviour should be the same as if we wouldn't reuse cells. --- src/shared/format-table.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 6fdb583a72..7d529801a1 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -270,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);