1
0
mirror of https://github.com/systemd/systemd.git synced 2025-08-25 13:49:55 +03:00

format-table: add TABLE_STRV_WRAPPED

The idea is that we have strvs like list of server names or addresses, where
the majority of strings is rather short, but some are long and there can
potentially be many strings. So formattting them either all on one line or all
in separate lines leads to output that is either hard to read or uses way too
many rows. We want to wrap them, but relying on the pager to do the wrapping is
not nice. Normal text has a lot of redundancy, so when the pager wraps a line
in the middle of a word the read can understand what is going on without any
trouble. But for a high-density zero-redundancy text like an IP address it is
much nicer to wrap between words. This also makes c&p easier.

This adds a variant of TABLE_STRV which is wrapped on output (with line breaks
inserted between different strv entries).

The change table_print() is quite ugly. A second pass is added to re-calculate
column widths. Since column size is now "soft", i.e. it can adjust based on
available columns, we need to two passes:
- first we figure out how much space we want
- in the second pass we figure out what the actual wrapped columns
  widths will be.

To avoid unnessary work, the second pass is only done when we actually have
wrappable fields.

A test is added in test-format-table.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2020-10-12 13:29:46 +02:00
parent 6f8ca84c9b
commit b0e3d79989
5 changed files with 378 additions and 183 deletions

View File

@ -634,6 +634,8 @@ static inline int __coverity_check_and_return__(int condition) {
_copy; \
})
#define SIZE_ADD(x, y) ((x) >= SIZE_MAX - (y) ? SIZE_MAX : (x) + (y))
static inline size_t size_add(size_t x, size_t y) {
return y >= SIZE_MAX - x ? SIZE_MAX : x + y;
}
#include "log.h"

View File

@ -1320,12 +1320,12 @@ static int status_print_strv_ifindex(int ifindex, const char *ifname, char **p)
size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen).
* If that happens, we'll just print one item per line. */
if (position <= indent || SIZE_ADD(SIZE_ADD(position, 1), our_len) < cols) {
if (position <= indent || size_add(size_add(position, 1), our_len) < cols) {
printf(" %s", *i);
position = SIZE_ADD(SIZE_ADD(position, 1), our_len);
position = size_add(size_add(position, 1), our_len);
} else {
printf("\n%*s%s", indent, "", *i);
position = SIZE_ADD(our_len, indent);
position = size_add(our_len, indent);
}
}

View File

