format-table: allow to explicitly override JSON field names

In some cases it's useful to explicitly generate the JSON field names to
generate for table columns, instead of auto-mangling them from table
header names that are intended for human consumption.

This adds the infra and a test for it.

It's intended to be used by #20544, for the first column, which in text
mode should have an empty header field, but have an explicit name in
json output mode.
This commit is contained in:
Lennart Poettering
2021-09-03 11:11:18 +02:00
committed by Luca Boccassi
parent 0d5765f7af
commit b03803f0dc
3 changed files with 105 additions and 16 deletions

View File

@@ -145,6 +145,9 @@ struct Table {
size_t *sort_map; /* The columns to order rows by, in order of preference. */
size_t n_sort_map;
char **json_fields;
size_t n_json_fields;
bool *reverse_map;
char *empty_string;
@@ -241,6 +244,11 @@ Table *table_unref(Table *t) {
free(t->reverse_map);
free(t->empty_string);
for (size_t i = 0; i < t->n_json_fields; i++)
free(t->json_fields[i]);
free(t->json_fields);
return mfree(t);
}
@@ -2608,6 +2616,12 @@ 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) {
assert(t);
return column < t->n_json_fields ? t->json_fields[column] : NULL;
}
int table_to_json(Table *t, JsonVariant **ret) {
JsonVariant **rows = NULL, **elements = NULL;
_cleanup_free_ size_t *sorted = NULL;
@@ -2651,26 +2665,36 @@ int table_to_json(Table *t, JsonVariant **ret) {
for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *mangled = NULL;
const char *formatted;
TableData *d;
const char *n;
size_t c;
assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
c = t->display_map ? t->display_map[j] : j;
/* Field names must be strings, hence format whatever we got here as a string first */
formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
if (!formatted) {
r = -ENOMEM;
goto finish;
/* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
n = table_get_json_field_name(t, c);
if (!n) {
const char *formatted;
TableData *d;
assert_se(d = t->data[c]);
/* Field names must be strings, hence format whatever we got here as a string first */
formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
if (!formatted) {
r = -ENOMEM;
goto finish;
}
/* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
mangled = string_to_json_field_name(formatted);
if (!mangled) {
r = -ENOMEM;
goto finish;
}
n = mangled;
}
/* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
mangled = string_to_json_field_name(formatted);
if (!mangled) {
r = -ENOMEM;
goto finish;
}
r = json_variant_new_string(elements + j*2, mangled);
r = json_variant_new_string(elements + j*2, n);
if (r < 0)
goto finish;
}
@@ -2771,3 +2795,30 @@ int table_print_with_pager(
return 0;
}
int table_set_json_field_name(Table *t, size_t column, const char *name) {
int r;
assert(t);
if (name) {
size_t m;
m = MAX(column + 1, t->n_json_fields);
if (!GREEDY_REALLOC0(t->json_fields, m))
return -ENOMEM;
r = free_and_strdup(t->json_fields + column, name);
if (r < 0)
return r;
t->n_json_fields = m;
return r;
} else {
if (column >= t->n_json_fields)
return 0;
t->json_fields[column] = mfree(t->json_fields[column]);
return 1;
}
}

View File

@@ -130,6 +130,8 @@ 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);
#define table_log_add_error(r) \
log_error_errno(r, "Failed to add cell(s) to table: %m")

View File

@@ -366,6 +366,41 @@ static void test_strv_wrapped(void) {
formatted = mfree(formatted);
}
static void test_json(void) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
_cleanup_(table_unrefp) Table *t = NULL;
log_info("/* %s */", __func__);
assert_se(t = table_new("foo bar", "quux", "piep miau"));
assert_se(table_set_json_field_name(t, 2, "zzz") >= 0);
assert_se(table_add_many(t,
TABLE_STRING, "v1",
TABLE_UINT64, UINT64_C(4711),
TABLE_BOOLEAN, true) >= 0);
assert_se(table_add_many(t,
TABLE_STRV, STRV_MAKE("a", "b", "c"),
TABLE_EMPTY,
TABLE_MODE, 0755) >= 0);
assert_se(table_to_json(t, &v) >= 0);
assert_se(json_build(&w,
JSON_BUILD_ARRAY(
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("foo_bar", JSON_BUILD_STRING("v1")),
JSON_BUILD_PAIR("quux", JSON_BUILD_UNSIGNED(4711)),
JSON_BUILD_PAIR("zzz", JSON_BUILD_BOOLEAN(true))),
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("foo_bar", JSON_BUILD_STRV(STRV_MAKE("a", "b", "c"))),
JSON_BUILD_PAIR("quux", JSON_BUILD_NULL),
JSON_BUILD_PAIR("zzz", JSON_BUILD_UNSIGNED(0755))))) >= 0);
assert_se(json_variant_equal(v, w));
}
int main(int argc, char *argv[]) {
_cleanup_(table_unrefp) Table *t = NULL;
_cleanup_free_ char *formatted = NULL;
@@ -509,6 +544,7 @@ int main(int argc, char *argv[]) {
test_multiline();
test_strv();
test_strv_wrapped();
test_json();
return 0;
}