]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/format-table.c
table: add more basic types
[thirdparty/systemd.git] / src / shared / format-table.c
index e685efea0e147fb999f77777a04ca44a231af1c5..09357694ddb26e8bf845cb5fa59254b303cebc51 100644 (file)
@@ -1,15 +1,17 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <stdio_ext.h>
+#include <ctype.h>
 
 #include "alloc-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
 #include "gunicode.h"
+#include "memory-util.h"
 #include "pager.h"
 #include "parse-util.h"
 #include "pretty-print.h"
+#include "sort-util.h"
 #include "string-util.h"
 #include "terminal-util.h"
 #include "time-util.h"
@@ -58,6 +60,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 */
@@ -69,6 +73,10 @@ typedef struct TableData {
                 usec_t timespan;
                 uint64_t size;
                 char string[0];
+                int int_val;
+                int32_t int32;
+                int64_t int64;
+                unsigned uint_val;
                 uint32_t uint32;
                 uint64_t uint64;
                 int percent;        /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
@@ -132,6 +140,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 +148,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 +160,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;
@@ -224,12 +230,16 @@ static size_t table_data_size(TableDataType type, const void *data) {
                 return sizeof(usec_t);
 
         case TABLE_SIZE:
+        case TABLE_INT64:
         case TABLE_UINT64:
                 return sizeof(uint64_t);
 
+        case TABLE_INT32:
         case TABLE_UINT32:
                 return sizeof(uint32_t);
 
+        case TABLE_INT:
+        case TABLE_UINT:
         case TABLE_PERCENT:
                 return sizeof(int);
 
@@ -269,6 +279,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);
 
@@ -370,6 +388,20 @@ int table_add_cell_full(
         return 0;
 }
 
+int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
+        _cleanup_free_ char *buffer = NULL;
+        va_list ap;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&buffer, format, ap);
+        va_end(ap);
+        if (r < 0)
+                return -ENOMEM;
+
+        return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
+}
+
 int table_dup_cell(Table *t, TableCell *cell) {
         size_t i;
 
@@ -427,6 +459,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 +607,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 +661,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;
@@ -631,6 +686,10 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                 union {
                         uint64_t size;
                         usec_t usec;
+                        int int_val;
+                        int32_t int32;
+                        int64_t int64;
+                        unsigned uint_val;
                         uint32_t uint32;
                         uint64_t uint64;
                         int percent;
@@ -663,6 +722,26 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         data = &buffer.size;
                         break;
 
+                case TABLE_INT:
+                        buffer.int_val = va_arg(ap, int);
+                        data = &buffer.int_val;
+                        break;
+
+                case TABLE_INT32:
+                        buffer.int32 = va_arg(ap, int32_t);
+                        data = &buffer.int32;
+                        break;
+
+                case TABLE_INT64:
+                        buffer.int64 = va_arg(ap, int64_t);
+                        data = &buffer.int64;
+                        break;
+
+                case TABLE_UINT:
+                        buffer.uint_val = va_arg(ap, unsigned);
+                        data = &buffer.uint_val;
+                        break;
+
                 case TABLE_UINT32:
                         buffer.uint32 = va_arg(ap, uint32_t);
                         data = &buffer.uint32;
@@ -798,6 +877,18 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                 case TABLE_SIZE:
                         return CMP(a->size, b->size);
 
+                case TABLE_INT:
+                        return CMP(a->int_val, b->int_val);
+
+                case TABLE_INT32:
+                        return CMP(a->int32, b->int32);
+
+                case TABLE_INT64:
+                        return CMP(a->int64, b->int64);
+
+                case TABLE_UINT:
+                        return CMP(a->uint_val, b->uint_val);
+
                 case TABLE_UINT32:
                         return CMP(a->uint32, b->uint32);
 
@@ -812,7 +903,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                 }
         }
 
-        /* Generic fallback using the orginal order in which the cells where added. */
+        /* Generic fallback using the original order in which the cells where added. */
         return CMP(index_a, index_b);
 }
 
@@ -858,6 +949,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:
@@ -884,7 +989,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);
@@ -905,6 +1010,54 @@ static const char *table_data_format(TableData *d) {
                 break;
         }
 
+        case TABLE_INT: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%i", d->int_val);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_INT32: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%" PRIi32, d->int32);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_INT64: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%" PRIi64, d->int64);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_UINT: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%u", d->uint_val);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
         case TABLE_UINT32: {
                 _cleanup_free_ char *p;
 
@@ -1328,12 +1481,10 @@ int table_format(Table *t, char **ret) {
         size_t sz = 0;
         int r;
 
-        f = open_memstream(&buf, &sz);
+        f = open_memstream_unlocked(&buf, &sz);
         if (!f)
                 return -ENOMEM;
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
         r = table_print(t, f);
         if (r < 0)
                 return r;
@@ -1414,3 +1565,178 @@ 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_INT:
+                return json_variant_new_integer(ret, d->int_val);
+
+        case TABLE_INT32:
+                return json_variant_new_integer(ret, d->int32);
+
+        case TABLE_INT64:
+                return json_variant_new_integer(ret, d->int64);
+
+        case TABLE_UINT:
+                return json_variant_new_unsigned(ret, d->uint_val);
+
+        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);
+}