@ -66,6 +66,7 @@ typedef struct TableData {
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 */
@ -211,7 +212,7 @@ static TableData *table_data_free(TableData *d) {
free(d->formatted);
free(d->url);
if (d->type == TABLE_STRV)
if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
strv_free(d->strv);
return mfree(d);
@ -248,6 +249,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
return strlen(data) + 1;
case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return sizeof(char **);
case TABLE_BOOLEAN:
@ -372,7 +374,7 @@ static TableData *table_data_new(
d->align_percent = align_percent;
d->ellipsize_percent = ellipsize_percent;
if (type == TABLE_STRV) {
if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
d->strv = strv_copy(data);
if (!d->strv)
return NULL;
@ -813,6 +815,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break;
case TABLE_STRV:
case TABLE_STRV_WRAPPED:
data = va_arg(ap, char * const *);
break;
@ -1162,6 +1165,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
return path_compare(a->string, b->string);
case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return strv_compare(a->strv, b->strv);
case TABLE_BOOLEAN:
@ -1269,10 +1273,46 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
return CMP(*a, *b);
}
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing) {
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) {
@ -1305,6 +1345,22 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
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);
@ -1618,16 +1674,19 @@ static int console_width_height(
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, false);
t = table_data_format(table, d, false, available_width, &soft);
if (!t)
return -ENOMEM;
@ -1655,6 +1714,8 @@ static int table_data_requested_width_height(
*ret_width = width;
if (ret_height)
*ret_height = height;
if (have_soft && soft)
*have_soft = true;
return truncation_applied;
}
@ -1725,7 +1786,7 @@ static bool table_data_isempty(TableData *d) {
return true;
/* Let's also consider an empty strv as truly empty. */
if (d->type == TABLE_STRV)
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! */
@ -1757,7 +1818,7 @@ static const char* table_data_rgap_color(TableData *d) {
int table_print(Table *t, FILE *f) {
size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
*width;
*width = NULL;
_cleanup_free_ size_t *sorted = NULL;
uint64_t *column_weight, weight_sum;
int r;
@ -1796,200 +1857,220 @@ int table_print(Table *t, FILE *f) {
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 (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 (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
TableData **row;
/* 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++) {
TableData *d;
size_t req_width, req_height;
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
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;
/* 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);
if (!field)
return -ENOMEM;
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 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 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;
/* 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];
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];
}
/* 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_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
table_effective_width = table_maximum_width;
if (table_effective_width < table_minimum_width)
table_effective_width = table_minimum_width;
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. */
extra = table_effective_width - table_requested_width;
for (size_t 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 (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];
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;
assert(weight_sum >= column_weight[j]);
weight_sum -= column_weight[j];
}
} 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;
for (unsigned pass = 0; pass < 2; pass++) {
/* First pass: determine column sizes */
for (size_t j = 0; j < display_columns; j++)
width[j] = (size_t) -1;
requested_width[j] = (size_t) -1;
for (;;) {
bool restart = false;
bool any_soft = false;
for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
TableData **row;
/* 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++) {
size_t delta, w;
TableData *d;
size_t req_width, req_height;
/* Did this column already get something assigned? If so, let's skip to the next */
if (width[j] != (size_t) -1)
continue;
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
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;
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 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 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;
/* 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];
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];
}
/* 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_effective_width = MIN(table_requested_width, columns());
if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
table_effective_width = table_maximum_width;
if (table_effective_width < table_minimum_width)
table_effective_width = table_minimum_width;
if (!width)
width = newa(size_t, display_columns);
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. */
extra = table_effective_width - table_requested_width;
for (size_t j = 0; j < display_columns; j++) {
size_t delta;
if (weight_sum == 0)
w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
else
w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
width[j] = requested_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 (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
width[j] = maximum_width[j];
w = requested_width[j];
restart = true;
if (width[j] < minimum_width[j])
width[j] = minimum_width[j];
} else if (!finalize)
continue;
assert(width[j] >= requested_width[j]);
delta = width[j] - requested_width[j];
width[j] = w;
assert(w >= minimum_width[j]);
delta = w - minimum_width[j];
assert(delta <= extra);
extra -= delta;
/* 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];
if (restart && !finalize)
break;
}
if (finalize)
break;
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;
if (!restart)
finalize = true;
extra = table_effective_width - table_minimum_width;
for (size_t j = 0; j < display_columns; j++)
width[j] = (size_t) -1;
for (;;) {
bool restart = false;
for (size_t 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;
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.*/
w = requested_width[j];
restart = true;
} else if (!finalize)
continue;
width[j] = w;
assert(w >= minimum_width[j]);
delta = w - minimum_width[j];
assert(delta <= extra);
extra -= delta;
assert(weight_sum >= column_weight[j]);
weight_sum -= column_weight[j];
if (restart && !finalize)
break;
}
if (finalize)
break;
if (!restart)
finalize = true;
}
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;
}
}
@ -2017,7 +2098,7 @@ int table_print(Table *t, FILE *f) {
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
field = table_data_format(t, d, false);
field = table_data_format(t, d, false, width[j], NULL);
if (!field)
return -ENOMEM;
@ -2232,6 +2313,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
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:
@ -2381,7 +2463,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
assert_se(d = t->data[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);
formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
if (!formatted) {
r = -ENOMEM;
goto finish;

View File

@ -12,6 +12,7 @@ typedef enum TableDataType {
TABLE_EMPTY,
TABLE_STRING,
TABLE_STRV,
TABLE_STRV_WRAPPED,
TABLE_PATH,
TABLE_BOOLEAN,
TABLE_TIMESTAMP,

View File

@ -12,6 +12,8 @@ static void test_issue_9549(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0);
assert_se(table_add_many(table,
@ -36,6 +38,8 @@ static void test_multiline(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
@ -148,6 +152,8 @@ static void test_strv(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
@ -256,8 +262,111 @@ static void test_strv(void) {
formatted = mfree(formatted);
}
int main(int argc, char *argv[]) {
static void test_strv_wrapped(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
assert_se(table_add_many(table,
TABLE_STRV_WRAPPED, STRV_MAKE("three", "different", "lines"),
TABLE_STRV_WRAPPED, STRV_MAKE("two", "lines")) >= 0);
table_set_cell_height_max(table, 1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 2);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 3);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, (size_t) -1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
assert_se(table_add_many(table,
TABLE_STRING, "short",
TABLE_STRV_WRAPPED, STRV_MAKE("a", "pair")) >= 0);
assert_se(table_add_many(table,
TABLE_STRV_WRAPPED, STRV_MAKE("short2"),
TABLE_STRV_WRAPPED, STRV_MAKE("a", "eight", "line", "ćęłł",
"___5___", "___6___", "___7___", "___8___")) >= 0);
table_set_cell_height_max(table, 1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different… two lines\n"
"short a pair\n"
"short2 a eight line ćęłł…\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 2);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___…\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 3);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___\n"
" ___7___ ___8___\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, (size_t) -1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___\n"
" ___7___ ___8___\n"));
formatted = mfree(formatted);
}
int main(int argc, char *argv[]) {
_cleanup_(table_unrefp) Table *t = NULL;
_cleanup_free_ char *formatted = NULL;
@ -399,6 +508,7 @@ int main(int argc, char *argv[]) {
test_issue_9549();
test_multiline();
test_strv();
test_strv_wrapped();
return 0;
}