1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 "process-util.h"
24 #include "signal-util.h"
25 #include "sort-util.h"
26 #include "string-util.h"
28 #include "terminal-util.h"
29 #include "time-util.h"
30 #include "user-util.h"
34 #define DEFAULT_WEIGHT 100
37 A few notes on implementation details:
39 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
40 table. It can be easily converted to an index number and back.
42 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
43 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
44 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
45 outside only sees Table and TableCell.
47 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
50 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
51 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
52 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
53 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
55 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
56 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
57 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
58 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
61 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
62 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
63 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
66 typedef struct TableData
{
70 size_t minimum_width
; /* minimum width for the column */
71 size_t maximum_width
; /* maximum width for the column */
72 size_t formatted_for_width
; /* the width we tried to format for */
73 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
74 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
75 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
77 bool uppercase
; /* Uppercase string on display */
79 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 */
80 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 */
81 char *url
; /* A URL to use for a clickable hyperlink */
82 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
85 uint8_t data
[0]; /* data is generic array */
102 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
104 union in_addr_union address
;
109 /* … add more here as we start supporting more cell data types … */
113 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
118 i
= PTR_TO_SIZE(cell
);
124 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
125 assert(index
!= SIZE_MAX
);
126 return SIZE_TO_PTR(index
+ 1);
133 bool header
; /* Whether to show the header row? */
134 size_t width
; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
135 * width or less wide, but not wider. Otherwise the width to format this table in. */
136 size_t cell_height_max
; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If SIZE_MAX then no limit is set, the default. == 0 is not allowed.) */
141 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 */
142 size_t n_display_map
;
144 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
152 Table
*table_new_raw(size_t n_columns
) {
153 _cleanup_(table_unrefp
) Table
*t
= NULL
;
155 assert(n_columns
> 0);
161 *t
= (struct Table
) {
162 .n_columns
= n_columns
,
165 .cell_height_max
= SIZE_MAX
,
171 Table
*table_new_internal(const char *first_header
, ...) {
172 _cleanup_(table_unrefp
) Table
*t
= NULL
;
173 size_t n_columns
= 1;
177 assert(first_header
);
179 va_start(ap
, first_header
);
181 if (!va_arg(ap
, const char*))
188 t
= table_new_raw(n_columns
);
192 va_start(ap
, first_header
);
193 for (const char *h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
196 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
202 /* Make the table header uppercase */
203 r
= table_set_uppercase(t
, cell
, true);
211 assert(t
->n_columns
== t
->n_cells
);
215 static TableData
*table_data_free(TableData
*d
) {
221 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
227 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
228 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
230 Table
*table_unref(Table
*t
) {
234 for (size_t i
= 0; i
< t
->n_cells
; i
++)
235 table_data_unref(t
->data
[i
]);
238 free(t
->display_map
);
240 free(t
->reverse_map
);
241 free(t
->empty_string
);
246 static size_t table_data_size(TableDataType type
, const void *data
) {
255 return strlen(data
) + 1;
258 case TABLE_STRV_WRAPPED
:
259 return sizeof(char **);
264 case TABLE_TIMESTAMP
:
265 case TABLE_TIMESTAMP_UTC
:
266 case TABLE_TIMESTAMP_RELATIVE
:
268 case TABLE_TIMESPAN_MSEC
:
269 return sizeof(usec_t
);
275 return sizeof(uint64_t);
279 return sizeof(uint32_t);
283 return sizeof(uint16_t);
287 return sizeof(uint8_t);
297 return sizeof(struct in_addr
);
300 return sizeof(struct in6_addr
);
304 return sizeof(sd_id128_t
);
307 return sizeof(uid_t
);
309 return sizeof(gid_t
);
311 return sizeof(pid_t
);
314 assert_not_reached("Uh? Unexpected cell type");
318 static bool table_data_matches(
322 size_t minimum_width
,
323 size_t maximum_width
,
325 unsigned align_percent
,
326 unsigned ellipsize_percent
) {
334 if (d
->minimum_width
!= minimum_width
)
337 if (d
->maximum_width
!= maximum_width
)
340 if (d
->weight
!= weight
)
343 if (d
->align_percent
!= align_percent
)
346 if (d
->ellipsize_percent
!= ellipsize_percent
)
349 /* If a color/url/uppercase flag is set, refuse to merge */
350 if (d
->color
|| d
->rgap_color
)
357 k
= table_data_size(type
, data
);
358 l
= table_data_size(d
->type
, d
->data
);
362 return memcmp_safe(data
, d
->data
, l
) == 0;
365 static TableData
*table_data_new(
368 size_t minimum_width
,
369 size_t maximum_width
,
371 unsigned align_percent
,
372 unsigned ellipsize_percent
) {
374 _cleanup_free_ TableData
*d
= NULL
;
377 data_size
= table_data_size(type
, data
);
379 d
= malloc0(offsetof(TableData
, data
) + data_size
);
385 d
->minimum_width
= minimum_width
;
386 d
->maximum_width
= maximum_width
;
388 d
->align_percent
= align_percent
;
389 d
->ellipsize_percent
= ellipsize_percent
;
391 if (IN_SET(type
, TABLE_STRV
, TABLE_STRV_WRAPPED
)) {
392 d
->strv
= strv_copy(data
);
396 memcpy_safe(d
->data
, data
, data_size
);
401 int table_add_cell_full(
403 TableCell
**ret_cell
,
406 size_t minimum_width
,
407 size_t maximum_width
,
409 unsigned align_percent
,
410 unsigned ellipsize_percent
) {
412 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
417 assert(type
< _TABLE_DATA_TYPE_MAX
);
419 /* Special rule: patch NULL data fields to the empty field */
423 /* Determine the cell adjacent to the current one, but one row up */
424 if (t
->n_cells
>= t
->n_columns
)
425 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
429 /* If formatting parameters are left unspecified, copy from the previous row */
430 if (minimum_width
== SIZE_MAX
)
431 minimum_width
= p
? p
->minimum_width
: 1;
433 if (weight
== UINT_MAX
)
434 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
436 if (align_percent
== UINT_MAX
)
437 align_percent
= p
? p
->align_percent
: 0;
439 if (ellipsize_percent
== UINT_MAX
)
440 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
442 assert(align_percent
<= 100);
443 assert(ellipsize_percent
<= 100);
445 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
446 * formatting. Let's see if we can reuse the cell data and ref it once more. */
448 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
449 d
= table_data_ref(p
);
451 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
456 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
460 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
462 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
467 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
468 _cleanup_free_
char *buffer
= NULL
;
472 va_start(ap
, format
);
473 r
= vasprintf(&buffer
, format
, ap
);
478 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
481 int table_fill_empty(Table
*t
, size_t until_column
) {
486 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
487 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
489 if (until_column
>= t
->n_columns
)
493 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
497 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
502 int table_dup_cell(Table
*t
, TableCell
*cell
) {
507 /* Add the data of the specified cell a second time as a new cell to the end. */
509 i
= TABLE_CELL_TO_INDEX(cell
);
513 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
516 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
520 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
521 _cleanup_free_
char *curl
= NULL
;
527 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
528 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
530 i
= TABLE_CELL_TO_INDEX(cell
);
534 assert_se(od
= t
->data
[i
]);
538 assert(od
->n_ref
> 1);
541 curl
= strdup(od
->url
);
553 od
->ellipsize_percent
);
557 nd
->color
= od
->color
;
558 nd
->rgap_color
= od
->rgap_color
;
559 nd
->url
= TAKE_PTR(curl
);
560 nd
->uppercase
= od
->uppercase
;
562 table_data_unref(od
);
565 assert(nd
->n_ref
== 1);
570 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
576 /* Get the data object of the specified cell, or NULL if it doesn't exist */
578 i
= TABLE_CELL_TO_INDEX(cell
);
583 assert(t
->data
[i
]->n_ref
> 0);
588 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
594 if (minimum_width
== SIZE_MAX
)
597 r
= table_dedup_cell(t
, cell
);
601 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
605 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
611 r
= table_dedup_cell(t
, cell
);
615 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
619 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
625 if (weight
== UINT_MAX
)
626 weight
= DEFAULT_WEIGHT
;
628 r
= table_dedup_cell(t
, cell
);
632 table_get_data(t
, cell
)->weight
= weight
;
636 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
642 if (percent
== UINT_MAX
)
645 assert(percent
<= 100);
647 r
= table_dedup_cell(t
, cell
);
651 table_get_data(t
, cell
)->align_percent
= percent
;
655 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
661 if (percent
== UINT_MAX
)
664 assert(percent
<= 100);
666 r
= table_dedup_cell(t
, cell
);
670 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
674 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
680 r
= table_dedup_cell(t
, cell
);
684 table_get_data(t
, cell
)->color
= empty_to_null(color
);
688 int table_set_rgap_color(Table
*t
, TableCell
*cell
, const char *color
) {
694 r
= table_dedup_cell(t
, cell
);
698 table_get_data(t
, cell
)->rgap_color
= empty_to_null(color
);
702 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
703 _cleanup_free_
char *copy
= NULL
;
715 r
= table_dedup_cell(t
, cell
);
719 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
722 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
729 r
= table_dedup_cell(t
, cell
);
733 assert_se(d
= table_get_data(t
, cell
));
735 if (d
->uppercase
== b
)
738 d
->formatted
= mfree(d
->formatted
);
743 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
744 _cleanup_free_
char *curl
= NULL
;
751 i
= TABLE_CELL_TO_INDEX(cell
);
755 assert_se(od
= t
->data
[i
]);
758 curl
= strdup(od
->url
);
770 od
->ellipsize_percent
);
774 nd
->color
= od
->color
;
775 nd
->rgap_color
= od
->rgap_color
;
776 nd
->url
= TAKE_PTR(curl
);
777 nd
->uppercase
= od
->uppercase
;
779 table_data_unref(od
);
785 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
786 TableCell
*last_cell
= NULL
;
791 assert(first_type
>= 0);
792 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
794 va_start(ap
, first_type
);
796 for (TableDataType type
= first_type
;; type
= va_arg(ap
, TableDataType
)) {
814 union in_addr_union address
;
829 data
= va_arg(ap
, const char *);
833 case TABLE_STRV_WRAPPED
:
834 data
= va_arg(ap
, char * const *);
838 buffer
.b
= va_arg(ap
, int);
842 case TABLE_TIMESTAMP
:
843 case TABLE_TIMESTAMP_UTC
:
844 case TABLE_TIMESTAMP_RELATIVE
:
846 case TABLE_TIMESPAN_MSEC
:
847 buffer
.usec
= va_arg(ap
, usec_t
);
853 buffer
.size
= va_arg(ap
, uint64_t);
859 buffer
.int_val
= va_arg(ap
, int);
860 data
= &buffer
.int_val
;
864 int x
= va_arg(ap
, int);
865 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
873 int x
= va_arg(ap
, int);
874 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
877 data
= &buffer
.int16
;
882 buffer
.int32
= va_arg(ap
, int32_t);
883 data
= &buffer
.int32
;
887 buffer
.int64
= va_arg(ap
, int64_t);
888 data
= &buffer
.int64
;
892 buffer
.uint_val
= va_arg(ap
, unsigned);
893 data
= &buffer
.uint_val
;
897 unsigned x
= va_arg(ap
, unsigned);
898 assert(x
<= UINT8_MAX
);
901 data
= &buffer
.uint8
;
906 unsigned x
= va_arg(ap
, unsigned);
907 assert(x
<= UINT16_MAX
);
910 data
= &buffer
.uint16
;
915 buffer
.uint32
= va_arg(ap
, uint32_t);
916 data
= &buffer
.uint32
;
920 buffer
.uint64
= va_arg(ap
, uint64_t);
921 data
= &buffer
.uint64
;
925 buffer
.percent
= va_arg(ap
, int);
926 data
= &buffer
.percent
;
930 buffer
.ifindex
= va_arg(ap
, int);
931 data
= &buffer
.ifindex
;
935 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
936 data
= &buffer
.address
.in
;
940 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
941 data
= &buffer
.address
.in6
;
946 buffer
.id128
= va_arg(ap
, sd_id128_t
);
947 data
= &buffer
.id128
;
951 buffer
.uid
= va_arg(ap
, uid_t
);
956 buffer
.gid
= va_arg(ap
, gid_t
);
961 buffer
.pid
= va_arg(ap
, pid_t
);
965 case TABLE_SET_MINIMUM_WIDTH
: {
966 size_t w
= va_arg(ap
, size_t);
968 r
= table_set_minimum_width(t
, last_cell
, w
);
972 case TABLE_SET_MAXIMUM_WIDTH
: {
973 size_t w
= va_arg(ap
, size_t);
974 r
= table_set_maximum_width(t
, last_cell
, w
);
978 case TABLE_SET_WEIGHT
: {
979 unsigned w
= va_arg(ap
, unsigned);
980 r
= table_set_weight(t
, last_cell
, w
);
984 case TABLE_SET_ALIGN_PERCENT
: {
985 unsigned p
= va_arg(ap
, unsigned);
986 r
= table_set_align_percent(t
, last_cell
, p
);
990 case TABLE_SET_ELLIPSIZE_PERCENT
: {
991 unsigned p
= va_arg(ap
, unsigned);
992 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
996 case TABLE_SET_COLOR
: {
997 const char *c
= va_arg(ap
, const char*);
998 r
= table_set_color(t
, last_cell
, c
);
1002 case TABLE_SET_RGAP_COLOR
: {
1003 const char *c
= va_arg(ap
, const char*);
1004 r
= table_set_rgap_color(t
, last_cell
, c
);
1008 case TABLE_SET_BOTH_COLORS
: {
1009 const char *c
= va_arg(ap
, const char*);
1011 r
= table_set_color(t
, last_cell
, c
);
1017 r
= table_set_rgap_color(t
, last_cell
, c
);
1021 case TABLE_SET_URL
: {
1022 const char *u
= va_arg(ap
, const char*);
1023 r
= table_set_url(t
, last_cell
, u
);
1027 case TABLE_SET_UPPERCASE
: {
1028 int u
= va_arg(ap
, int);
1029 r
= table_set_uppercase(t
, last_cell
, u
);
1033 case _TABLE_DATA_TYPE_MAX
:
1034 /* Used as end marker */
1039 assert_not_reached("Uh? Unexpected data type.");
1042 r
= table_add_cell(t
, &last_cell
, type
, data
);
1051 void table_set_header(Table
*t
, bool b
) {
1057 void table_set_width(Table
*t
, size_t width
) {
1063 void table_set_cell_height_max(Table
*t
, size_t height
) {
1065 assert(height
>= 1 || height
== SIZE_MAX
);
1067 t
->cell_height_max
= height
;
1070 int table_set_empty_string(Table
*t
, const char *empty
) {
1073 return free_and_strdup(&t
->empty_string
, empty
);
1076 static int table_set_display_all(Table
*t
) {
1081 /* Initialize the display map to the identity */
1083 d
= reallocarray(t
->display_map
, t
->n_columns
, sizeof(size_t));
1087 for (size_t i
= 0; i
< t
->n_columns
; i
++)
1091 t
->n_display_map
= t
->n_columns
;
1096 int table_set_display_internal(Table
*t
, size_t first_column
, ...) {
1097 size_t allocated
, column
;
1102 allocated
= t
->n_display_map
;
1103 column
= first_column
;
1105 va_start(ap
, first_column
);
1107 assert(column
< t
->n_columns
);
1109 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
1114 t
->display_map
[t
->n_display_map
++] = column
;
1116 column
= va_arg(ap
, size_t);
1117 if (column
== SIZE_MAX
)
1126 int table_set_sort_internal(Table
*t
, size_t first_column
, ...) {
1127 size_t allocated
, column
;
1132 allocated
= t
->n_sort_map
;
1133 column
= first_column
;
1135 va_start(ap
, first_column
);
1137 assert(column
< t
->n_columns
);
1139 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1144 t
->sort_map
[t
->n_sort_map
++] = column
;
1146 column
= va_arg(ap
, size_t);
1147 if (column
== SIZE_MAX
)
1155 int table_hide_column_from_display(Table
*t
, size_t column
) {
1159 assert(column
< t
->n_columns
);
1161 /* If the display map is empty, initialize it with all available columns */
1162 if (!t
->display_map
) {
1163 r
= table_set_display_all(t
);
1168 size_t allocated
= t
->n_display_map
, cur
= 0;
1170 for (size_t i
= 0; i
< allocated
; i
++) {
1171 if (t
->display_map
[i
] == column
)
1174 t
->display_map
[cur
++] = t
->display_map
[i
];
1177 t
->n_display_map
= cur
;
1182 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1186 if (a
->type
== b
->type
) {
1188 /* We only define ordering for cells of the same data type. If cells with different data types are
1189 * compared we follow the order the cells were originally added in */
1194 return strcmp(a
->string
, b
->string
);
1197 return path_compare(a
->string
, b
->string
);
1200 case TABLE_STRV_WRAPPED
:
1201 return strv_compare(a
->strv
, b
->strv
);
1204 if (!a
->boolean
&& b
->boolean
)
1206 if (a
->boolean
&& !b
->boolean
)
1210 case TABLE_TIMESTAMP
:
1211 case TABLE_TIMESTAMP_UTC
:
1212 case TABLE_TIMESTAMP_RELATIVE
:
1213 return CMP(a
->timestamp
, b
->timestamp
);
1215 case TABLE_TIMESPAN
:
1216 case TABLE_TIMESPAN_MSEC
:
1217 return CMP(a
->timespan
, b
->timespan
);
1221 return CMP(a
->size
, b
->size
);
1225 return CMP(a
->int_val
, b
->int_val
);
1228 return CMP(a
->int8
, b
->int8
);
1231 return CMP(a
->int16
, b
->int16
);
1234 return CMP(a
->int32
, b
->int32
);
1237 return CMP(a
->int64
, b
->int64
);
1240 return CMP(a
->uint_val
, b
->uint_val
);
1243 return CMP(a
->uint8
, b
->uint8
);
1246 return CMP(a
->uint16
, b
->uint16
);
1249 return CMP(a
->uint32
, b
->uint32
);
1252 return CMP(a
->uint64
, b
->uint64
);
1255 return CMP(a
->percent
, b
->percent
);
1258 return CMP(a
->ifindex
, b
->ifindex
);
1261 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1263 case TABLE_IN6_ADDR
:
1264 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1268 return memcmp(&a
->id128
, &b
->id128
, sizeof(sd_id128_t
));
1271 return CMP(a
->uid
, b
->uid
);
1274 return CMP(a
->gid
, b
->gid
);
1277 return CMP(a
->pid
, b
->pid
);
1284 /* Generic fallback using the original order in which the cells where added. */
1285 return CMP(index_a
, index_b
);
1288 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1292 assert(t
->sort_map
);
1294 /* Make sure the header stays at the beginning */
1295 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1297 if (*a
< t
->n_columns
)
1299 if (*b
< t
->n_columns
)
1302 /* Order other lines by the sorting map */
1303 for (size_t i
= 0; i
< t
->n_sort_map
; i
++) {
1306 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1307 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1309 r
= cell_data_compare(d
, *a
, dd
, *b
);
1311 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1314 /* Order identical lines by the order there were originally added in */
1318 static char* format_strv_width(char **strv
, size_t column_width
) {
1319 _cleanup_free_
char *buf
= NULL
; /* buf must be freed after f */
1320 _cleanup_fclose_
FILE *f
= NULL
;
1323 f
= open_memstream_unlocked(&buf
, &sz
);
1327 size_t position
= 0;
1329 STRV_FOREACH(p
, strv
) {
1330 size_t our_len
= utf8_console_width(*p
); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1331 * If that happens, we'll just print one item per line. */
1333 if (position
== 0) {
1336 } else if (size_add(size_add(position
, 1), our_len
) <= column_width
) {
1337 fprintf(f
, " %s", *p
);
1338 position
= size_add(size_add(position
, 1), our_len
);
1340 fprintf(f
, "\n%s", *p
);
1345 if (fflush_and_check(f
) < 0)
1349 return TAKE_PTR(buf
);
1352 static const char *table_data_format(Table
*t
, TableData
*d
, bool avoid_uppercasing
, size_t column_width
, bool *have_soft
) {
1356 /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1357 (d
->type
!= TABLE_STRV_WRAPPED
|| d
->formatted_for_width
== column_width
))
1358 return d
->formatted
;
1362 return strempty(t
->empty_string
);
1366 if (d
->uppercase
&& !avoid_uppercasing
) {
1367 d
->formatted
= new(char, strlen(d
->string
) + 1);
1371 char *q
= d
->formatted
;
1372 for (char *p
= d
->string
; *p
; p
++, q
++)
1373 *q
= (char) toupper((unsigned char) *p
);
1376 return d
->formatted
;
1382 if (strv_isempty(d
->strv
))
1383 return strempty(t
->empty_string
);
1385 d
->formatted
= strv_join(d
->strv
, "\n");
1390 case TABLE_STRV_WRAPPED
: {
1391 if (strv_isempty(d
->strv
))
1392 return strempty(t
->empty_string
);
1394 char *buf
= format_strv_width(d
->strv
, column_width
);
1398 free_and_replace(d
->formatted
, buf
);
1399 d
->formatted_for_width
= column_width
;
1407 return yes_no(d
->boolean
);
1409 case TABLE_TIMESTAMP
:
1410 case TABLE_TIMESTAMP_UTC
:
1411 case TABLE_TIMESTAMP_RELATIVE
: {
1412 _cleanup_free_
char *p
;
1415 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1419 if (d
->type
== TABLE_TIMESTAMP
)
1420 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1421 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1422 ret
= format_timestamp_style(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
, TIMESTAMP_UTC
);
1424 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1428 d
->formatted
= TAKE_PTR(p
);
1432 case TABLE_TIMESPAN
:
1433 case TABLE_TIMESPAN_MSEC
: {
1434 _cleanup_free_
char *p
;
1436 p
= new(char, FORMAT_TIMESPAN_MAX
);
1440 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1441 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1444 d
->formatted
= TAKE_PTR(p
);
1449 _cleanup_free_
char *p
;
1451 p
= new(char, FORMAT_BYTES_MAX
);
1455 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1458 d
->formatted
= TAKE_PTR(p
);
1463 _cleanup_free_
char *p
;
1466 p
= new(char, FORMAT_BYTES_MAX
+2);
1470 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1474 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1476 d
->formatted
= TAKE_PTR(p
);
1481 _cleanup_free_
char *p
;
1483 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1487 sprintf(p
, "%i", d
->int_val
);
1488 d
->formatted
= TAKE_PTR(p
);
1493 _cleanup_free_
char *p
;
1495 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1499 sprintf(p
, "%" PRIi8
, d
->int8
);
1500 d
->formatted
= TAKE_PTR(p
);
1505 _cleanup_free_
char *p
;
1507 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1511 sprintf(p
, "%" PRIi16
, d
->int16
);
1512 d
->formatted
= TAKE_PTR(p
);
1517 _cleanup_free_
char *p
;
1519 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1523 sprintf(p
, "%" PRIi32
, d
->int32
);
1524 d
->formatted
= TAKE_PTR(p
);
1529 _cleanup_free_
char *p
;
1531 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1535 sprintf(p
, "%" PRIi64
, d
->int64
);
1536 d
->formatted
= TAKE_PTR(p
);
1541 _cleanup_free_
char *p
;
1543 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1547 sprintf(p
, "%u", d
->uint_val
);
1548 d
->formatted
= TAKE_PTR(p
);
1553 _cleanup_free_
char *p
;
1555 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1559 sprintf(p
, "%" PRIu8
, d
->uint8
);
1560 d
->formatted
= TAKE_PTR(p
);
1564 case TABLE_UINT16
: {
1565 _cleanup_free_
char *p
;
1567 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1571 sprintf(p
, "%" PRIu16
, d
->uint16
);
1572 d
->formatted
= TAKE_PTR(p
);
1576 case TABLE_UINT32
: {
1577 _cleanup_free_
char *p
;
1579 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1583 sprintf(p
, "%" PRIu32
, d
->uint32
);
1584 d
->formatted
= TAKE_PTR(p
);
1588 case TABLE_UINT64
: {
1589 _cleanup_free_
char *p
;
1591 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1595 sprintf(p
, "%" PRIu64
, d
->uint64
);
1596 d
->formatted
= TAKE_PTR(p
);
1600 case TABLE_PERCENT
: {
1601 _cleanup_free_
char *p
;
1603 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1607 sprintf(p
, "%i%%" , d
->percent
);
1608 d
->formatted
= TAKE_PTR(p
);
1612 case TABLE_IFINDEX
: {
1613 _cleanup_free_
char *p
= NULL
;
1614 char name
[IF_NAMESIZE
+ 1];
1616 if (format_ifname(d
->ifindex
, name
)) {
1621 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1625 d
->formatted
= TAKE_PTR(p
);
1630 case TABLE_IN6_ADDR
: {
1631 _cleanup_free_
char *p
= NULL
;
1633 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1634 &d
->address
, &p
) < 0)
1637 d
->formatted
= TAKE_PTR(p
);
1644 p
= new(char, SD_ID128_STRING_MAX
);
1648 d
->formatted
= sd_id128_to_string(d
->id128
, p
);
1655 p
= new(char, ID128_UUID_STRING_MAX
);
1659 d
->formatted
= id128_to_uuid_string(d
->id128
, p
);
1664 _cleanup_free_
char *p
= NULL
;
1666 if (!uid_is_valid(d
->uid
))
1669 p
= new(char, DECIMAL_STR_WIDTH(d
->uid
) + 1);
1673 sprintf(p
, UID_FMT
, d
->uid
);
1674 d
->formatted
= TAKE_PTR(p
);
1679 _cleanup_free_
char *p
= NULL
;
1681 if (!gid_is_valid(d
->gid
))
1684 p
= new(char, DECIMAL_STR_WIDTH(d
->gid
) + 1);
1688 sprintf(p
, GID_FMT
, d
->gid
);
1689 d
->formatted
= TAKE_PTR(p
);
1694 _cleanup_free_
char *p
= NULL
;
1696 if (!pid_is_valid(d
->pid
))
1699 p
= new(char, DECIMAL_STR_WIDTH(d
->pid
) + 1);
1703 sprintf(p
, PID_FMT
, d
->pid
);
1704 d
->formatted
= TAKE_PTR(p
);
1708 case TABLE_SIGNAL
: {
1709 _cleanup_free_
char *p
= NULL
;
1712 suffix
= signal_to_string(d
->int_val
);
1716 p
= strjoin("SIG", suffix
);
1720 d
->formatted
= TAKE_PTR(p
);
1725 assert_not_reached("Unexpected type?");
1728 return d
->formatted
;
1731 static int console_width_height(
1734 size_t *ret_height
) {
1736 size_t max_width
= 0, height
= 0;
1741 /* Determine the width and height in console character cells the specified string needs. */
1746 p
= strchr(s
, '\n');
1748 _cleanup_free_
char *c
= NULL
;
1750 c
= strndup(s
, p
- s
);
1754 k
= utf8_console_width(c
);
1757 k
= utf8_console_width(s
);
1766 } while (!isempty(s
));
1769 *ret_width
= max_width
;
1772 *ret_height
= height
;
1777 static int table_data_requested_width_height(
1780 size_t available_width
,
1785 _cleanup_free_
char *truncated
= NULL
;
1786 bool truncation_applied
= false;
1787 size_t width
, height
;
1792 t
= table_data_format(table
, d
, false, available_width
, &soft
);
1796 if (table
->cell_height_max
!= SIZE_MAX
) {
1797 r
= string_truncate_lines(t
, table
->cell_height_max
, &truncated
);
1801 truncation_applied
= true;
1806 r
= console_width_height(t
, &width
, &height
);
1810 if (d
->maximum_width
!= SIZE_MAX
&& width
> d
->maximum_width
)
1811 width
= d
->maximum_width
;
1813 if (width
< d
->minimum_width
)
1814 width
= d
->minimum_width
;
1819 *ret_height
= height
;
1820 if (have_soft
&& soft
)
1823 return truncation_applied
;
1826 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1827 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1828 _cleanup_free_
char *clickable
= NULL
;
1833 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1836 assert(percent
<= 100);
1838 old_length
= strlen(str
);
1841 r
= terminal_urlify(url
, str
, &clickable
);
1845 clickable_length
= strlen(clickable
);
1847 clickable_length
= old_length
;
1849 /* Determine current width on screen */
1851 while (p
< str
+ old_length
) {
1854 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1855 p
++, w
++; /* count invalid chars as 1 */
1859 p
= utf8_next_char(p
);
1860 w
+= unichar_iswide(c
) ? 2 : 1;
1863 /* Already wider than the target, if so, don't do anything */
1864 if (w
>= new_length
)
1865 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1867 /* How much spaces shall we add? An how much on the left side? */
1868 space
= new_length
- w
;
1869 lspace
= space
* percent
/ 100U;
1871 ret
= new(char, space
+ clickable_length
+ 1);
1875 for (size_t i
= 0; i
< lspace
; i
++)
1877 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1878 for (size_t i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1881 ret
[space
+ clickable_length
] = 0;
1885 static bool table_data_isempty(TableData
*d
) {
1888 if (d
->type
== TABLE_EMPTY
)
1891 /* Let's also consider an empty strv as truly empty. */
1892 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
1893 return strv_isempty(d
->strv
);
1895 /* Note that an empty string we do not consider empty here! */
1899 static const char* table_data_color(TableData
*d
) {
1905 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1906 if (table_data_isempty(d
))
1912 static const char* table_data_rgap_color(TableData
*d
) {
1916 return d
->rgap_color
;
1921 int table_print(Table
*t
, FILE *f
) {
1922 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1923 table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1925 _cleanup_free_
size_t *sorted
= NULL
;
1926 uint64_t *column_weight
, weight_sum
;
1934 /* Ensure we have no incomplete rows */
1935 assert(t
->n_cells
% t
->n_columns
== 0);
1937 n_rows
= t
->n_cells
/ t
->n_columns
;
1938 assert(n_rows
> 0); /* at least the header row must be complete */
1941 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1943 sorted
= new(size_t, n_rows
);
1947 for (size_t i
= 0; i
< n_rows
; i
++)
1948 sorted
[i
] = i
* t
->n_columns
;
1950 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1954 display_columns
= t
->n_display_map
;
1956 display_columns
= t
->n_columns
;
1958 assert(display_columns
> 0);
1960 minimum_width
= newa(size_t, display_columns
);
1961 maximum_width
= newa(size_t, display_columns
);
1962 requested_width
= newa(size_t, display_columns
);
1963 column_weight
= newa0(uint64_t, display_columns
);
1965 for (size_t j
= 0; j
< display_columns
; j
++) {
1966 minimum_width
[j
] = 1;
1967 maximum_width
[j
] = SIZE_MAX
;
1970 for (unsigned pass
= 0; pass
< 2; pass
++) {
1971 /* First pass: determine column sizes */
1973 for (size_t j
= 0; j
< display_columns
; j
++)
1974 requested_width
[j
] = SIZE_MAX
;
1976 bool any_soft
= false;
1978 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1981 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1982 * hence we don't care for sorted[] during the first pass. */
1983 row
= t
->data
+ i
* t
->n_columns
;
1985 for (size_t j
= 0; j
< display_columns
; j
++) {
1987 size_t req_width
, req_height
;
1989 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1991 r
= table_data_requested_width_height(t
, d
,
1992 width
? width
[j
] : SIZE_MAX
,
1993 &req_width
, &req_height
, &any_soft
);
1996 if (r
> 0) { /* Truncated because too many lines? */
1997 _cleanup_free_
char *last
= NULL
;
2000 /* If we are going to show only the first few lines of a cell that has
2001 * multiple make sure that we have enough space horizontally to show an
2002 * ellipsis. Hence, let's figure out the last line, and account for its
2003 * length plus ellipsis. */
2005 field
= table_data_format(t
, d
, false,
2006 width
? width
[j
] : SIZE_MAX
,
2011 assert_se(t
->cell_height_max
> 0);
2012 r
= string_extract_line(field
, t
->cell_height_max
-1, &last
);
2016 req_width
= MAX(req_width
,
2017 utf8_console_width(last
) +
2018 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS
)));
2021 /* Determine the biggest width that any cell in this column would like to have */
2022 if (requested_width
[j
] == SIZE_MAX
||
2023 requested_width
[j
] < req_width
)
2024 requested_width
[j
] = req_width
;
2026 /* Determine the minimum width any cell in this column needs */
2027 if (minimum_width
[j
] < d
->minimum_width
)
2028 minimum_width
[j
] = d
->minimum_width
;
2030 /* Determine the maximum width any cell in this column needs */
2031 if (d
->maximum_width
!= SIZE_MAX
&&
2032 (maximum_width
[j
] == SIZE_MAX
||
2033 maximum_width
[j
] > d
->maximum_width
))
2034 maximum_width
[j
] = d
->maximum_width
;
2036 /* Determine the full columns weight */
2037 column_weight
[j
] += d
->weight
;
2041 /* One space between each column */
2042 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
2044 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2046 for (size_t j
= 0; j
< display_columns
; j
++) {
2047 weight_sum
+= column_weight
[j
];
2049 table_minimum_width
+= minimum_width
[j
];
2051 if (maximum_width
[j
] == SIZE_MAX
)
2052 table_maximum_width
= SIZE_MAX
;
2054 table_maximum_width
+= maximum_width
[j
];
2056 table_requested_width
+= requested_width
[j
];
2059 /* Calculate effective table width */
2060 if (t
->width
!= 0 && t
->width
!= SIZE_MAX
)
2061 table_effective_width
= t
->width
;
2062 else if (t
->width
== 0 ||
2063 ((pass
> 0 || !any_soft
) && (pager_have() || !isatty(STDOUT_FILENO
))))
2064 table_effective_width
= table_requested_width
;
2066 table_effective_width
= MIN(table_requested_width
, columns());
2068 if (table_maximum_width
!= SIZE_MAX
&& table_effective_width
> table_maximum_width
)
2069 table_effective_width
= table_maximum_width
;
2071 if (table_effective_width
< table_minimum_width
)
2072 table_effective_width
= table_minimum_width
;
2075 width
= newa(size_t, display_columns
);
2077 if (table_effective_width
>= table_requested_width
) {
2080 /* We have extra room, let's distribute it among columns according to their weights. We first provide
2081 * each column with what it asked for and the distribute the rest. */
2083 extra
= table_effective_width
- table_requested_width
;
2085 for (size_t j
= 0; j
< display_columns
; j
++) {
2088 if (weight_sum
== 0)
2089 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
2091 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
2093 if (maximum_width
[j
] != SIZE_MAX
&& width
[j
] > maximum_width
[j
])
2094 width
[j
] = maximum_width
[j
];
2096 if (width
[j
] < minimum_width
[j
])
2097 width
[j
] = minimum_width
[j
];
2099 delta
= LESS_BY(width
[j
], requested_width
[j
]);
2101 /* Subtract what we just added from the rest */
2107 assert(weight_sum
>= column_weight
[j
]);
2108 weight_sum
-= column_weight
[j
];
2111 break; /* Every column should be happy, no need to repeat calculations. */
2113 /* We need to compress the table, columns can't get what they asked for. We first provide each column
2114 * with the minimum they need, and then distribute anything left. */
2115 bool finalize
= false;
2118 extra
= table_effective_width
- table_minimum_width
;
2120 for (size_t j
= 0; j
< display_columns
; j
++)
2121 width
[j
] = SIZE_MAX
;
2124 bool restart
= false;
2126 for (size_t j
= 0; j
< display_columns
; j
++) {
2129 /* Did this column already get something assigned? If so, let's skip to the next */
2130 if (width
[j
] != SIZE_MAX
)
2133 if (weight_sum
== 0)
2134 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
2136 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
2138 if (w
>= requested_width
[j
]) {
2139 /* Never give more than requested. If we hit a column like this, there's more
2140 * space to allocate to other columns which means we need to restart the
2141 * iteration. However, if we hit a column like this, let's assign it the space
2142 * it wanted for good early.*/
2144 w
= requested_width
[j
];
2147 } else if (!finalize
)
2152 assert(w
>= minimum_width
[j
]);
2153 delta
= w
- minimum_width
[j
];
2155 assert(delta
<= extra
);
2158 assert(weight_sum
>= column_weight
[j
]);
2159 weight_sum
-= column_weight
[j
];
2161 if (restart
&& !finalize
)
2172 if (!any_soft
) /* Some columns got less than requested. If some cells were "soft",
2173 * let's try to reformat them with the new widths. Otherwise, let's
2179 /* Second pass: show output */
2180 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
2181 size_t n_subline
= 0;
2186 row
= t
->data
+ sorted
[i
];
2188 row
= t
->data
+ i
* t
->n_columns
;
2191 const char *gap_color
= NULL
;
2192 more_sublines
= false;
2194 for (size_t j
= 0; j
< display_columns
; j
++) {
2195 _cleanup_free_
char *buffer
= NULL
, *extracted
= NULL
;
2196 bool lines_truncated
= false;
2197 const char *field
, *color
= NULL
;
2201 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2203 field
= table_data_format(t
, d
, false, width
[j
], NULL
);
2207 r
= string_extract_line(field
, n_subline
, &extracted
);
2211 /* There are more lines to come */
2212 if ((t
->cell_height_max
== SIZE_MAX
|| n_subline
+ 1 < t
->cell_height_max
))
2213 more_sublines
= true; /* There are more lines to come */
2215 lines_truncated
= true;
2220 l
= utf8_console_width(field
);
2222 /* Field is wider than allocated space. Let's ellipsize */
2224 buffer
= ellipsize(field
, width
[j
], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2225 lines_truncated
? 100 : d
->ellipsize_percent
);
2231 if (lines_truncated
) {
2232 _cleanup_free_
char *padded
= NULL
;
2234 /* We truncated more lines of this cell, let's add an
2235 * ellipsis. We first append it, but that might make our
2236 * string grow above what we have space for, hence ellipsize
2237 * right after. This will truncate the ellipsis and add a new
2240 padded
= strjoin(field
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
2244 buffer
= ellipsize(padded
, width
[j
], 100);
2249 l
= utf8_console_width(field
);
2253 _cleanup_free_
char *aligned
= NULL
;
2254 /* Field is shorter than allocated space. Let's align with spaces */
2256 aligned
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
2260 /* Drop trailing white spaces of last column when no cosmetics is set. */
2261 if (j
== display_columns
- 1 &&
2262 (!colors_enabled() || (!table_data_color(d
) && row
!= t
->data
)) &&
2263 (!urlify_enabled() || !d
->url
))
2264 delete_trailing_chars(aligned
, NULL
);
2266 free_and_replace(buffer
, aligned
);
2271 if (l
>= width
[j
] && d
->url
) {
2272 _cleanup_free_
char *clickable
= NULL
;
2274 r
= terminal_urlify(d
->url
, field
, &clickable
);
2278 free_and_replace(buffer
, clickable
);
2282 if (colors_enabled()) {
2284 fputs(gap_color
, f
);
2285 else if (row
== t
->data
) /* underline header line fully, including the column separator */
2286 fputs(ansi_underline(), f
);
2290 fputc(' ', f
); /* column separator left of cell */
2292 if (colors_enabled()) {
2293 color
= table_data_color(d
);
2295 /* Undo gap color */
2296 if (gap_color
|| (color
&& row
== t
->data
))
2297 fputs(ANSI_NORMAL
, f
);
2301 else if (gap_color
&& row
== t
->data
) /* underline header line cell */
2302 fputs(ansi_underline(), f
);
2307 if (colors_enabled() && (color
|| row
== t
->data
))
2308 fputs(ANSI_NORMAL
, f
);
2310 gap_color
= table_data_rgap_color(d
);
2315 } while (more_sublines
);
2318 return fflush_and_check(f
);
2321 int table_format(Table
*t
, char **ret
) {
2322 _cleanup_free_
char *buf
= NULL
;
2323 _cleanup_fclose_
FILE *f
= NULL
;
2327 f
= open_memstream_unlocked(&buf
, &sz
);
2331 r
= table_print(t
, f
);
2337 *ret
= TAKE_PTR(buf
);
2342 size_t table_get_rows(Table
*t
) {
2346 assert(t
->n_columns
> 0);
2347 return t
->n_cells
/ t
->n_columns
;
2350 size_t table_get_columns(Table
*t
) {
2354 assert(t
->n_columns
> 0);
2355 return t
->n_columns
;
2358 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
2360 assert(column
< t
->n_columns
);
2362 if (!t
->reverse_map
) {
2366 t
->reverse_map
= new0(bool, t
->n_columns
);
2367 if (!t
->reverse_map
)
2371 t
->reverse_map
[column
] = b
;
2375 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
2380 if (column
>= t
->n_columns
)
2383 i
= row
* t
->n_columns
+ column
;
2384 if (i
>= t
->n_cells
)
2387 return TABLE_INDEX_TO_CELL(i
);
2390 const void *table_get(Table
*t
, TableCell
*cell
) {
2395 d
= table_get_data(t
, cell
);
2402 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
2405 cell
= table_get_cell(t
, row
, column
);
2409 return table_get(t
, cell
);
2412 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
2417 return json_variant_new_null(ret
);
2421 return json_variant_new_string(ret
, d
->string
);
2424 case TABLE_STRV_WRAPPED
:
2425 return json_variant_new_array_strv(ret
, d
->strv
);
2428 return json_variant_new_boolean(ret
, d
->boolean
);
2430 case TABLE_TIMESTAMP
:
2431 case TABLE_TIMESTAMP_UTC
:
2432 case TABLE_TIMESTAMP_RELATIVE
:
2433 if (d
->timestamp
== USEC_INFINITY
)
2434 return json_variant_new_null(ret
);
2436 return json_variant_new_unsigned(ret
, d
->timestamp
);
2438 case TABLE_TIMESPAN
:
2439 case TABLE_TIMESPAN_MSEC
:
2440 if (d
->timespan
== USEC_INFINITY
)
2441 return json_variant_new_null(ret
);
2443 return json_variant_new_unsigned(ret
, d
->timespan
);
2447 if (d
->size
== UINT64_MAX
)
2448 return json_variant_new_null(ret
);
2450 return json_variant_new_unsigned(ret
, d
->size
);
2453 return json_variant_new_integer(ret
, d
->int_val
);
2456 return json_variant_new_integer(ret
, d
->int8
);
2459 return json_variant_new_integer(ret
, d
->int16
);
2462 return json_variant_new_integer(ret
, d
->int32
);
2465 return json_variant_new_integer(ret
, d
->int64
);
2468 return json_variant_new_unsigned(ret
, d
->uint_val
);
2471 return json_variant_new_unsigned(ret
, d
->uint8
);
2474 return json_variant_new_unsigned(ret
, d
->uint16
);
2477 return json_variant_new_unsigned(ret
, d
->uint32
);
2480 return json_variant_new_unsigned(ret
, d
->uint64
);
2483 return json_variant_new_integer(ret
, d
->percent
);
2486 if (d
->ifindex
<= 0)
2487 return json_variant_new_null(ret
);
2489 return json_variant_new_integer(ret
, d
->ifindex
);
2492 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
2494 case TABLE_IN6_ADDR
:
2495 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
2498 char buf
[SD_ID128_STRING_MAX
];
2499 return json_variant_new_string(ret
, sd_id128_to_string(d
->id128
, buf
));
2503 char buf
[ID128_UUID_STRING_MAX
];
2504 return json_variant_new_string(ret
, id128_to_uuid_string(d
->id128
, buf
));
2508 if (!uid_is_valid(d
->uid
))
2509 return json_variant_new_null(ret
);
2511 return json_variant_new_integer(ret
, d
->uid
);
2514 if (!gid_is_valid(d
->gid
))
2515 return json_variant_new_null(ret
);
2517 return json_variant_new_integer(ret
, d
->gid
);
2520 if (!pid_is_valid(d
->pid
))
2521 return json_variant_new_null(ret
);
2523 return json_variant_new_integer(ret
, d
->pid
);
2526 if (!SIGNAL_VALID(d
->int_val
))
2527 return json_variant_new_null(ret
);
2529 return json_variant_new_integer(ret
, d
->int_val
);
2536 static char* string_to_json_field_name(const char *f
) {
2537 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2538 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2539 * underscores and leave everything as is. */
2541 char *c
= strdup(f
);
2545 for (char *x
= c
; *x
; x
++)
2552 int table_to_json(Table
*t
, JsonVariant
**ret
) {
2553 JsonVariant
**rows
= NULL
, **elements
= NULL
;
2554 _cleanup_free_
size_t *sorted
= NULL
;
2555 size_t n_rows
, display_columns
;
2560 /* Ensure we have no incomplete rows */
2561 assert(t
->n_cells
% t
->n_columns
== 0);
2563 n_rows
= t
->n_cells
/ t
->n_columns
;
2564 assert(n_rows
> 0); /* at least the header row must be complete */
2567 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2569 sorted
= new(size_t, n_rows
);
2575 for (size_t i
= 0; i
< n_rows
; i
++)
2576 sorted
[i
] = i
* t
->n_columns
;
2578 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2582 display_columns
= t
->n_display_map
;
2584 display_columns
= t
->n_columns
;
2585 assert(display_columns
> 0);
2587 elements
= new0(JsonVariant
*, display_columns
* 2);
2593 for (size_t j
= 0; j
< display_columns
; j
++) {
2594 _cleanup_free_
char *mangled
= NULL
;
2595 const char *formatted
;
2598 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2600 /* Field names must be strings, hence format whatever we got here as a string first */
2601 formatted
= table_data_format(t
, d
, true, SIZE_MAX
, NULL
);
2607 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2608 mangled
= string_to_json_field_name(formatted
);
2614 r
= json_variant_new_string(elements
+ j
*2, mangled
);
2619 rows
= new0(JsonVariant
*, n_rows
-1);
2625 for (size_t i
= 1; i
< n_rows
; i
++) {
2629 row
= t
->data
+ sorted
[i
];
2631 row
= t
->data
+ i
* t
->n_columns
;
2633 for (size_t j
= 0; j
< display_columns
; j
++) {
2637 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2640 elements
[k
] = json_variant_unref(elements
[k
]);
2642 r
= table_data_to_json(d
, elements
+ k
);
2647 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2652 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2656 json_variant_unref_many(rows
, n_rows
-1);
2661 json_variant_unref_many(elements
, display_columns
*2);
2668 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2669 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2674 if (flags
& JSON_FORMAT_OFF
) /* If JSON output is turned off, use regular output */
2675 return table_print(t
, f
);
2680 r
= table_to_json(t
, &v
);
2684 json_variant_dump(v
, flags
, f
, NULL
);
2686 return fflush_and_check(f
);
2689 int table_print_with_pager(
2691 JsonFormatFlags json_format_flags
,
2692 PagerFlags pager_flags
,
2700 /* A all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
2701 * the table header and logs about any error. */
2703 if (json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
2704 (void) pager_open(pager_flags
);
2706 saved_header
= t
->header
;
2707 t
->header
= show_header
;
2708 r
= table_print_json(t
, stdout
, json_format_flags
);
2709 t
->header
= saved_header
;
2711 return table_log_print_error(r
);