-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <ctype.h>
#include <net/if.h>
#include <unistd.h>
+#include "sd-id128.h"
+
#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "gunicode.h"
+#include "id128-util.h"
#include "in-addr-util.h"
#include "locale-util.h"
#include "memory-util.h"
size_t minimum_width; /* minimum width for the column */
size_t maximum_width; /* maximum width for the column */
+ size_t formatted_for_width; /* the width we tried to format for */
unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
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 */
+ const char *rgap_color; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
char *url; /* A URL to use for a clickable hyperlink */
char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
usec_t timespan;
uint64_t size;
char string[0];
+ char **strv;
int int_val;
int8_t int8;
int16_t int16;
int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
int ifindex;
union in_addr_union address;
+ sd_id128_t id128;
/* … add more here as we start supporting more cell data types … */
};
} TableData;
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;
va_start(ap, first_header);
for (;;) {
- h = va_arg(ap, const char*);
- if (!h)
+ if (!va_arg(ap, const char*))
break;
n_columns++;
return NULL;
va_start(ap, first_header);
- for (h = first_header; h; h = va_arg(ap, const char*)) {
+ for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
TableCell *cell;
r = table_add_cell(t, &cell, TABLE_STRING, h);
free(d->formatted);
free(d->url);
+ if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
+ strv_free(d->strv);
+
return mfree(d);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
Table *table_unref(Table *t) {
- size_t i;
-
if (!t)
return NULL;
- for (i = 0; i < t->n_cells; i++)
+ for (size_t i = 0; i < t->n_cells; i++)
table_data_unref(t->data[i]);
free(t->data);
case TABLE_PATH:
return strlen(data) + 1;
+ case TABLE_STRV:
+ case TABLE_STRV_WRAPPED:
+ return sizeof(char **);
+
case TABLE_BOOLEAN:
return sizeof(bool);
case TABLE_IN6_ADDR:
return sizeof(struct in6_addr);
+ case TABLE_UUID:
+ case TABLE_ID128:
+ return sizeof(sd_id128_t);
+
default:
assert_not_reached("Uh? Unexpected cell type");
}
return false;
/* If a color/url/uppercase flag is set, refuse to merge */
- if (d->color)
+ if (d->color || d->rgap_color)
return false;
if (d->url)
return false;
k = table_data_size(type, data);
l = table_data_size(d->type, d->data);
-
if (k != l)
return false;
unsigned align_percent,
unsigned ellipsize_percent) {
+ _cleanup_free_ TableData *d = NULL;
size_t data_size;
- TableData *d;
data_size = table_data_size(type, data);
d->weight = weight;
d->align_percent = align_percent;
d->ellipsize_percent = ellipsize_percent;
- memcpy_safe(d->data, data, data_size);
- return d;
+ if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
+ d->strv = strv_copy(data);
+ if (!d->strv)
+ return NULL;
+ } else
+ memcpy_safe(d->data, data, data_size);
+
+ return TAKE_PTR(d);
}
int table_add_cell_full(
return -ENOMEM;
nd->color = od->color;
+ nd->rgap_color = od->rgap_color;
nd->url = TAKE_PTR(curl);
nd->uppercase = od->uppercase;
return 0;
}
+int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
+ int r;
+
+ assert(t);
+ assert(cell);
+
+ r = table_dedup_cell(t, cell);
+ if (r < 0)
+ return r;
+
+ table_get_data(t, cell)->rgap_color = empty_to_null(color);
+ return 0;
+}
+
int table_set_url(Table *t, TableCell *cell, const char *url) {
_cleanup_free_ char *copy = NULL;
int r;
return -ENOMEM;
nd->color = od->color;
+ nd->rgap_color = od->rgap_color;
nd->url = TAKE_PTR(curl);
nd->uppercase = od->uppercase;
int ifindex;
bool b;
union in_addr_union address;
+ sd_id128_t id128;
} buffer;
switch (type) {
data = va_arg(ap, const char *);
break;
+ case TABLE_STRV:
+ case TABLE_STRV_WRAPPED:
+ data = va_arg(ap, char * const *);
+ break;
+
case TABLE_BOOLEAN:
buffer.b = va_arg(ap, int);
data = &buffer.b;
data = &buffer.address.in6;
break;
+ case TABLE_UUID:
+ case TABLE_ID128:
+ buffer.id128 = va_arg(ap, sd_id128_t);
+ data = &buffer.id128;
+ break;
+
case TABLE_SET_MINIMUM_WIDTH: {
size_t w = va_arg(ap, size_t);
break;
}
+ case TABLE_SET_RGAP_COLOR: {
+ const char *c = va_arg(ap, const char*);
+ r = table_set_rgap_color(t, last_cell, c);
+ break;
+ }
+
+ case TABLE_SET_BOTH_COLORS: {
+ const char *c = va_arg(ap, const char*);
+
+ r = table_set_color(t, last_cell, c);
+ if (r < 0) {
+ va_end(ap);
+ return r;
+ }
+
+ r = table_set_rgap_color(t, last_cell, c);
+ break;
+ }
+
case TABLE_SET_URL: {
const char *u = va_arg(ap, const char*);
r = table_set_url(t, last_cell, u);
return free_and_strdup(&t->empty_string, empty);
}
+int table_set_display_all(Table *t) {
+ assert(t);
+
+ size_t allocated = t->n_display_map;
+
+ if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, allocated)))
+ return -ENOMEM;
+
+ for (size_t i = 0; i < t->n_columns; i++)
+ t->display_map[i] = i;
+
+ t->n_display_map = t->n_columns;
+
+ return 0;
+}
+
int table_set_display(Table *t, size_t first_column, ...) {
size_t allocated, column;
va_list ap;
return 0;
}
+int table_hide_column_from_display(Table *t, size_t column) {
+ int r;
+
+ assert(t);
+ assert(column < t->n_columns);
+
+ /* If the display map is empty, initialize it with all available columns */
+ if (!t->display_map) {
+ r = table_set_display_all(t);
+ if (r < 0)
+ return r;
+ }
+
+ size_t allocated = t->n_display_map, cur = 0;
+
+ for (size_t i = 0; i < allocated; i++) {
+ if (t->display_map[i] == column)
+ continue;
+
+ t->display_map[cur++] = t->display_map[i];
+ }
+
+ t->n_display_map = cur;
+
+ return 0;
+}
+
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
assert(a);
assert(b);
case TABLE_PATH:
return path_compare(a->string, b->string);
+ case TABLE_STRV:
+ case TABLE_STRV_WRAPPED:
+ return strv_compare(a->strv, b->strv);
+
case TABLE_BOOLEAN:
if (!a->boolean && b->boolean)
return -1;
case TABLE_IN6_ADDR:
return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
+ case TABLE_UUID:
+ case TABLE_ID128:
+ return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
+
default:
;
}
}
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
- size_t i;
int r;
assert(t);
return 1;
/* Order other lines by the sorting map */
- for (i = 0; i < t->n_sort_map; i++) {
+ for (size_t i = 0; i < t->n_sort_map; i++) {
TableData *d, *dd;
d = t->data[*a + t->sort_map[i]];
return CMP(*a, *b);
}
-static const char *table_data_format(Table *t, TableData *d) {
+static char* format_strv_width(char **strv, size_t column_width) {
+ _cleanup_fclose_ FILE *f = NULL;
+ size_t sz = 0;
+ _cleanup_free_ char *buf = NULL;
+
+ f = open_memstream_unlocked(&buf, &sz);
+ if (!f)
+ return NULL;
+
+ size_t position = 0;
+ char **p;
+ STRV_FOREACH(p, strv) {
+ size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
+ * If that happens, we'll just print one item per line. */
+
+ if (position == 0) {
+ fputs(*p, f);
+ position = our_len;
+ } else if (size_add(size_add(position, 1), our_len) <= column_width) {
+ fprintf(f, " %s", *p);
+ position = size_add(size_add(position, 1), our_len);
+ } else {
+ fprintf(f, "\n%s", *p);
+ position = our_len;
+ }
+ }
+
+ if (fflush_and_check(f) < 0)
+ return NULL;
+
+ f = safe_fclose(f);
+ return TAKE_PTR(buf);
+}
+
+static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
assert(d);
- if (d->formatted)
+ if (d->formatted &&
+ /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
+ (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
return d->formatted;
switch (d->type) {
case TABLE_STRING:
case TABLE_PATH:
- if (d->uppercase) {
- char *p, *q;
-
+ if (d->uppercase && !avoid_uppercasing) {
d->formatted = new(char, strlen(d->string) + 1);
if (!d->formatted)
return NULL;
- for (p = d->string, q = d->formatted; *p; p++, q++)
+ char *q = d->formatted;
+ for (char *p = d->string; *p; p++, q++)
*q = (char) toupper((unsigned char) *p);
*q = 0;
return d->string;
+ case TABLE_STRV:
+ if (strv_isempty(d->strv))
+ return strempty(t->empty_string);
+
+ d->formatted = strv_join(d->strv, "\n");
+ if (!d->formatted)
+ return NULL;
+ break;
+
+ case TABLE_STRV_WRAPPED: {
+ if (strv_isempty(d->strv))
+ return strempty(t->empty_string);
+
+ char *buf = format_strv_width(d->strv, column_width);
+ if (!buf)
+ return NULL;
+
+ free_and_replace(d->formatted, buf);
+ d->formatted_for_width = column_width;
+ if (have_soft)
+ *have_soft = true;
+
+ break;
+ }
+
case TABLE_BOOLEAN:
return yes_no(d->boolean);
if (d->type == TABLE_TIMESTAMP)
ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
else if (d->type == TABLE_TIMESTAMP_UTC)
- ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
+ ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
else
ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
if (!ret)
break;
}
+ case TABLE_ID128: {
+ char *p;
+
+ p = new(char, SD_ID128_STRING_MAX);
+ if (!p)
+ return NULL;
+
+ d->formatted = sd_id128_to_string(d->id128, p);
+ break;
+ }
+
+ case TABLE_UUID: {
+ char *p;
+
+ p = new(char, ID128_UUID_STRING_MAX);
+ if (!p)
+ return NULL;
+
+ d->formatted = id128_to_uuid_string(d->id128, p);
+ break;
+ }
+
default:
assert_not_reached("Unexpected type?");
}
static int table_data_requested_width_height(
Table *table,
TableData *d,
+ size_t available_width,
size_t *ret_width,
- size_t *ret_height) {
+ size_t *ret_height,
+ bool *have_soft) {
_cleanup_free_ char *truncated = NULL;
bool truncation_applied = false;
size_t width, height;
const char *t;
int r;
+ bool soft = false;
- t = table_data_format(table, d);
+ t = table_data_format(table, d, false, available_width, &soft);
if (!t)
return -ENOMEM;
*ret_width = width;
if (ret_height)
*ret_height = height;
+ if (have_soft && soft)
+ *have_soft = true;
return truncation_applied;
}
_cleanup_free_ char *clickable = NULL;
const char *p;
char *ret;
- size_t i;
int r;
/* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
if (!ret)
return NULL;
- for (i = 0; i < lspace; i++)
+ for (size_t i = 0; i < lspace; i++)
ret[i] = ' ';
memcpy(ret + lspace, clickable ?: str, clickable_length);
- for (i = lspace + clickable_length; i < space + clickable_length; i++)
+ for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
ret[i] = ' ';
ret[space + clickable_length] = 0;
return ret;
}
+static bool table_data_isempty(TableData *d) {
+ assert(d);
+
+ if (d->type == TABLE_EMPTY)
+ return true;
+
+ /* Let's also consider an empty strv as truly empty. */
+ if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
+ return strv_isempty(d->strv);
+
+ /* Note that an empty string we do not consider empty here! */
+ return false;
+}
+
static const char* table_data_color(TableData *d) {
assert(d);
return d->color;
/* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
- if (d->type == TABLE_EMPTY)
+ if (table_data_isempty(d))
return ansi_grey();
return NULL;
}
+static const char* table_data_rgap_color(TableData *d) {
+ assert(d);
+
+ if (d->rgap_color)
+ return d->rgap_color;
+
+ return NULL;
+}
+
int table_print(Table *t, FILE *f) {
size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
- i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
- *width;
+ table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
+ *width = NULL;
_cleanup_free_ size_t *sorted = NULL;
uint64_t *column_weight, weight_sum;
int r;
if (!sorted)
return -ENOMEM;
- for (i = 0; i < n_rows; i++)
+ for (size_t i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
minimum_width = newa(size_t, display_columns);
maximum_width = newa(size_t, display_columns);
requested_width = newa(size_t, display_columns);
- width = newa(size_t, display_columns);
column_weight = newa0(uint64_t, display_columns);
- for (j = 0; j < display_columns; j++) {
+ for (size_t j = 0; j < display_columns; j++) {
minimum_width[j] = 1;
maximum_width[j] = (size_t) -1;
- requested_width[j] = (size_t) -1;
}
- /* First pass: determine column sizes */
- for (i = t->header ? 0 : 1; i < n_rows; i++) {
- TableData **row;
+ for (unsigned pass = 0; pass < 2; pass++) {
+ /* First pass: determine column sizes */
- /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
- * hence we don't care for sorted[] during the first pass. */
- row = t->data + i * t->n_columns;
+ for (size_t j = 0; j < display_columns; j++)
+ requested_width[j] = (size_t) -1;
- for (j = 0; j < display_columns; j++) {
- TableData *d;
- size_t req_width, req_height;
+ bool any_soft = false;
- assert_se(d = row[t->display_map ? t->display_map[j] : j]);
+ for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
+ TableData **row;
- r = table_data_requested_width_height(t, d, &req_width, &req_height);
- if (r < 0)
- return r;
- if (r > 0) { /* Truncated because too many lines? */
- _cleanup_free_ char *last = NULL;
- const char *field;
+ /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
+ * hence we don't care for sorted[] during the first pass. */
+ row = t->data + i * t->n_columns;
- /* If we are going to show only the first few lines of a cell that has
- * multiple make sure that we have enough space horizontally to show an
- * ellipsis. Hence, let's figure out the last line, and account for its
- * length plus ellipsis. */
+ for (size_t j = 0; j < display_columns; j++) {
+ TableData *d;
+ size_t req_width, req_height;
- field = table_data_format(t, d);
- if (!field)
- return -ENOMEM;
+ assert_se(d = row[t->display_map ? t->display_map[j] : j]);
- assert_se(t->cell_height_max > 0);
- r = string_extract_line(field, t->cell_height_max-1, &last);
+ r = table_data_requested_width_height(t, d,
+ width ? width[j] : SIZE_MAX,
+ &req_width, &req_height, &any_soft);
if (r < 0)
return r;
+ if (r > 0) { /* Truncated because too many lines? */
+ _cleanup_free_ char *last = NULL;
+ const char *field;
+
+ /* If we are going to show only the first few lines of a cell that has
+ * multiple make sure that we have enough space horizontally to show an
+ * ellipsis. Hence, let's figure out the last line, and account for its
+ * length plus ellipsis. */
+
+ field = table_data_format(t, d, false,
+ width ? width[j] : SIZE_MAX,
+ &any_soft);
+ if (!field)
+ return -ENOMEM;
- req_width = MAX(req_width,
- utf8_console_width(last) +
- utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
- }
+ assert_se(t->cell_height_max > 0);
+ r = string_extract_line(field, t->cell_height_max-1, &last);
+ if (r < 0)
+ return r;
+
+ req_width = MAX(req_width,
+ utf8_console_width(last) +
+ utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
+ }
- /* Determine the biggest width that any cell in this column would like to have */
- if (requested_width[j] == (size_t) -1 ||
- requested_width[j] < req_width)
- requested_width[j] = req_width;
+ /* Determine the biggest width that any cell in this column would like to have */
+ if (requested_width[j] == (size_t) -1 ||
+ requested_width[j] < req_width)
+ requested_width[j] = req_width;
- /* Determine the minimum width any cell in this column needs */
- if (minimum_width[j] < d->minimum_width)
- minimum_width[j] = d->minimum_width;
+ /* Determine the minimum width any cell in this column needs */
+ if (minimum_width[j] < d->minimum_width)
+ minimum_width[j] = d->minimum_width;
- /* Determine the maximum width any cell in this column needs */
- if (d->maximum_width != (size_t) -1 &&
- (maximum_width[j] == (size_t) -1 ||
- maximum_width[j] > d->maximum_width))
- maximum_width[j] = d->maximum_width;
+ /* Determine the maximum width any cell in this column needs */
+ if (d->maximum_width != (size_t) -1 &&
+ (maximum_width[j] == (size_t) -1 ||
+ maximum_width[j] > d->maximum_width))
+ maximum_width[j] = d->maximum_width;
- /* Determine the full columns weight */
- column_weight[j] += d->weight;
+ /* Determine the full columns weight */
+ column_weight[j] += d->weight;
+ }
}
- }
- /* One space between each column */
- table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
+ /* One space between each column */
+ table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
+
+ /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
+ weight_sum = 0;
+ for (size_t j = 0; j < display_columns; j++) {
+ weight_sum += column_weight[j];
- /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
- weight_sum = 0;
- for (j = 0; j < display_columns; j++) {
- weight_sum += column_weight[j];
+ table_minimum_width += minimum_width[j];
- table_minimum_width += minimum_width[j];
+ if (maximum_width[j] == (size_t) -1)
+ table_maximum_width = (size_t) -1;
+ else
+ table_maximum_width += maximum_width[j];
+
+ table_requested_width += requested_width[j];
+ }
- if (maximum_width[j] == (size_t) -1)
- table_maximum_width = (size_t) -1;
+ /* Calculate effective table width */
+ if (t->width != 0 && t->width != (size_t) -1)
+ table_effective_width = t->width;
+ else if (t->width == 0 ||
+ ((pass > 0 || !any_soft) && (pager_have() || !isatty(STDOUT_FILENO))))
+ table_effective_width = table_requested_width;
else
- table_maximum_width += maximum_width[j];
+ table_effective_width = MIN(table_requested_width, columns());
- table_requested_width += requested_width[j];
- }
+ if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
+ table_effective_width = table_maximum_width;
- /* Calculate effective table width */
- if (t->width != 0 && t->width != (size_t) -1)
- table_effective_width = t->width;
- else if (t->width == 0 || pager_have() || !isatty(STDOUT_FILENO))
- table_effective_width = table_requested_width;
- else
- table_effective_width = MIN(table_requested_width, columns());
+ if (table_effective_width < table_minimum_width)
+ table_effective_width = table_minimum_width;
- if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
- table_effective_width = table_maximum_width;
+ if (!width)
+ width = newa(size_t, display_columns);
- if (table_effective_width < table_minimum_width)
- table_effective_width = table_minimum_width;
+ if (table_effective_width >= table_requested_width) {
+ size_t extra;
- if (table_effective_width >= table_requested_width) {
- size_t extra;
+ /* We have extra room, let's distribute it among columns according to their weights. We first provide
+ * each column with what it asked for and the distribute the rest. */
- /* We have extra room, let's distribute it among columns according to their weights. We first provide
- * each column with what it asked for and the distribute the rest. */
+ extra = table_effective_width - table_requested_width;
- extra = table_effective_width - table_requested_width;
+ for (size_t j = 0; j < display_columns; j++) {
+ size_t delta;
- for (j = 0; j < display_columns; j++) {
- size_t delta;
+ if (weight_sum == 0)
+ width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
+ else
+ width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
- if (weight_sum == 0)
- width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
- else
- width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
+ if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
+ width[j] = maximum_width[j];
- if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
- width[j] = maximum_width[j];
+ if (width[j] < minimum_width[j])
+ width[j] = minimum_width[j];
- if (width[j] < minimum_width[j])
- width[j] = minimum_width[j];
+ assert(width[j] >= requested_width[j]);
+ delta = width[j] - requested_width[j];
- assert(width[j] >= requested_width[j]);
- delta = width[j] - requested_width[j];
+ /* Subtract what we just added from the rest */
+ if (extra > delta)
+ extra -= delta;
+ else
+ extra = 0;
- /* Subtract what we just added from the rest */
- if (extra > delta)
- extra -= delta;
- else
- extra = 0;
+ assert(weight_sum >= column_weight[j]);
+ weight_sum -= column_weight[j];
+ }
- assert(weight_sum >= column_weight[j]);
- weight_sum -= column_weight[j];
- }
+ break; /* Every column should be happy, no need to repeat calculations. */
+ } else {
+ /* We need to compress the table, columns can't get what they asked for. We first provide each column
+ * with the minimum they need, and then distribute anything left. */
+ bool finalize = false;
+ size_t extra;
- } else {
- /* We need to compress the table, columns can't get what they asked for. We first provide each column
- * with the minimum they need, and then distribute anything left. */
- bool finalize = false;
- size_t extra;
+ extra = table_effective_width - table_minimum_width;
- extra = table_effective_width - table_minimum_width;
+ for (size_t j = 0; j < display_columns; j++)
+ width[j] = (size_t) -1;
- for (j = 0; j < display_columns; j++)
- width[j] = (size_t) -1;
+ for (;;) {
+ bool restart = false;
- for (;;) {
- bool restart = false;
+ for (size_t j = 0; j < display_columns; j++) {
+ size_t delta, w;
- for (j = 0; j < display_columns; j++) {
- size_t delta, w;
+ /* Did this column already get something assigned? If so, let's skip to the next */
+ if (width[j] != (size_t) -1)
+ continue;
- /* Did this column already get something assigned? If so, let's skip to the next */
- if (width[j] != (size_t) -1)
- continue;
+ if (weight_sum == 0)
+ w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
+ else
+ w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
- if (weight_sum == 0)
- w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
- else
- w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
+ if (w >= requested_width[j]) {
+ /* Never give more than requested. If we hit a column like this, there's more
+ * space to allocate to other columns which means we need to restart the
+ * iteration. However, if we hit a column like this, let's assign it the space
+ * it wanted for good early.*/
- if (w >= requested_width[j]) {
- /* Never give more than requested. If we hit a column like this, there's more
- * space to allocate to other columns which means we need to restart the
- * iteration. However, if we hit a column like this, let's assign it the space
- * it wanted for good early.*/
+ w = requested_width[j];
+ restart = true;
- w = requested_width[j];
- restart = true;
+ } else if (!finalize)
+ continue;
- } else if (!finalize)
- continue;
+ width[j] = w;
- width[j] = w;
+ assert(w >= minimum_width[j]);
+ delta = w - minimum_width[j];
- assert(w >= minimum_width[j]);
- delta = w - minimum_width[j];
+ assert(delta <= extra);
+ extra -= delta;
- assert(delta <= extra);
- extra -= delta;
+ assert(weight_sum >= column_weight[j]);
+ weight_sum -= column_weight[j];
- assert(weight_sum >= column_weight[j]);
- weight_sum -= column_weight[j];
+ if (restart && !finalize)
+ break;
+ }
- if (restart && !finalize)
+ if (finalize)
break;
+
+ if (!restart)
+ finalize = true;
}
- if (finalize)
+ if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
+ * let's try to reformat them with the new widths. Otherwise, let's
+ * move on. */
break;
-
- if (!restart)
- finalize = true;
}
}
/* Second pass: show output */
- for (i = t->header ? 0 : 1; i < n_rows; i++) {
+ for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
size_t n_subline = 0;
bool more_sublines;
TableData **row;
row = t->data + i * t->n_columns;
do {
+ const char *gap_color = NULL;
more_sublines = false;
- for (j = 0; j < display_columns; j++) {
+ for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *buffer = NULL, *extracted = NULL;
bool lines_truncated = false;
- const char *field;
+ const char *field, *color = NULL;
TableData *d;
size_t l;
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
- field = table_data_format(t, d);
+ field = table_data_format(t, d, false, width[j], NULL);
if (!field)
return -ENOMEM;
_cleanup_free_ char *padded = NULL;
/* We truncated more lines of this cell, let's add an
- * ellipsis. We first append it, but thta might make our
+ * ellipsis. We first append it, but that might make our
* string grow above what we have space for, hence ellipsize
* right after. This will truncate the ellipsis and add a new
* one. */
field = buffer;
}
- if (row == t->data) /* underline header line fully, including the column separator */
- fputs(ansi_underline(), f);
+ if (colors_enabled()) {
+ if (gap_color)
+ fputs(gap_color, f);
+ else if (row == t->data) /* underline header line fully, including the column separator */
+ fputs(ansi_underline(), f);
+ }
if (j > 0)
- fputc(' ', f); /* column separator */
+ fputc(' ', f); /* column separator left of cell */
- if (table_data_color(d) && colors_enabled()) {
- if (row == t->data) /* first undo header underliner */
+ if (colors_enabled()) {
+ color = table_data_color(d);
+
+ /* Undo gap color */
+ if (gap_color || (color && row == t->data))
fputs(ANSI_NORMAL, f);
- fputs(table_data_color(d), f);
+ if (color)
+ fputs(color, f);
+ else if (gap_color && row == t->data) /* underline header line cell */
+ fputs(ansi_underline(), f);
}
fputs(field, f);
- if (colors_enabled() && (table_data_color(d) || row == t->data))
+ if (colors_enabled() && (color || row == t->data))
fputs(ANSI_NORMAL, f);
+
+ gap_color = table_data_rgap_color(d);
}
fputc('\n', f);
case TABLE_PATH:
return json_variant_new_string(ret, d->string);
+ case TABLE_STRV:
+ case TABLE_STRV_WRAPPED:
+ return json_variant_new_array_strv(ret, d->strv);
+
case TABLE_BOOLEAN:
return json_variant_new_boolean(ret, d->boolean);
case TABLE_SIZE:
case TABLE_BPS:
- if (d->size == (size_t) -1)
+ if (d->size == (uint64_t) -1)
return json_variant_new_null(ret);
return json_variant_new_unsigned(ret, d->size);
case TABLE_IN6_ADDR:
return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
+ case TABLE_ID128: {
+ char buf[SD_ID128_STRING_MAX];
+ return json_variant_new_string(ret, sd_id128_to_string(d->id128, buf));
+ }
+
+ case TABLE_UUID: {
+ char buf[ID128_UUID_STRING_MAX];
+ return json_variant_new_string(ret, id128_to_uuid_string(d->id128, buf));
+ }
+
default:
return -EINVAL;
}
}
+static char* string_to_json_field_name(const char *f) {
+ /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
+ * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
+ * underscores and leave everything as is. */
+
+ char *c = strdup(f);
+ if (!c)
+ return NULL;
+
+ for (char *x = c; *x; x++)
+ if (isspace(*x))
+ *x = '_';
+
+ return c;
+}
+
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;
+ size_t n_rows, display_columns;
int r;
assert(t);
goto finish;
}
- for (i = 0; i < n_rows; i++)
+ for (size_t i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
goto finish;
}
- for (j = 0; j < display_columns; j++) {
+ for (size_t j = 0; j < display_columns; j++) {
+ _cleanup_free_ char *mangled = NULL;
+ const char *formatted;
TableData *d;
assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
- r = table_data_to_json(d, elements + j*2);
+ /* 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;
+ }
+
+ r = json_variant_new_string(elements + j*2, mangled);
if (r < 0)
goto finish;
}
goto finish;
}
- for (i = 1; i < n_rows; i++) {
+ for (size_t i = 1; i < n_rows; i++) {
TableData **row;
if (sorted)
else
row = t->data + i * t->n_columns;
- for (j = 0; j < display_columns; j++) {
+ for (size_t j = 0; j < display_columns; j++) {
TableData *d;
size_t k;