1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-12 13:18:14 +03:00

Merge pull request #25328 from poettering/vertical-tables

format-table: add concept of "vertical" table
This commit is contained in:
Yu Watanabe 2022-11-11 15:18:12 +09:00 committed by GitHub
commit b27c803601
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 232 additions and 75 deletions

View File

@ -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);

View File

@ -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);
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->string;
return d->formatted;
}
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;
}
}

View File

@ -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")

View File

@ -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);

View File

@ -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);