diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2022-11-11 15:18:12 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-11 15:18:12 +0900 |
commit | b27c8036014f87048405623380ab36afc6cdfd58 (patch) | |
tree | 837a0f3813b86f94ff609b24e65fd0f25e2367b9 | |
parent | 021397f7762ea441f9f9dc015c7ea0cce7c0337a (diff) | |
parent | 31a19acf82c1a52e0a84c5419f381bdade0dd29b (diff) | |
download | systemd-b27c8036014f87048405623380ab36afc6cdfd58.tar.gz |
Merge pull request #25328 from poettering/vertical-tables
format-table: add concept of "vertical" table
-rw-r--r-- | src/resolve/resolvectl.c | 58 | ||||
-rw-r--r-- | src/shared/format-table.c | 162 | ||||
-rw-r--r-- | src/shared/format-table.h | 4 | ||||
-rw-r--r-- | src/test/test-format-table.c | 31 | ||||
-rw-r--r-- | src/timedate/timedatectl.c | 52 |
5 files changed, 232 insertions, 75 deletions
diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index c76ab1b018..ff645fc0d7 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1115,49 +1115,49 @@ static int show_statistics(int argc, char **argv, void *userdata) { if (r < 0) return bus_log_parse_error(r); - table = table_new("key", "value"); + table = table_new_vertical(); if (!table) return log_oom(); - table_set_header(table, false); - r = table_add_many(table, TABLE_STRING, "Transactions", TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, TABLE_EMPTY, - TABLE_STRING, "Current Transactions:", + TABLE_FIELD, "Current Transactions", TABLE_SET_ALIGN_PERCENT, 100, TABLE_UINT64, n_current_transactions, - TABLE_STRING, "Total Transactions:", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_FIELD, "Total Transactions", TABLE_UINT64, n_total_transactions, TABLE_EMPTY, TABLE_EMPTY, TABLE_STRING, "Cache", TABLE_SET_COLOR, ansi_highlight(), TABLE_SET_ALIGN_PERCENT, 0, TABLE_EMPTY, - TABLE_STRING, "Current Cache Size:", + TABLE_FIELD, "Current Cache Size", TABLE_SET_ALIGN_PERCENT, 100, TABLE_UINT64, cache_size, - TABLE_STRING, "Cache Hits:", + TABLE_FIELD, "Cache Hits", TABLE_UINT64, n_cache_hit, - TABLE_STRING, "Cache Misses:", + TABLE_FIELD, "Cache Misses", TABLE_UINT64, n_cache_miss, TABLE_EMPTY, TABLE_EMPTY, TABLE_STRING, "DNSSEC Verdicts", TABLE_SET_COLOR, ansi_highlight(), TABLE_SET_ALIGN_PERCENT, 0, TABLE_EMPTY, - TABLE_STRING, "Secure:", + TABLE_FIELD, "Secure", TABLE_SET_ALIGN_PERCENT, 100, TABLE_UINT64, n_dnssec_secure, - TABLE_STRING, "Insecure:", + TABLE_FIELD, "Insecure", TABLE_UINT64, n_dnssec_insecure, - TABLE_STRING, "Bogus:", + TABLE_FIELD, "Bogus", TABLE_UINT64, n_dnssec_bogus, - TABLE_STRING, "Indeterminate:", + TABLE_FIELD, "Indeterminate:", TABLE_UINT64, n_dnssec_indeterminate); if (r < 0) - table_log_add_error(r); + return table_log_add_error(r); r = table_print(table, NULL); if (r < 0) @@ -1477,14 +1477,14 @@ static void global_info_clear(GlobalInfo *p) { strv_free(p->ntas); } -static int dump_list(Table *table, const char *prefix, char * const *l) { +static int dump_list(Table *table, const char *field, char * const *l) { int r; if (strv_isempty(l)) return 0; r = table_add_many(table, - TABLE_STRING, prefix, + TABLE_FIELD, field, TABLE_STRV_WRAPPED, l); if (r < 0) return table_log_add_error(r); @@ -1656,15 +1656,13 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode printf("%sLink %i (%s)%s\n", ansi_highlight(), ifindex, name, ansi_normal()); - table = table_new("key", "value"); + table = table_new_vertical(); if (!table) return log_oom(); - table_set_header(table, false); - r = table_add_many(table, - TABLE_STRING, "Current Scopes:", - TABLE_SET_ALIGN_PERCENT, 100); + TABLE_FIELD, "Current Scopes", + TABLE_SET_MINIMUM_WIDTH, 19); if (r < 0) return table_log_add_error(r); @@ -1696,24 +1694,24 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode return log_oom(); r = table_add_many(table, - TABLE_STRING, "Protocols:", + TABLE_FIELD, "Protocols", TABLE_STRV_WRAPPED, pstatus); if (r < 0) return table_log_add_error(r); if (link_info.current_dns) { r = table_add_many(table, - TABLE_STRING, "Current DNS Server:", + TABLE_FIELD, "Current DNS Server", TABLE_STRING, link_info.current_dns_ex ?: link_info.current_dns); if (r < 0) return table_log_add_error(r); } - r = dump_list(table, "DNS Servers:", link_info.dns_ex ?: link_info.dns); + r = dump_list(table, "DNS Servers", link_info.dns_ex ?: link_info.dns); if (r < 0) return r; - r = dump_list(table, "DNS Domain:", link_info.domains); + r = dump_list(table, "DNS Domain", link_info.domains); if (r < 0) return r; @@ -1902,26 +1900,24 @@ static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) { printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); - table = table_new("key", "value"); + table = table_new_vertical(); if (!table) return log_oom(); - table_set_header(table, false); - _cleanup_strv_free_ char **pstatus = global_protocol_status(&global_info); if (!pstatus) return log_oom(); r = table_add_many(table, - TABLE_STRING, "Protocols:", - TABLE_SET_ALIGN_PERCENT, 100, + TABLE_FIELD, "Protocols", + TABLE_SET_MINIMUM_WIDTH, 19, TABLE_STRV_WRAPPED, pstatus); if (r < 0) return table_log_add_error(r); if (global_info.resolv_conf_mode) { r = table_add_many(table, - TABLE_STRING, "resolv.conf mode:", + TABLE_FIELD, "resolv.conf mode", TABLE_STRING, global_info.resolv_conf_mode); if (r < 0) return table_log_add_error(r); @@ -1929,7 +1925,7 @@ static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) { if (global_info.current_dns) { r = table_add_many(table, - TABLE_STRING, "Current DNS Server:", + TABLE_FIELD, "Current DNS Server", TABLE_STRING, global_info.current_dns_ex ?: global_info.current_dns); if (r < 0) return table_log_add_error(r); diff --git a/src/shared/format-table.c b/src/shared/format-table.c index bf2c88d893..2a3defeb7c 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -132,6 +132,8 @@ struct Table { size_t n_cells; bool header; /* Whether to show the header row? */ + bool vertical; /* Whether to field names are on the left rather than the first line */ + TableErsatz ersatz; /* What to show when we have an empty cell or an invalid value that cannot be rendered. */ size_t width; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console @@ -216,6 +218,38 @@ Table *table_new_internal(const char *first_header, ...) { return TAKE_PTR(t); } +Table *table_new_vertical(void) { + _cleanup_(table_unrefp) Table *t = NULL; + TableCell *cell; + + t = table_new_raw(2); + if (!t) + return NULL; + + t->vertical = true; + t->header = false; + + if (table_add_cell(t, &cell, TABLE_STRING, "key") < 0) + return NULL; + + if (table_set_uppercase(t, cell, true) < 0) + return NULL; + + if (table_set_align_percent(t, cell, 100) < 0) + return NULL; + + if (table_add_cell(t, &cell, TABLE_STRING, "value") < 0) + return NULL; + + if (table_set_uppercase(t, cell, true) < 0) + return NULL; + + if (table_set_align_percent(t, cell, 0) < 0) + return NULL; + + return TAKE_PTR(t); +} + static TableData *table_data_free(TableData *d) { assert(d); @@ -260,6 +294,7 @@ static size_t table_data_size(TableDataType type, const void *data) { case TABLE_STRING: case TABLE_PATH: + case TABLE_FIELD: return strlen(data) + 1; case TABLE_STRV: @@ -840,6 +875,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { case TABLE_STRING: case TABLE_PATH: + case TABLE_FIELD: data = va_arg(ap, const char *); break; @@ -1241,6 +1277,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t switch (a->type) { case TABLE_STRING: + case TABLE_FIELD: return strcmp(a->string, b->string); case TABLE_PATH: @@ -1416,20 +1453,33 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas case TABLE_STRING: case TABLE_PATH: + case TABLE_FIELD: if (d->uppercase && !avoid_uppercasing) { - d->formatted = new(char, strlen(d->string) + 1); + d->formatted = new(char, strlen(d->string) + (d->type == TABLE_FIELD) + 1); if (!d->formatted) return NULL; char *q = d->formatted; - for (char *p = d->string; *p; p++, q++) - *q = (char) toupper((unsigned char) *p); - *q = 0; + for (char *p = d->string; *p; p++) + *(q++) = (char) toupper((unsigned char) *p); + + if (d->type == TABLE_FIELD) + *(q++) = ':'; + *q = 0; return d->formatted; - } + } else { + if (d->type == TABLE_FIELD) { + d->formatted = strjoin(d->string, ":"); + if (!d->formatted) + return NULL; + + return d->formatted; + } - return d->string; + return d->string; + } + break; case TABLE_STRV: if (strv_isempty(d->strv)) @@ -1982,6 +2032,9 @@ static const char* table_data_color(TableData *d) { if (table_data_isempty(d)) return ansi_grey(); + if (d->type == TABLE_FIELD) + return ansi_bright_blue(); + return NULL; } @@ -2494,6 +2547,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) { case TABLE_STRING: case TABLE_PATH: + case TABLE_FIELD: return json_variant_new_string(ret, d->string); case TABLE_STRV: @@ -2629,21 +2683,23 @@ static char* string_to_json_field_name(const char *f) { return c; } -static const char *table_get_json_field_name(Table *t, size_t column) { +static const char *table_get_json_field_name(Table *t, size_t idx) { assert(t); - return column < t->n_json_fields ? t->json_fields[column] : NULL; + return idx < t->n_json_fields ? t->json_fields[idx] : NULL; } -int table_to_json(Table *t, JsonVariant **ret) { +static int table_to_json_regular(Table *t, JsonVariant **ret) { JsonVariant **rows = NULL, **elements = NULL; _cleanup_free_ size_t *sorted = NULL; size_t n_rows, display_columns; int r; assert(t); + assert(!t->vertical); /* Ensure we have no incomplete rows */ + assert(t->n_columns > 0); assert(t->n_cells % t->n_columns == 0); n_rows = t->n_cells / t->n_columns; @@ -2761,6 +2817,84 @@ finish: return r; } +static int table_to_json_vertical(Table *t, JsonVariant **ret) { + JsonVariant **elements = NULL; + size_t n_elements = 0; + int r; + + assert(t); + assert(t->vertical); + + if (t->n_columns != 2) + return -EINVAL; + + /* Ensure we have no incomplete rows */ + assert(t->n_cells % t->n_columns == 0); + + elements = new0(JsonVariant *, t->n_cells); + if (!elements) { + r = -ENOMEM; + goto finish; + } + + for (size_t i = t->n_columns; i < t->n_cells; i++) { + + if (i % t->n_columns == 0) { + _cleanup_free_ char *mangled = NULL; + const char *n; + + n = table_get_json_field_name(t, i / t->n_columns - 1); + if (!n) { + TableData *d = ASSERT_PTR(t->data[i]); + + if (d->type == TABLE_FIELD) + n = d->string; + else { + n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL); + if (!n) { + r = -ENOMEM; + goto finish; + } + } + + mangled = string_to_json_field_name(n); + if (!mangled) { + r = -ENOMEM; + goto finish; + } + + n = mangled; + } + + r = json_variant_new_string(elements + n_elements, n); + } else + r = table_data_to_json(t->data[i], elements + n_elements); + if (r < 0) + goto finish; + + n_elements++; + } + + r = json_variant_new_object(ret, elements, n_elements); + +finish: + if (elements) { + json_variant_unref_many(elements, n_elements); + free(elements); + } + + return r; +} + +int table_to_json(Table *t, JsonVariant **ret) { + assert(t); + + if (t->vertical) + return table_to_json_vertical(t, ret); + + return table_to_json_regular(t, ret); +} + int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; int r; @@ -2809,7 +2943,7 @@ int table_print_with_pager( return 0; } -int table_set_json_field_name(Table *t, size_t column, const char *name) { +int table_set_json_field_name(Table *t, size_t idx, const char *name) { int r; assert(t); @@ -2817,21 +2951,21 @@ int table_set_json_field_name(Table *t, size_t column, const char *name) { if (name) { size_t m; - m = MAX(column + 1, t->n_json_fields); + m = MAX(idx + 1, t->n_json_fields); if (!GREEDY_REALLOC0(t->json_fields, m)) return -ENOMEM; - r = free_and_strdup(t->json_fields + column, name); + r = free_and_strdup(t->json_fields + idx, name); if (r < 0) return r; t->n_json_fields = m; return r; } else { - if (column >= t->n_json_fields) + if (idx >= t->n_json_fields) return 0; - t->json_fields[column] = mfree(t->json_fields[column]); + t->json_fields[idx] = mfree(t->json_fields[idx]); return 1; } } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index c88a060dbe..e90a7b7a69 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -12,6 +12,7 @@ typedef enum TableDataType { TABLE_EMPTY, TABLE_STRING, + TABLE_FIELD, /* used in vertical mode */ TABLE_STRV, TABLE_STRV_WRAPPED, TABLE_PATH, @@ -78,6 +79,7 @@ typedef struct TableCell TableCell; Table *table_new_internal(const char *first_header, ...) _sentinel_; #define table_new(...) table_new_internal(__VA_ARGS__, NULL) Table *table_new_raw(size_t n_columns); +Table *table_new_vertical(void); Table *table_unref(Table *t); DEFINE_TRIVIAL_CLEANUP_FUNC(Table*, table_unref); @@ -139,7 +141,7 @@ int table_print_json(Table *t, FILE *f, JsonFormatFlags json_flags); int table_print_with_pager(Table *t, JsonFormatFlags json_format_flags, PagerFlags pager_flags, bool show_header); -int table_set_json_field_name(Table *t, size_t column, const char *name); +int table_set_json_field_name(Table *t, size_t idx, const char *name); #define table_log_add_error(r) \ log_error_errno(r, "Failed to add cells to table: %m") diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 60d5fee71f..14341b97b4 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -534,6 +534,37 @@ TEST(table) { "5min 5min \n")); } +TEST(vertical) { + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(t = table_new_vertical()); + + assert_se(table_add_many(t, + TABLE_FIELD, "pfft aa", TABLE_STRING, "foo", + TABLE_FIELD, "uuu o", TABLE_SIZE, UINT64_C(1024), + TABLE_FIELD, "lllllllllllo", TABLE_STRING, "jjjjjjjjjjjjjjjjj") >= 0); + + assert_se(table_set_json_field_name(t, 1, "dimpfelmoser") >= 0); + + assert_se(table_format(t, &formatted) >= 0); + + assert_se(streq(formatted, + " pfft aa: foo\n" + " uuu o: 1.0K\n" + "lllllllllllo: jjjjjjjjjjjjjjjjj\n")); + + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL; + assert_se(table_to_json(t, &a) >= 0); + + assert_se(json_build(&b, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("pfft_aa", JSON_BUILD_STRING("foo")), + JSON_BUILD_PAIR("dimpfelmoser", JSON_BUILD_UNSIGNED(1024)), + JSON_BUILD_PAIR("lllllllllllo", JSON_BUILD_STRING("jjjjjjjjjjjjjjjjj")))) >= 0); + + assert_se(json_variant_equal(a, b)); +} + static int intro(void) { assert_se(setenv("SYSTEMD_COLORS", "0", 1) >= 0); assert_se(setenv("COLUMNS", "40", 1) >= 0); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index e3c722610a..5d1be11509 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -61,15 +61,12 @@ static int print_status_info(const StatusInfo *i) { assert(i); - table = table_new("key", "value"); + table = table_new_vertical(); if (!table) return log_oom(); - table_set_header(table, false); - assert_se(cell = table_get_cell(table, 0, 0)); (void) table_set_ellipsize_percent(table, cell, 100); - (void) table_set_align_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); (void) table_set_ellipsize_percent(table, cell, 100); @@ -97,14 +94,14 @@ static int print_status_info(const StatusInfo *i) { n = have_time ? strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) : 0; r = table_add_many(table, - TABLE_STRING, "Local time:", + TABLE_FIELD, "Local time", TABLE_STRING, n > 0 ? a : "n/a"); if (r < 0) return table_log_add_error(r); n = have_time ? strftime(a, sizeof a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)) : 0; r = table_add_many(table, - TABLE_STRING, "Universal time:", + TABLE_FIELD, "Universal time", TABLE_STRING, n > 0 ? a : "n/a"); if (r < 0) return table_log_add_error(r); @@ -117,12 +114,12 @@ static int print_status_info(const StatusInfo *i) { } else n = 0; r = table_add_many(table, - TABLE_STRING, "RTC time:", + TABLE_FIELD, "RTC time", TABLE_STRING, n > 0 ? a : "n/a"); if (r < 0) return table_log_add_error(r); - r = table_add_cell(table, NULL, TABLE_STRING, "Time zone:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Time zone"); if (r < 0) return table_log_add_error(r); @@ -139,11 +136,11 @@ static int print_status_info(const StatusInfo *i) { tzset(); r = table_add_many(table, - TABLE_STRING, "System clock synchronized:", + TABLE_FIELD, "System clock synchronized", TABLE_BOOLEAN, i->ntp_synced, - TABLE_STRING, "NTP service:", + TABLE_FIELD, "NTP service", TABLE_STRING, i->ntp_capable ? (i->ntp_active ? "active" : "inactive") : "n/a", - TABLE_STRING, "RTC in local TZ:", + TABLE_FIELD, "RTC in local TZ", TABLE_BOOLEAN, i->rtc_local); if (r < 0) return table_log_add_error(r); @@ -363,15 +360,12 @@ static int print_ntp_status_info(NTPStatusInfo *i) { assert(i); - table = table_new("key", "value"); + table = table_new_vertical(); if (!table) return log_oom(); - table_set_header(table, false); - assert_se(cell = table_get_cell(table, 0, 0)); (void) table_set_ellipsize_percent(table, cell, 100); - (void) table_set_align_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); (void) table_set_ellipsize_percent(table, cell, 100); @@ -388,7 +382,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" */ - r = table_add_cell(table, NULL, TABLE_STRING, "Server:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Server"); if (r < 0) return table_log_add_error(r); @@ -396,7 +390,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_add_cell(table, NULL, TABLE_STRING, "Poll interval:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Poll interval"); if (r < 0) return table_log_add_error(r); @@ -409,7 +403,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (i->packet_count == 0) { r = table_add_many(table, - TABLE_STRING, "Packet count:", + TABLE_FIELD, "Packet count", TABLE_STRING, "0"); if (r < 0) return table_log_add_error(r); @@ -440,13 +434,13 @@ static int print_ntp_status_info(NTPStatusInfo *i) { root_distance = i->root_delay / 2 + i->root_dispersion; r = table_add_many(table, - TABLE_STRING, "Leap:", + TABLE_FIELD, "Leap", TABLE_STRING, ntp_leap_to_string(i->leap), - TABLE_STRING, "Version:", + TABLE_FIELD, "Version", TABLE_UINT32, i->version, - TABLE_STRING, "Stratum:", + TABLE_FIELD, "Stratum", TABLE_UINT32, i->stratum, - TABLE_STRING, "Reference:"); + TABLE_FIELD, "Reference"); if (r < 0) return table_log_add_error(r); @@ -457,7 +451,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_add_cell(table, NULL, TABLE_STRING, "Precision:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Precision"); if (r < 0) return table_log_add_error(r); @@ -467,7 +461,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_add_cell(table, NULL, TABLE_STRING, "Root distance:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Root distance"); if (r < 0) return table_log_add_error(r); @@ -477,7 +471,7 @@ static int print_ntp_status_info(NTPStatusInfo *i) { if (r < 0) return table_log_add_error(r); - r = table_add_cell(table, NULL, TABLE_STRING, "Offset:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Offset"); if (r < 0) return table_log_add_error(r); @@ -488,17 +482,17 @@ static int print_ntp_status_info(NTPStatusInfo *i) { return table_log_add_error(r); r = table_add_many(table, - TABLE_STRING, "Delay:", + TABLE_FIELD, "Delay", TABLE_STRING, FORMAT_TIMESPAN(delay, 0), - TABLE_STRING, "Jitter:", + TABLE_FIELD, "Jitter", TABLE_STRING, FORMAT_TIMESPAN(i->jitter, 0), - TABLE_STRING, "Packet count:", + TABLE_FIELD, "Packet count", TABLE_UINT64, i->packet_count); if (r < 0) return table_log_add_error(r); if (!i->spike) { - r = table_add_cell(table, NULL, TABLE_STRING, "Frequency:"); + r = table_add_cell(table, NULL, TABLE_FIELD, "Frequency"); if (r < 0) return table_log_add_error(r); |