1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "alloc-util.h"
12 #include "format-table.h"
13 #include "format-util.h"
15 #include "id128-util.h"
16 #include "in-addr-util.h"
17 #include "locale-util.h"
18 #include "memory-util.h"
20 #include "parse-util.h"
21 #include "path-util.h"
22 #include "pretty-print.h"
23 #include "sort-util.h"
24 #include "string-util.h"
26 #include "terminal-util.h"
27 #include "time-util.h"
31 #define DEFAULT_WEIGHT 100
34 A few notes on implementation details:
36 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
37 table. It can be easily converted to an index number and back.
39 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
40 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
41 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
42 outside only sees Table and TableCell.
44 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
47 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
48 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
49 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
50 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
52 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
53 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
54 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
55 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
58 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
59 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
60 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
63 typedef struct TableData
{
67 size_t minimum_width
; /* minimum width for the column */
68 size_t maximum_width
; /* maximum width for the column */
69 size_t formatted_for_width
; /* the width we tried to format for */
70 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
71 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
72 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
74 bool uppercase
; /* Uppercase string on display */
76 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 */
77 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 */
78 char *url
; /* A URL to use for a clickable hyperlink */
79 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
82 uint8_t data
[0]; /* data is generic array */
99 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
101 union in_addr_union address
;
103 /* … add more here as we start supporting more cell data types … */
107 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
112 i
= PTR_TO_SIZE(cell
);
118 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
119 assert(index
!= (size_t) -1);
120 return SIZE_TO_PTR(index
+ 1);
127 bool header
; /* Whether to show the header row? */
128 size_t width
; /* If == 0 format this as wide as necessary. If (size_t) -1 format this to console
129 * width or less wide, but not wider. Otherwise the width to format this table in. */
130 size_t cell_height_max
; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If (size_t) -1 then no limit is set, the default. == 0 is not allowed.) */
135 size_t *display_map
; /* List of columns to show (by their index). It's fine if columns are listed multiple times or not at all */
136 size_t n_display_map
;
138 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
146 Table
*table_new_raw(size_t n_columns
) {
147 _cleanup_(table_unrefp
) Table
*t
= NULL
;
149 assert(n_columns
> 0);
155 *t
= (struct Table
) {
156 .n_columns
= n_columns
,
158 .width
= (size_t) -1,
159 .cell_height_max
= (size_t) -1,
165 Table
*table_new_internal(const char *first_header
, ...) {
166 _cleanup_(table_unrefp
) Table
*t
= NULL
;
167 size_t n_columns
= 1;
171 assert(first_header
);
173 va_start(ap
, first_header
);
175 if (!va_arg(ap
, const char*))
182 t
= table_new_raw(n_columns
);
186 va_start(ap
, first_header
);
187 for (const char *h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
190 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
196 /* Make the table header uppercase */
197 r
= table_set_uppercase(t
, cell
, true);
205 assert(t
->n_columns
== t
->n_cells
);
209 static TableData
*table_data_free(TableData
*d
) {
215 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
221 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
222 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
224 Table
*table_unref(Table
*t
) {
228 for (size_t i
= 0; i
< t
->n_cells
; i
++)
229 table_data_unref(t
->data
[i
]);
232 free(t
->display_map
);
234 free(t
->reverse_map
);
235 free(t
->empty_string
);
240 static size_t table_data_size(TableDataType type
, const void *data
) {
249 return strlen(data
) + 1;
252 case TABLE_STRV_WRAPPED
:
253 return sizeof(char **);
258 case TABLE_TIMESTAMP
:
259 case TABLE_TIMESTAMP_UTC
:
260 case TABLE_TIMESTAMP_RELATIVE
:
262 case TABLE_TIMESPAN_MSEC
:
263 return sizeof(usec_t
);
269 return sizeof(uint64_t);
273 return sizeof(uint32_t);
277 return sizeof(uint16_t);
281 return sizeof(uint8_t);
290 return sizeof(struct in_addr
);
293 return sizeof(struct in6_addr
);
297 return sizeof(sd_id128_t
);
300 assert_not_reached("Uh? Unexpected cell type");
304 static bool table_data_matches(
308 size_t minimum_width
,
309 size_t maximum_width
,
311 unsigned align_percent
,
312 unsigned ellipsize_percent
) {
320 if (d
->minimum_width
!= minimum_width
)
323 if (d
->maximum_width
!= maximum_width
)
326 if (d
->weight
!= weight
)
329 if (d
->align_percent
!= align_percent
)
332 if (d
->ellipsize_percent
!= ellipsize_percent
)
335 /* If a color/url/uppercase flag is set, refuse to merge */
336 if (d
->color
|| d
->rgap_color
)
343 k
= table_data_size(type
, data
);
344 l
= table_data_size(d
->type
, d
->data
);
348 return memcmp_safe(data
, d
->data
, l
) == 0;
351 static TableData
*table_data_new(
354 size_t minimum_width
,
355 size_t maximum_width
,
357 unsigned align_percent
,
358 unsigned ellipsize_percent
) {
360 _cleanup_free_ TableData
*d
= NULL
;
363 data_size
= table_data_size(type
, data
);
365 d
= malloc0(offsetof(TableData
, data
) + data_size
);
371 d
->minimum_width
= minimum_width
;
372 d
->maximum_width
= maximum_width
;
374 d
->align_percent
= align_percent
;
375 d
->ellipsize_percent
= ellipsize_percent
;
377 if (IN_SET(type
, TABLE_STRV
, TABLE_STRV_WRAPPED
)) {
378 d
->strv
= strv_copy(data
);
382 memcpy_safe(d
->data
, data
, data_size
);
387 int table_add_cell_full(
389 TableCell
**ret_cell
,
392 size_t minimum_width
,
393 size_t maximum_width
,
395 unsigned align_percent
,
396 unsigned ellipsize_percent
) {
398 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
403 assert(type
< _TABLE_DATA_TYPE_MAX
);
405 /* Special rule: patch NULL data fields to the empty field */
409 /* Determine the cell adjacent to the current one, but one row up */
410 if (t
->n_cells
>= t
->n_columns
)
411 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
415 /* If formatting parameters are left unspecified, copy from the previous row */
416 if (minimum_width
== (size_t) -1)
417 minimum_width
= p
? p
->minimum_width
: 1;
419 if (weight
== (unsigned) -1)
420 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
422 if (align_percent
== (unsigned) -1)
423 align_percent
= p
? p
->align_percent
: 0;
425 if (ellipsize_percent
== (unsigned) -1)
426 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
428 assert(align_percent
<= 100);
429 assert(ellipsize_percent
<= 100);
431 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
432 * formatting. Let's see if we can reuse the cell data and ref it once more. */
434 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
435 d
= table_data_ref(p
);
437 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
442 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
446 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
448 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
453 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
454 _cleanup_free_
char *buffer
= NULL
;
458 va_start(ap
, format
);
459 r
= vasprintf(&buffer
, format
, ap
);
464 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
467 int table_fill_empty(Table
*t
, size_t until_column
) {
472 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
473 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
475 if (until_column
>= t
->n_columns
)
479 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
483 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
488 int table_dup_cell(Table
*t
, TableCell
*cell
) {
493 /* Add the data of the specified cell a second time as a new cell to the end. */
495 i
= TABLE_CELL_TO_INDEX(cell
);
499 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
502 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
506 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
507 _cleanup_free_
char *curl
= NULL
;
513 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
514 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
516 i
= TABLE_CELL_TO_INDEX(cell
);
520 assert_se(od
= t
->data
[i
]);
524 assert(od
->n_ref
> 1);
527 curl
= strdup(od
->url
);
539 od
->ellipsize_percent
);
543 nd
->color
= od
->color
;
544 nd
->rgap_color
= od
->rgap_color
;
545 nd
->url
= TAKE_PTR(curl
);
546 nd
->uppercase
= od
->uppercase
;
548 table_data_unref(od
);
551 assert(nd
->n_ref
== 1);
556 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
562 /* Get the data object of the specified cell, or NULL if it doesn't exist */
564 i
= TABLE_CELL_TO_INDEX(cell
);
569 assert(t
->data
[i
]->n_ref
> 0);
574 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
580 if (minimum_width
== (size_t) -1)
583 r
= table_dedup_cell(t
, cell
);
587 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
591 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
597 r
= table_dedup_cell(t
, cell
);
601 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
605 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
611 if (weight
== (unsigned) -1)
612 weight
= DEFAULT_WEIGHT
;
614 r
= table_dedup_cell(t
, cell
);
618 table_get_data(t
, cell
)->weight
= weight
;
622 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
628 if (percent
== (unsigned) -1)
631 assert(percent
<= 100);
633 r
= table_dedup_cell(t
, cell
);
637 table_get_data(t
, cell
)->align_percent
= percent
;
641 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
647 if (percent
== (unsigned) -1)
650 assert(percent
<= 100);
652 r
= table_dedup_cell(t
, cell
);
656 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
660 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
666 r
= table_dedup_cell(t
, cell
);
670 table_get_data(t
, cell
)->color
= empty_to_null(color
);
674 int table_set_rgap_color(Table
*t
, TableCell
*cell
, const char *color
) {
680 r
= table_dedup_cell(t
, cell
);
684 table_get_data(t
, cell
)->rgap_color
= empty_to_null(color
);
688 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
689 _cleanup_free_
char *copy
= NULL
;
701 r
= table_dedup_cell(t
, cell
);
705 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
708 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
715 r
= table_dedup_cell(t
, cell
);
719 assert_se(d
= table_get_data(t
, cell
));
721 if (d
->uppercase
== b
)
724 d
->formatted
= mfree(d
->formatted
);
729 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
730 _cleanup_free_
char *curl
= NULL
;
737 i
= TABLE_CELL_TO_INDEX(cell
);
741 assert_se(od
= t
->data
[i
]);
744 curl
= strdup(od
->url
);
756 od
->ellipsize_percent
);
760 nd
->color
= od
->color
;
761 nd
->rgap_color
= od
->rgap_color
;
762 nd
->url
= TAKE_PTR(curl
);
763 nd
->uppercase
= od
->uppercase
;
765 table_data_unref(od
);
771 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
774 TableCell
*last_cell
= NULL
;
778 assert(first_type
>= 0);
779 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
783 va_start(ap
, first_type
);
802 union in_addr_union address
;
814 data
= va_arg(ap
, const char *);
818 case TABLE_STRV_WRAPPED
:
819 data
= va_arg(ap
, char * const *);
823 buffer
.b
= va_arg(ap
, int);
827 case TABLE_TIMESTAMP
:
828 case TABLE_TIMESTAMP_UTC
:
829 case TABLE_TIMESTAMP_RELATIVE
:
831 case TABLE_TIMESPAN_MSEC
:
832 buffer
.usec
= va_arg(ap
, usec_t
);
838 buffer
.size
= va_arg(ap
, uint64_t);
843 buffer
.int_val
= va_arg(ap
, int);
844 data
= &buffer
.int_val
;
848 int x
= va_arg(ap
, int);
849 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
857 int x
= va_arg(ap
, int);
858 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
861 data
= &buffer
.int16
;
866 buffer
.int32
= va_arg(ap
, int32_t);
867 data
= &buffer
.int32
;
871 buffer
.int64
= va_arg(ap
, int64_t);
872 data
= &buffer
.int64
;
876 buffer
.uint_val
= va_arg(ap
, unsigned);
877 data
= &buffer
.uint_val
;
881 unsigned x
= va_arg(ap
, unsigned);
882 assert(x
<= UINT8_MAX
);
885 data
= &buffer
.uint8
;
890 unsigned x
= va_arg(ap
, unsigned);
891 assert(x
<= UINT16_MAX
);
894 data
= &buffer
.uint16
;
899 buffer
.uint32
= va_arg(ap
, uint32_t);
900 data
= &buffer
.uint32
;
904 buffer
.uint64
= va_arg(ap
, uint64_t);
905 data
= &buffer
.uint64
;
909 buffer
.percent
= va_arg(ap
, int);
910 data
= &buffer
.percent
;
914 buffer
.ifindex
= va_arg(ap
, int);
915 data
= &buffer
.ifindex
;
919 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
920 data
= &buffer
.address
.in
;
924 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
925 data
= &buffer
.address
.in6
;
930 buffer
.id128
= va_arg(ap
, sd_id128_t
);
931 data
= &buffer
.id128
;
934 case TABLE_SET_MINIMUM_WIDTH
: {
935 size_t w
= va_arg(ap
, size_t);
937 r
= table_set_minimum_width(t
, last_cell
, w
);
941 case TABLE_SET_MAXIMUM_WIDTH
: {
942 size_t w
= va_arg(ap
, size_t);
943 r
= table_set_maximum_width(t
, last_cell
, w
);
947 case TABLE_SET_WEIGHT
: {
948 unsigned w
= va_arg(ap
, unsigned);
949 r
= table_set_weight(t
, last_cell
, w
);
953 case TABLE_SET_ALIGN_PERCENT
: {
954 unsigned p
= va_arg(ap
, unsigned);
955 r
= table_set_align_percent(t
, last_cell
, p
);
959 case TABLE_SET_ELLIPSIZE_PERCENT
: {
960 unsigned p
= va_arg(ap
, unsigned);
961 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
965 case TABLE_SET_COLOR
: {
966 const char *c
= va_arg(ap
, const char*);
967 r
= table_set_color(t
, last_cell
, c
);
971 case TABLE_SET_RGAP_COLOR
: {
972 const char *c
= va_arg(ap
, const char*);
973 r
= table_set_rgap_color(t
, last_cell
, c
);
977 case TABLE_SET_BOTH_COLORS
: {
978 const char *c
= va_arg(ap
, const char*);
980 r
= table_set_color(t
, last_cell
, c
);
986 r
= table_set_rgap_color(t
, last_cell
, c
);
990 case TABLE_SET_URL
: {
991 const char *u
= va_arg(ap
, const char*);
992 r
= table_set_url(t
, last_cell
, u
);
996 case TABLE_SET_UPPERCASE
: {
997 int u
= va_arg(ap
, int);
998 r
= table_set_uppercase(t
, last_cell
, u
);
1002 case _TABLE_DATA_TYPE_MAX
:
1003 /* Used as end marker */
1008 assert_not_reached("Uh? Unexpected data type.");
1011 if (type
< _TABLE_DATA_TYPE_MAX
)
1012 r
= table_add_cell(t
, &last_cell
, type
, data
);
1019 type
= va_arg(ap
, TableDataType
);
1023 void table_set_header(Table
*t
, bool b
) {
1029 void table_set_width(Table
*t
, size_t width
) {
1035 void table_set_cell_height_max(Table
*t
, size_t height
) {
1037 assert(height
>= 1 || height
== (size_t) -1);
1039 t
->cell_height_max
= height
;
1042 int table_set_empty_string(Table
*t
, const char *empty
) {
1045 return free_and_strdup(&t
->empty_string
, empty
);
1048 int table_set_display_all(Table
*t
) {
1051 size_t allocated
= t
->n_display_map
;
1053 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, allocated
)))
1056 for (size_t i
= 0; i
< t
->n_columns
; i
++)
1057 t
->display_map
[i
] = i
;
1059 t
->n_display_map
= t
->n_columns
;
1064 int table_set_display(Table
*t
, size_t first_column
, ...) {
1065 size_t allocated
, column
;
1070 allocated
= t
->n_display_map
;
1071 column
= first_column
;
1073 va_start(ap
, first_column
);
1075 assert(column
< t
->n_columns
);
1077 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
1082 t
->display_map
[t
->n_display_map
++] = column
;
1084 column
= va_arg(ap
, size_t);
1085 if (column
== (size_t) -1)
1094 int table_set_sort(Table
*t
, size_t first_column
, ...) {
1095 size_t allocated
, column
;
1100 allocated
= t
->n_sort_map
;
1101 column
= first_column
;
1103 va_start(ap
, first_column
);
1105 assert(column
< t
->n_columns
);
1107 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1112 t
->sort_map
[t
->n_sort_map
++] = column
;
1114 column
= va_arg(ap
, size_t);
1115 if (column
== (size_t) -1)
1123 int table_hide_column_from_display(Table
*t
, size_t column
) {
1127 assert(column
< t
->n_columns
);
1129 /* If the display map is empty, initialize it with all available columns */
1130 if (!t
->display_map
) {
1131 r
= table_set_display_all(t
);
1136 size_t allocated
= t
->n_display_map
, cur
= 0;
1138 for (size_t i
= 0; i
< allocated
; i
++) {
1139 if (t
->display_map
[i
] == column
)
1142 t
->display_map
[cur
++] = t
->display_map
[i
];
1145 t
->n_display_map
= cur
;
1150 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1154 if (a
->type
== b
->type
) {
1156 /* We only define ordering for cells of the same data type. If cells with different data types are
1157 * compared we follow the order the cells were originally added in */
1162 return strcmp(a
->string
, b
->string
);
1165 return path_compare(a
->string
, b
->string
);
1168 case TABLE_STRV_WRAPPED
:
1169 return strv_compare(a
->strv
, b
->strv
);
1172 if (!a
->boolean
&& b
->boolean
)
1174 if (a
->boolean
&& !b
->boolean
)
1178 case TABLE_TIMESTAMP
:
1179 case TABLE_TIMESTAMP_UTC
:
1180 case TABLE_TIMESTAMP_RELATIVE
:
1181 return CMP(a
->timestamp
, b
->timestamp
);
1183 case TABLE_TIMESPAN
:
1184 case TABLE_TIMESPAN_MSEC
:
1185 return CMP(a
->timespan
, b
->timespan
);
1189 return CMP(a
->size
, b
->size
);
1192 return CMP(a
->int_val
, b
->int_val
);
1195 return CMP(a
->int8
, b
->int8
);
1198 return CMP(a
->int16
, b
->int16
);
1201 return CMP(a
->int32
, b
->int32
);
1204 return CMP(a
->int64
, b
->int64
);
1207 return CMP(a
->uint_val
, b
->uint_val
);
1210 return CMP(a
->uint8
, b
->uint8
);
1213 return CMP(a
->uint16
, b
->uint16
);
1216 return CMP(a
->uint32
, b
->uint32
);
1219 return CMP(a
->uint64
, b
->uint64
);
1222 return CMP(a
->percent
, b
->percent
);
1225 return CMP(a
->ifindex
, b
->ifindex
);
1228 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1230 case TABLE_IN6_ADDR
:
1231 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1235 return memcmp(&a
->id128
, &b
->id128
, sizeof(sd_id128_t
));
1242 /* Generic fallback using the original order in which the cells where added. */
1243 return CMP(index_a
, index_b
);
1246 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1250 assert(t
->sort_map
);
1252 /* Make sure the header stays at the beginning */
1253 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1255 if (*a
< t
->n_columns
)
1257 if (*b
< t
->n_columns
)
1260 /* Order other lines by the sorting map */
1261 for (size_t i
= 0; i
< t
->n_sort_map
; i
++) {
1264 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1265 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1267 r
= cell_data_compare(d
, *a
, dd
, *b
);
1269 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1272 /* Order identical lines by the order there were originally added in */
1276 static char* format_strv_width(char **strv
, size_t column_width
) {
1277 _cleanup_fclose_
FILE *f
= NULL
;
1279 _cleanup_free_
char *buf
= NULL
;
1281 f
= open_memstream_unlocked(&buf
, &sz
);
1285 size_t position
= 0;
1287 STRV_FOREACH(p
, strv
) {
1288 size_t our_len
= utf8_console_width(*p
); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1289 * If that happens, we'll just print one item per line. */
1291 if (position
== 0) {
1294 } else if (size_add(size_add(position
, 1), our_len
) <= column_width
) {
1295 fprintf(f
, " %s", *p
);
1296 position
= size_add(size_add(position
, 1), our_len
);
1298 fprintf(f
, "\n%s", *p
);
1303 if (fflush_and_check(f
) < 0)
1307 return TAKE_PTR(buf
);
1310 static const char *table_data_format(Table
*t
, TableData
*d
, bool avoid_uppercasing
, size_t column_width
, bool *have_soft
) {
1314 /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1315 (d
->type
!= TABLE_STRV_WRAPPED
|| d
->formatted_for_width
== column_width
))
1316 return d
->formatted
;
1320 return strempty(t
->empty_string
);
1324 if (d
->uppercase
&& !avoid_uppercasing
) {
1325 d
->formatted
= new(char, strlen(d
->string
) + 1);
1329 char *q
= d
->formatted
;
1330 for (char *p
= d
->string
; *p
; p
++, q
++)
1331 *q
= (char) toupper((unsigned char) *p
);
1334 return d
->formatted
;
1340 if (strv_isempty(d
->strv
))
1341 return strempty(t
->empty_string
);
1343 d
->formatted
= strv_join(d
->strv
, "\n");
1348 case TABLE_STRV_WRAPPED
: {
1349 if (strv_isempty(d
->strv
))
1350 return strempty(t
->empty_string
);
1352 char *buf
= format_strv_width(d
->strv
, column_width
);
1356 free_and_replace(d
->formatted
, buf
);
1357 d
->formatted_for_width
= column_width
;
1365 return yes_no(d
->boolean
);
1367 case TABLE_TIMESTAMP
:
1368 case TABLE_TIMESTAMP_UTC
:
1369 case TABLE_TIMESTAMP_RELATIVE
: {
1370 _cleanup_free_
char *p
;
1373 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1377 if (d
->type
== TABLE_TIMESTAMP
)
1378 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1379 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1380 ret
= format_timestamp_style(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
, TIMESTAMP_UTC
);
1382 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1386 d
->formatted
= TAKE_PTR(p
);
1390 case TABLE_TIMESPAN
:
1391 case TABLE_TIMESPAN_MSEC
: {
1392 _cleanup_free_
char *p
;
1394 p
= new(char, FORMAT_TIMESPAN_MAX
);
1398 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1399 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1402 d
->formatted
= TAKE_PTR(p
);
1407 _cleanup_free_
char *p
;
1409 p
= new(char, FORMAT_BYTES_MAX
);
1413 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1416 d
->formatted
= TAKE_PTR(p
);
1421 _cleanup_free_
char *p
;
1424 p
= new(char, FORMAT_BYTES_MAX
+2);
1428 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1432 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1434 d
->formatted
= TAKE_PTR(p
);
1439 _cleanup_free_
char *p
;
1441 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1445 sprintf(p
, "%i", d
->int_val
);
1446 d
->formatted
= TAKE_PTR(p
);
1451 _cleanup_free_
char *p
;
1453 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1457 sprintf(p
, "%" PRIi8
, d
->int8
);
1458 d
->formatted
= TAKE_PTR(p
);
1463 _cleanup_free_
char *p
;
1465 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1469 sprintf(p
, "%" PRIi16
, d
->int16
);
1470 d
->formatted
= TAKE_PTR(p
);
1475 _cleanup_free_
char *p
;
1477 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1481 sprintf(p
, "%" PRIi32
, d
->int32
);
1482 d
->formatted
= TAKE_PTR(p
);
1487 _cleanup_free_
char *p
;
1489 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1493 sprintf(p
, "%" PRIi64
, d
->int64
);
1494 d
->formatted
= TAKE_PTR(p
);
1499 _cleanup_free_
char *p
;
1501 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1505 sprintf(p
, "%u", d
->uint_val
);
1506 d
->formatted
= TAKE_PTR(p
);
1511 _cleanup_free_
char *p
;
1513 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1517 sprintf(p
, "%" PRIu8
, d
->uint8
);
1518 d
->formatted
= TAKE_PTR(p
);
1522 case TABLE_UINT16
: {
1523 _cleanup_free_
char *p
;
1525 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1529 sprintf(p
, "%" PRIu16
, d
->uint16
);
1530 d
->formatted
= TAKE_PTR(p
);
1534 case TABLE_UINT32
: {
1535 _cleanup_free_
char *p
;
1537 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1541 sprintf(p
, "%" PRIu32
, d
->uint32
);
1542 d
->formatted
= TAKE_PTR(p
);
1546 case TABLE_UINT64
: {
1547 _cleanup_free_
char *p
;
1549 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1553 sprintf(p
, "%" PRIu64
, d
->uint64
);
1554 d
->formatted
= TAKE_PTR(p
);
1558 case TABLE_PERCENT
: {
1559 _cleanup_free_
char *p
;
1561 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1565 sprintf(p
, "%i%%" , d
->percent
);
1566 d
->formatted
= TAKE_PTR(p
);
1570 case TABLE_IFINDEX
: {
1571 _cleanup_free_
char *p
= NULL
;
1572 char name
[IF_NAMESIZE
+ 1];
1574 if (format_ifname(d
->ifindex
, name
)) {
1579 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1583 d
->formatted
= TAKE_PTR(p
);
1588 case TABLE_IN6_ADDR
: {
1589 _cleanup_free_
char *p
= NULL
;
1591 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1592 &d
->address
, &p
) < 0)
1595 d
->formatted
= TAKE_PTR(p
);
1602 p
= new(char, SD_ID128_STRING_MAX
);
1606 d
->formatted
= sd_id128_to_string(d
->id128
, p
);
1613 p
= new(char, ID128_UUID_STRING_MAX
);
1617 d
->formatted
= id128_to_uuid_string(d
->id128
, p
);
1622 assert_not_reached("Unexpected type?");
1625 return d
->formatted
;
1628 static int console_width_height(
1631 size_t *ret_height
) {
1633 size_t max_width
= 0, height
= 0;
1638 /* Determine the width and height in console character cells the specified string needs. */
1643 p
= strchr(s
, '\n');
1645 _cleanup_free_
char *c
= NULL
;
1647 c
= strndup(s
, p
- s
);
1651 k
= utf8_console_width(c
);
1654 k
= utf8_console_width(s
);
1657 if (k
== (size_t) -1)
1663 } while (!isempty(s
));
1666 *ret_width
= max_width
;
1669 *ret_height
= height
;
1674 static int table_data_requested_width_height(
1677 size_t available_width
,
1682 _cleanup_free_
char *truncated
= NULL
;
1683 bool truncation_applied
= false;
1684 size_t width
, height
;
1689 t
= table_data_format(table
, d
, false, available_width
, &soft
);
1693 if (table
->cell_height_max
!= (size_t) -1) {
1694 r
= string_truncate_lines(t
, table
->cell_height_max
, &truncated
);
1698 truncation_applied
= true;
1703 r
= console_width_height(t
, &width
, &height
);
1707 if (d
->maximum_width
!= (size_t) -1 && width
> d
->maximum_width
)
1708 width
= d
->maximum_width
;
1710 if (width
< d
->minimum_width
)
1711 width
= d
->minimum_width
;
1716 *ret_height
= height
;
1717 if (have_soft
&& soft
)
1720 return truncation_applied
;
1723 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1724 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1725 _cleanup_free_
char *clickable
= NULL
;
1730 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1733 assert(percent
<= 100);
1735 old_length
= strlen(str
);
1738 r
= terminal_urlify(url
, str
, &clickable
);
1742 clickable_length
= strlen(clickable
);
1744 clickable_length
= old_length
;
1746 /* Determine current width on screen */
1748 while (p
< str
+ old_length
) {
1751 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1752 p
++, w
++; /* count invalid chars as 1 */
1756 p
= utf8_next_char(p
);
1757 w
+= unichar_iswide(c
) ? 2 : 1;
1760 /* Already wider than the target, if so, don't do anything */
1761 if (w
>= new_length
)
1762 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1764 /* How much spaces shall we add? An how much on the left side? */
1765 space
= new_length
- w
;
1766 lspace
= space
* percent
/ 100U;
1768 ret
= new(char, space
+ clickable_length
+ 1);
1772 for (size_t i
= 0; i
< lspace
; i
++)
1774 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1775 for (size_t i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1778 ret
[space
+ clickable_length
] = 0;
1782 static bool table_data_isempty(TableData
*d
) {
1785 if (d
->type
== TABLE_EMPTY
)
1788 /* Let's also consider an empty strv as truly empty. */
1789 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
1790 return strv_isempty(d
->strv
);
1792 /* Note that an empty string we do not consider empty here! */
1796 static const char* table_data_color(TableData
*d
) {
1802 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1803 if (table_data_isempty(d
))
1809 static const char* table_data_rgap_color(TableData
*d
) {
1813 return d
->rgap_color
;
1818 int table_print(Table
*t
, FILE *f
) {
1819 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1820 table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1822 _cleanup_free_
size_t *sorted
= NULL
;
1823 uint64_t *column_weight
, weight_sum
;
1831 /* Ensure we have no incomplete rows */
1832 assert(t
->n_cells
% t
->n_columns
== 0);
1834 n_rows
= t
->n_cells
/ t
->n_columns
;
1835 assert(n_rows
> 0); /* at least the header row must be complete */
1838 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1840 sorted
= new(size_t, n_rows
);
1844 for (size_t i
= 0; i
< n_rows
; i
++)
1845 sorted
[i
] = i
* t
->n_columns
;
1847 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1851 display_columns
= t
->n_display_map
;
1853 display_columns
= t
->n_columns
;
1855 assert(display_columns
> 0);
1857 minimum_width
= newa(size_t, display_columns
);
1858 maximum_width
= newa(size_t, display_columns
);
1859 requested_width
= newa(size_t, display_columns
);
1860 column_weight
= newa0(uint64_t, display_columns
);
1862 for (size_t j
= 0; j
< display_columns
; j
++) {
1863 minimum_width
[j
] = 1;
1864 maximum_width
[j
] = (size_t) -1;
1867 for (unsigned pass
= 0; pass
< 2; pass
++) {
1868 /* First pass: determine column sizes */
1870 for (size_t j
= 0; j
< display_columns
; j
++)
1871 requested_width
[j
] = (size_t) -1;
1873 bool any_soft
= false;
1875 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1878 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1879 * hence we don't care for sorted[] during the first pass. */
1880 row
= t
->data
+ i
* t
->n_columns
;
1882 for (size_t j
= 0; j
< display_columns
; j
++) {
1884 size_t req_width
, req_height
;
1886 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1888 r
= table_data_requested_width_height(t
, d
,
1889 width
? width
[j
] : SIZE_MAX
,
1890 &req_width
, &req_height
, &any_soft
);
1893 if (r
> 0) { /* Truncated because too many lines? */
1894 _cleanup_free_
char *last
= NULL
;
1897 /* If we are going to show only the first few lines of a cell that has
1898 * multiple make sure that we have enough space horizontally to show an
1899 * ellipsis. Hence, let's figure out the last line, and account for its
1900 * length plus ellipsis. */
1902 field
= table_data_format(t
, d
, false,
1903 width
? width
[j
] : SIZE_MAX
,
1908 assert_se(t
->cell_height_max
> 0);
1909 r
= string_extract_line(field
, t
->cell_height_max
-1, &last
);
1913 req_width
= MAX(req_width
,
1914 utf8_console_width(last
) +
1915 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS
)));
1918 /* Determine the biggest width that any cell in this column would like to have */
1919 if (requested_width
[j
] == (size_t) -1 ||
1920 requested_width
[j
] < req_width
)
1921 requested_width
[j
] = req_width
;
1923 /* Determine the minimum width any cell in this column needs */
1924 if (minimum_width
[j
] < d
->minimum_width
)
1925 minimum_width
[j
] = d
->minimum_width
;
1927 /* Determine the maximum width any cell in this column needs */
1928 if (d
->maximum_width
!= (size_t) -1 &&
1929 (maximum_width
[j
] == (size_t) -1 ||
1930 maximum_width
[j
] > d
->maximum_width
))
1931 maximum_width
[j
] = d
->maximum_width
;
1933 /* Determine the full columns weight */
1934 column_weight
[j
] += d
->weight
;
1938 /* One space between each column */
1939 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1941 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1943 for (size_t j
= 0; j
< display_columns
; j
++) {
1944 weight_sum
+= column_weight
[j
];
1946 table_minimum_width
+= minimum_width
[j
];
1948 if (maximum_width
[j
] == (size_t) -1)
1949 table_maximum_width
= (size_t) -1;
1951 table_maximum_width
+= maximum_width
[j
];
1953 table_requested_width
+= requested_width
[j
];
1956 /* Calculate effective table width */
1957 if (t
->width
!= 0 && t
->width
!= (size_t) -1)
1958 table_effective_width
= t
->width
;
1959 else if (t
->width
== 0 ||
1960 ((pass
> 0 || !any_soft
) && (pager_have() || !isatty(STDOUT_FILENO
))))
1961 table_effective_width
= table_requested_width
;
1963 table_effective_width
= MIN(table_requested_width
, columns());
1965 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1966 table_effective_width
= table_maximum_width
;
1968 if (table_effective_width
< table_minimum_width
)
1969 table_effective_width
= table_minimum_width
;
1972 width
= newa(size_t, display_columns
);
1974 if (table_effective_width
>= table_requested_width
) {
1977 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1978 * each column with what it asked for and the distribute the rest. */
1980 extra
= table_effective_width
- table_requested_width
;
1982 for (size_t j
= 0; j
< display_columns
; j
++) {
1985 if (weight_sum
== 0)
1986 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1988 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1990 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1991 width
[j
] = maximum_width
[j
];
1993 if (width
[j
] < minimum_width
[j
])
1994 width
[j
] = minimum_width
[j
];
1996 assert(width
[j
] >= requested_width
[j
]);
1997 delta
= width
[j
] - requested_width
[j
];
1999 /* Subtract what we just added from the rest */
2005 assert(weight_sum
>= column_weight
[j
]);
2006 weight_sum
-= column_weight
[j
];
2009 break; /* Every column should be happy, no need to repeat calculations. */
2011 /* We need to compress the table, columns can't get what they asked for. We first provide each column
2012 * with the minimum they need, and then distribute anything left. */
2013 bool finalize
= false;
2016 extra
= table_effective_width
- table_minimum_width
;
2018 for (size_t j
= 0; j
< display_columns
; j
++)
2019 width
[j
] = (size_t) -1;
2022 bool restart
= false;
2024 for (size_t j
= 0; j
< display_columns
; j
++) {
2027 /* Did this column already get something assigned? If so, let's skip to the next */
2028 if (width
[j
] != (size_t) -1)
2031 if (weight_sum
== 0)
2032 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
2034 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
2036 if (w
>= requested_width
[j
]) {
2037 /* Never give more than requested. If we hit a column like this, there's more
2038 * space to allocate to other columns which means we need to restart the
2039 * iteration. However, if we hit a column like this, let's assign it the space
2040 * it wanted for good early.*/
2042 w
= requested_width
[j
];
2045 } else if (!finalize
)
2050 assert(w
>= minimum_width
[j
]);
2051 delta
= w
- minimum_width
[j
];
2053 assert(delta
<= extra
);
2056 assert(weight_sum
>= column_weight
[j
]);
2057 weight_sum
-= column_weight
[j
];
2059 if (restart
&& !finalize
)
2070 if (!any_soft
) /* Some columns got less than requested. If some cells were "soft",
2071 * let's try to reformat them with the new widths. Otherwise, let's
2077 /* Second pass: show output */
2078 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
2079 size_t n_subline
= 0;
2084 row
= t
->data
+ sorted
[i
];
2086 row
= t
->data
+ i
* t
->n_columns
;
2089 const char *gap_color
= NULL
;
2090 more_sublines
= false;
2092 for (size_t j
= 0; j
< display_columns
; j
++) {
2093 _cleanup_free_
char *buffer
= NULL
, *extracted
= NULL
;
2094 bool lines_truncated
= false;
2095 const char *field
, *color
= NULL
;
2099 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2101 field
= table_data_format(t
, d
, false, width
[j
], NULL
);
2105 r
= string_extract_line(field
, n_subline
, &extracted
);
2109 /* There are more lines to come */
2110 if ((t
->cell_height_max
== (size_t) -1 || n_subline
+ 1 < t
->cell_height_max
))
2111 more_sublines
= true; /* There are more lines to come */
2113 lines_truncated
= true;
2118 l
= utf8_console_width(field
);
2120 /* Field is wider than allocated space. Let's ellipsize */
2122 buffer
= ellipsize(field
, width
[j
], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2123 lines_truncated
? 100 : d
->ellipsize_percent
);
2129 if (lines_truncated
) {
2130 _cleanup_free_
char *padded
= NULL
;
2132 /* We truncated more lines of this cell, let's add an
2133 * ellipsis. We first append it, but that might make our
2134 * string grow above what we have space for, hence ellipsize
2135 * right after. This will truncate the ellipsis and add a new
2138 padded
= strjoin(field
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
2142 buffer
= ellipsize(padded
, width
[j
], 100);
2147 l
= utf8_console_width(field
);
2151 _cleanup_free_
char *aligned
= NULL
;
2152 /* Field is shorter than allocated space. Let's align with spaces */
2154 aligned
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
2158 free_and_replace(buffer
, aligned
);
2163 if (l
>= width
[j
] && d
->url
) {
2164 _cleanup_free_
char *clickable
= NULL
;
2166 r
= terminal_urlify(d
->url
, field
, &clickable
);
2170 free_and_replace(buffer
, clickable
);
2174 if (colors_enabled()) {
2176 fputs(gap_color
, f
);
2177 else if (row
== t
->data
) /* underline header line fully, including the column separator */
2178 fputs(ansi_underline(), f
);
2182 fputc(' ', f
); /* column separator left of cell */
2184 if (colors_enabled()) {
2185 color
= table_data_color(d
);
2187 /* Undo gap color */
2188 if (gap_color
|| (color
&& row
== t
->data
))
2189 fputs(ANSI_NORMAL
, f
);
2193 else if (gap_color
&& row
== t
->data
) /* underline header line cell */
2194 fputs(ansi_underline(), f
);
2199 if (colors_enabled() && (color
|| row
== t
->data
))
2200 fputs(ANSI_NORMAL
, f
);
2202 gap_color
= table_data_rgap_color(d
);
2207 } while (more_sublines
);
2210 return fflush_and_check(f
);
2213 int table_format(Table
*t
, char **ret
) {
2214 _cleanup_fclose_
FILE *f
= NULL
;
2219 f
= open_memstream_unlocked(&buf
, &sz
);
2223 r
= table_print(t
, f
);
2234 size_t table_get_rows(Table
*t
) {
2238 assert(t
->n_columns
> 0);
2239 return t
->n_cells
/ t
->n_columns
;
2242 size_t table_get_columns(Table
*t
) {
2246 assert(t
->n_columns
> 0);
2247 return t
->n_columns
;
2250 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
2252 assert(column
< t
->n_columns
);
2254 if (!t
->reverse_map
) {
2258 t
->reverse_map
= new0(bool, t
->n_columns
);
2259 if (!t
->reverse_map
)
2263 t
->reverse_map
[column
] = b
;
2267 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
2272 if (column
>= t
->n_columns
)
2275 i
= row
* t
->n_columns
+ column
;
2276 if (i
>= t
->n_cells
)
2279 return TABLE_INDEX_TO_CELL(i
);
2282 const void *table_get(Table
*t
, TableCell
*cell
) {
2287 d
= table_get_data(t
, cell
);
2294 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
2297 cell
= table_get_cell(t
, row
, column
);
2301 return table_get(t
, cell
);
2304 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
2309 return json_variant_new_null(ret
);
2313 return json_variant_new_string(ret
, d
->string
);
2316 case TABLE_STRV_WRAPPED
:
2317 return json_variant_new_array_strv(ret
, d
->strv
);
2320 return json_variant_new_boolean(ret
, d
->boolean
);
2322 case TABLE_TIMESTAMP
:
2323 case TABLE_TIMESTAMP_UTC
:
2324 case TABLE_TIMESTAMP_RELATIVE
:
2325 if (d
->timestamp
== USEC_INFINITY
)
2326 return json_variant_new_null(ret
);
2328 return json_variant_new_unsigned(ret
, d
->timestamp
);
2330 case TABLE_TIMESPAN
:
2331 case TABLE_TIMESPAN_MSEC
:
2332 if (d
->timespan
== USEC_INFINITY
)
2333 return json_variant_new_null(ret
);
2335 return json_variant_new_unsigned(ret
, d
->timespan
);
2339 if (d
->size
== (uint64_t) -1)
2340 return json_variant_new_null(ret
);
2342 return json_variant_new_unsigned(ret
, d
->size
);
2345 return json_variant_new_integer(ret
, d
->int_val
);
2348 return json_variant_new_integer(ret
, d
->int8
);
2351 return json_variant_new_integer(ret
, d
->int16
);
2354 return json_variant_new_integer(ret
, d
->int32
);
2357 return json_variant_new_integer(ret
, d
->int64
);
2360 return json_variant_new_unsigned(ret
, d
->uint_val
);
2363 return json_variant_new_unsigned(ret
, d
->uint8
);
2366 return json_variant_new_unsigned(ret
, d
->uint16
);
2369 return json_variant_new_unsigned(ret
, d
->uint32
);
2372 return json_variant_new_unsigned(ret
, d
->uint64
);
2375 return json_variant_new_integer(ret
, d
->percent
);
2378 return json_variant_new_integer(ret
, d
->ifindex
);
2381 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
2383 case TABLE_IN6_ADDR
:
2384 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
2387 char buf
[SD_ID128_STRING_MAX
];
2388 return json_variant_new_string(ret
, sd_id128_to_string(d
->id128
, buf
));
2392 char buf
[ID128_UUID_STRING_MAX
];
2393 return json_variant_new_string(ret
, id128_to_uuid_string(d
->id128
, buf
));
2401 static char* string_to_json_field_name(const char *f
) {
2402 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2403 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2404 * underscores and leave everything as is. */
2406 char *c
= strdup(f
);
2410 for (char *x
= c
; *x
; x
++)
2417 int table_to_json(Table
*t
, JsonVariant
**ret
) {
2418 JsonVariant
**rows
= NULL
, **elements
= NULL
;
2419 _cleanup_free_
size_t *sorted
= NULL
;
2420 size_t n_rows
, display_columns
;
2425 /* Ensure we have no incomplete rows */
2426 assert(t
->n_cells
% t
->n_columns
== 0);
2428 n_rows
= t
->n_cells
/ t
->n_columns
;
2429 assert(n_rows
> 0); /* at least the header row must be complete */
2432 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2434 sorted
= new(size_t, n_rows
);
2440 for (size_t i
= 0; i
< n_rows
; i
++)
2441 sorted
[i
] = i
* t
->n_columns
;
2443 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2447 display_columns
= t
->n_display_map
;
2449 display_columns
= t
->n_columns
;
2450 assert(display_columns
> 0);
2452 elements
= new0(JsonVariant
*, display_columns
* 2);
2458 for (size_t j
= 0; j
< display_columns
; j
++) {
2459 _cleanup_free_
char *mangled
= NULL
;
2460 const char *formatted
;
2463 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2465 /* Field names must be strings, hence format whatever we got here as a string first */
2466 formatted
= table_data_format(t
, d
, true, SIZE_MAX
, NULL
);
2472 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2473 mangled
= string_to_json_field_name(formatted
);
2479 r
= json_variant_new_string(elements
+ j
*2, mangled
);
2484 rows
= new0(JsonVariant
*, n_rows
-1);
2490 for (size_t i
= 1; i
< n_rows
; i
++) {
2494 row
= t
->data
+ sorted
[i
];
2496 row
= t
->data
+ i
* t
->n_columns
;
2498 for (size_t j
= 0; j
< display_columns
; j
++) {
2502 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2505 elements
[k
] = json_variant_unref(elements
[k
]);
2507 r
= table_data_to_json(d
, elements
+ k
);
2512 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2517 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2521 json_variant_unref_many(rows
, n_rows
-1);
2526 json_variant_unref_many(elements
, display_columns
*2);
2533 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2534 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2542 r
= table_to_json(t
, &v
);
2546 json_variant_dump(v
, flags
, f
, NULL
);
2548 return fflush_and_check(f
);