From: Lennart Poettering Date: Fri, 3 Sep 2021 09:11:18 +0000 (+0200) Subject: format-table: allow to explicitly override JSON field names X-Git-Tag: v250-rc1~733 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b03803f0dc46613edaa34355786d6d3c1dec228c;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 9651fd8c001..5390eddcd67 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -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; - /* 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; + 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; } - 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; + } +} diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 087daf34859..2b189f88923 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -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") diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 44d92a719d1..ea96e223916 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -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; }