1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
9 #include "format-table.h"
10 #include "format-util.h"
12 #include "in-addr-util.h"
13 #include "memory-util.h"
15 #include "parse-util.h"
16 #include "pretty-print.h"
17 #include "sort-util.h"
18 #include "string-util.h"
20 #include "terminal-util.h"
21 #include "time-util.h"
25 #define DEFAULT_WEIGHT 100
28 A few notes on implementation details:
30 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
31 table. It can be easily converted to an index number and back.
33 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
34 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
35 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
36 outside only sees Table and TableCell.
38 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
41 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
42 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
43 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
44 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
46 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
47 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
48 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
49 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
52 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
53 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
54 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
57 typedef struct TableData
{
61 size_t minimum_width
; /* minimum width for the column */
62 size_t maximum_width
; /* maximum width for the column */
63 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
64 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
65 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
67 bool uppercase
; /* Uppercase string on display */
69 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 */
70 char *url
; /* A URL to use for a clickable hyperlink */
71 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
74 uint8_t data
[0]; /* data is generic array */
86 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
88 union in_addr_union address
;
89 /* … add more here as we start supporting more cell data types … */
93 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
98 i
= PTR_TO_SIZE(cell
);
104 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
105 assert(index
!= (size_t) -1);
106 return SIZE_TO_PTR(index
+ 1);
113 bool header
; /* Whether to show the header row? */
114 size_t width
; /* If != (size_t) -1 the width to format this table in */
119 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 */
120 size_t n_display_map
;
122 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
128 Table
*table_new_raw(size_t n_columns
) {
129 _cleanup_(table_unrefp
) Table
*t
= NULL
;
131 assert(n_columns
> 0);
137 *t
= (struct Table
) {
138 .n_columns
= n_columns
,
140 .width
= (size_t) -1,
146 Table
*table_new_internal(const char *first_header
, ...) {
147 _cleanup_(table_unrefp
) Table
*t
= NULL
;
148 size_t n_columns
= 1;
153 assert(first_header
);
155 va_start(ap
, first_header
);
157 h
= va_arg(ap
, const char*);
165 t
= table_new_raw(n_columns
);
169 va_start(ap
, first_header
);
170 for (h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
173 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
179 /* Make the table header uppercase */
180 r
= table_set_uppercase(t
, cell
, true);
188 assert(t
->n_columns
== t
->n_cells
);
192 static TableData
*table_data_free(TableData
*d
) {
201 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
202 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
204 Table
*table_unref(Table
*t
) {
210 for (i
= 0; i
< t
->n_cells
; i
++)
211 table_data_unref(t
->data
[i
]);
214 free(t
->display_map
);
216 free(t
->reverse_map
);
221 static size_t table_data_size(TableDataType type
, const void *data
) {
229 return strlen(data
) + 1;
234 case TABLE_TIMESTAMP
:
235 case TABLE_TIMESTAMP_UTC
:
236 case TABLE_TIMESTAMP_RELATIVE
:
238 case TABLE_TIMESPAN_MSEC
:
239 return sizeof(usec_t
);
245 return sizeof(uint64_t);
249 return sizeof(uint32_t);
258 return sizeof(struct in_addr
);
261 return sizeof(struct in6_addr
);
264 assert_not_reached("Uh? Unexpected cell type");
268 static bool table_data_matches(
272 size_t minimum_width
,
273 size_t maximum_width
,
275 unsigned align_percent
,
276 unsigned ellipsize_percent
) {
284 if (d
->minimum_width
!= minimum_width
)
287 if (d
->maximum_width
!= maximum_width
)
290 if (d
->weight
!= weight
)
293 if (d
->align_percent
!= align_percent
)
296 if (d
->ellipsize_percent
!= ellipsize_percent
)
299 /* If a color/url/uppercase flag is set, refuse to merge */
307 k
= table_data_size(type
, data
);
308 l
= table_data_size(d
->type
, d
->data
);
313 return memcmp_safe(data
, d
->data
, l
) == 0;
316 static TableData
*table_data_new(
319 size_t minimum_width
,
320 size_t maximum_width
,
322 unsigned align_percent
,
323 unsigned ellipsize_percent
) {
328 data_size
= table_data_size(type
, data
);
330 d
= malloc0(offsetof(TableData
, data
) + data_size
);
336 d
->minimum_width
= minimum_width
;
337 d
->maximum_width
= maximum_width
;
339 d
->align_percent
= align_percent
;
340 d
->ellipsize_percent
= ellipsize_percent
;
341 memcpy_safe(d
->data
, data
, data_size
);
346 int table_add_cell_full(
348 TableCell
**ret_cell
,
351 size_t minimum_width
,
352 size_t maximum_width
,
354 unsigned align_percent
,
355 unsigned ellipsize_percent
) {
357 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
362 assert(type
< _TABLE_DATA_TYPE_MAX
);
364 /* Determine the cell adjacent to the current one, but one row up */
365 if (t
->n_cells
>= t
->n_columns
)
366 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
370 /* If formatting parameters are left unspecified, copy from the previous row */
371 if (minimum_width
== (size_t) -1)
372 minimum_width
= p
? p
->minimum_width
: 1;
374 if (weight
== (unsigned) -1)
375 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
377 if (align_percent
== (unsigned) -1)
378 align_percent
= p
? p
->align_percent
: 0;
380 if (ellipsize_percent
== (unsigned) -1)
381 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
383 assert(align_percent
<= 100);
384 assert(ellipsize_percent
<= 100);
386 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
387 * formatting. Let's see if we can reuse the cell data and ref it once more. */
389 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
390 d
= table_data_ref(p
);
392 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
397 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
401 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
403 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
408 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
409 _cleanup_free_
char *buffer
= NULL
;
413 va_start(ap
, format
);
414 r
= vasprintf(&buffer
, format
, ap
);
419 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
422 int table_dup_cell(Table
*t
, TableCell
*cell
) {
427 /* Add the data of the specified cell a second time as a new cell to the end. */
429 i
= TABLE_CELL_TO_INDEX(cell
);
433 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
436 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
440 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
441 _cleanup_free_
char *curl
= NULL
;
447 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
448 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
450 i
= TABLE_CELL_TO_INDEX(cell
);
454 assert_se(od
= t
->data
[i
]);
458 assert(od
->n_ref
> 1);
461 curl
= strdup(od
->url
);
473 od
->ellipsize_percent
);
477 nd
->color
= od
->color
;
478 nd
->url
= TAKE_PTR(curl
);
479 nd
->uppercase
= od
->uppercase
;
481 table_data_unref(od
);
484 assert(nd
->n_ref
== 1);
489 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
495 /* Get the data object of the specified cell, or NULL if it doesn't exist */
497 i
= TABLE_CELL_TO_INDEX(cell
);
502 assert(t
->data
[i
]->n_ref
> 0);
507 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
513 if (minimum_width
== (size_t) -1)
516 r
= table_dedup_cell(t
, cell
);
520 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
524 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
530 r
= table_dedup_cell(t
, cell
);
534 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
538 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
544 if (weight
== (unsigned) -1)
545 weight
= DEFAULT_WEIGHT
;
547 r
= table_dedup_cell(t
, cell
);
551 table_get_data(t
, cell
)->weight
= weight
;
555 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
561 if (percent
== (unsigned) -1)
564 assert(percent
<= 100);
566 r
= table_dedup_cell(t
, cell
);
570 table_get_data(t
, cell
)->align_percent
= percent
;
574 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
580 if (percent
== (unsigned) -1)
583 assert(percent
<= 100);
585 r
= table_dedup_cell(t
, cell
);
589 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
593 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
599 r
= table_dedup_cell(t
, cell
);
603 table_get_data(t
, cell
)->color
= empty_to_null(color
);
607 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
608 _cleanup_free_
char *copy
= NULL
;
620 r
= table_dedup_cell(t
, cell
);
624 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
627 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
634 r
= table_dedup_cell(t
, cell
);
638 assert_se(d
= table_get_data(t
, cell
));
640 if (d
->uppercase
== b
)
643 d
->formatted
= mfree(d
->formatted
);
648 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
649 _cleanup_free_
char *curl
= NULL
;
656 i
= TABLE_CELL_TO_INDEX(cell
);
660 assert_se(od
= t
->data
[i
]);
663 curl
= strdup(od
->url
);
675 od
->ellipsize_percent
);
679 nd
->color
= od
->color
;
680 nd
->url
= TAKE_PTR(curl
);
681 nd
->uppercase
= od
->uppercase
;
683 table_data_unref(od
);
689 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
692 TableCell
*last_cell
= NULL
;
696 assert(first_type
>= 0);
697 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
701 va_start(ap
, first_type
);
716 union in_addr_union address
;
726 data
= va_arg(ap
, const char *);
730 buffer
.b
= va_arg(ap
, int);
734 case TABLE_TIMESTAMP
:
735 case TABLE_TIMESTAMP_UTC
:
736 case TABLE_TIMESTAMP_RELATIVE
:
738 case TABLE_TIMESPAN_MSEC
:
739 buffer
.usec
= va_arg(ap
, usec_t
);
745 buffer
.size
= va_arg(ap
, uint64_t);
750 buffer
.int_val
= va_arg(ap
, int);
751 data
= &buffer
.int_val
;
755 buffer
.int32
= va_arg(ap
, int32_t);
756 data
= &buffer
.int32
;
760 buffer
.int64
= va_arg(ap
, int64_t);
761 data
= &buffer
.int64
;
765 buffer
.uint_val
= va_arg(ap
, unsigned);
766 data
= &buffer
.uint_val
;
770 buffer
.uint32
= va_arg(ap
, uint32_t);
771 data
= &buffer
.uint32
;
775 buffer
.uint64
= va_arg(ap
, uint64_t);
776 data
= &buffer
.uint64
;
780 buffer
.percent
= va_arg(ap
, int);
781 data
= &buffer
.percent
;
785 buffer
.ifindex
= va_arg(ap
, int);
786 data
= &buffer
.ifindex
;
790 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
791 data
= &buffer
.address
.in
;
795 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
796 data
= &buffer
.address
.in6
;
799 case TABLE_SET_MINIMUM_WIDTH
: {
800 size_t w
= va_arg(ap
, size_t);
802 r
= table_set_minimum_width(t
, last_cell
, w
);
806 case TABLE_SET_MAXIMUM_WIDTH
: {
807 size_t w
= va_arg(ap
, size_t);
808 r
= table_set_maximum_width(t
, last_cell
, w
);
812 case TABLE_SET_WEIGHT
: {
813 unsigned w
= va_arg(ap
, unsigned);
814 r
= table_set_weight(t
, last_cell
, w
);
818 case TABLE_SET_ALIGN_PERCENT
: {
819 unsigned p
= va_arg(ap
, unsigned);
820 r
= table_set_align_percent(t
, last_cell
, p
);
824 case TABLE_SET_ELLIPSIZE_PERCENT
: {
825 unsigned p
= va_arg(ap
, unsigned);
826 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
830 case TABLE_SET_COLOR
: {
831 const char *c
= va_arg(ap
, const char*);
832 r
= table_set_color(t
, last_cell
, c
);
836 case TABLE_SET_URL
: {
837 const char *u
= va_arg(ap
, const char*);
838 r
= table_set_url(t
, last_cell
, u
);
842 case TABLE_SET_UPPERCASE
: {
843 int u
= va_arg(ap
, int);
844 r
= table_set_uppercase(t
, last_cell
, u
);
848 case _TABLE_DATA_TYPE_MAX
:
849 /* Used as end marker */
854 assert_not_reached("Uh? Unexpected data type.");
857 if (type
< _TABLE_DATA_TYPE_MAX
)
858 r
= table_add_cell(t
, &last_cell
, type
, data
);
865 type
= va_arg(ap
, TableDataType
);
869 void table_set_header(Table
*t
, bool b
) {
875 void table_set_width(Table
*t
, size_t width
) {
881 int table_set_display(Table
*t
, size_t first_column
, ...) {
882 size_t allocated
, column
;
887 allocated
= t
->n_display_map
;
888 column
= first_column
;
890 va_start(ap
, first_column
);
892 assert(column
< t
->n_columns
);
894 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
899 t
->display_map
[t
->n_display_map
++] = column
;
901 column
= va_arg(ap
, size_t);
902 if (column
== (size_t) -1)
911 int table_set_sort(Table
*t
, size_t first_column
, ...) {
912 size_t allocated
, column
;
917 allocated
= t
->n_sort_map
;
918 column
= first_column
;
920 va_start(ap
, first_column
);
922 assert(column
< t
->n_columns
);
924 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
929 t
->sort_map
[t
->n_sort_map
++] = column
;
931 column
= va_arg(ap
, size_t);
932 if (column
== (size_t) -1)
940 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
944 if (a
->type
== b
->type
) {
946 /* We only define ordering for cells of the same data type. If cells with different data types are
947 * compared we follow the order the cells were originally added in */
952 return strcmp(a
->string
, b
->string
);
955 if (!a
->boolean
&& b
->boolean
)
957 if (a
->boolean
&& !b
->boolean
)
961 case TABLE_TIMESTAMP
:
962 case TABLE_TIMESTAMP_UTC
:
963 case TABLE_TIMESTAMP_RELATIVE
:
964 return CMP(a
->timestamp
, b
->timestamp
);
967 case TABLE_TIMESPAN_MSEC
:
968 return CMP(a
->timespan
, b
->timespan
);
972 return CMP(a
->size
, b
->size
);
975 return CMP(a
->int_val
, b
->int_val
);
978 return CMP(a
->int32
, b
->int32
);
981 return CMP(a
->int64
, b
->int64
);
984 return CMP(a
->uint_val
, b
->uint_val
);
987 return CMP(a
->uint32
, b
->uint32
);
990 return CMP(a
->uint64
, b
->uint64
);
993 return CMP(a
->percent
, b
->percent
);
996 return CMP(a
->ifindex
, b
->ifindex
);
999 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1001 case TABLE_IN6_ADDR
:
1002 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1009 /* Generic fallback using the original order in which the cells where added. */
1010 return CMP(index_a
, index_b
);
1013 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1018 assert(t
->sort_map
);
1020 /* Make sure the header stays at the beginning */
1021 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1023 if (*a
< t
->n_columns
)
1025 if (*b
< t
->n_columns
)
1028 /* Order other lines by the sorting map */
1029 for (i
= 0; i
< t
->n_sort_map
; i
++) {
1032 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1033 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1035 r
= cell_data_compare(d
, *a
, dd
, *b
);
1037 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1040 /* Order identical lines by the order there were originally added in */
1044 static const char *table_data_format(TableData
*d
) {
1048 return d
->formatted
;
1058 d
->formatted
= new(char, strlen(d
->string
) + 1);
1062 for (p
= d
->string
, q
= d
->formatted
; *p
; p
++, q
++)
1063 *q
= (char) toupper((unsigned char) *p
);
1066 return d
->formatted
;
1072 return yes_no(d
->boolean
);
1074 case TABLE_TIMESTAMP
:
1075 case TABLE_TIMESTAMP_UTC
:
1076 case TABLE_TIMESTAMP_RELATIVE
: {
1077 _cleanup_free_
char *p
;
1080 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1084 if (d
->type
== TABLE_TIMESTAMP
)
1085 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1086 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1087 ret
= format_timestamp_utc(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1089 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1093 d
->formatted
= TAKE_PTR(p
);
1097 case TABLE_TIMESPAN
:
1098 case TABLE_TIMESPAN_MSEC
: {
1099 _cleanup_free_
char *p
;
1101 p
= new(char, FORMAT_TIMESPAN_MAX
);
1105 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1106 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1109 d
->formatted
= TAKE_PTR(p
);
1114 _cleanup_free_
char *p
;
1116 p
= new(char, FORMAT_BYTES_MAX
);
1120 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1123 d
->formatted
= TAKE_PTR(p
);
1128 _cleanup_free_
char *p
;
1131 p
= new(char, FORMAT_BYTES_MAX
+2);
1135 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1139 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1141 d
->formatted
= TAKE_PTR(p
);
1146 _cleanup_free_
char *p
;
1148 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1152 sprintf(p
, "%i", d
->int_val
);
1153 d
->formatted
= TAKE_PTR(p
);
1158 _cleanup_free_
char *p
;
1160 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1164 sprintf(p
, "%" PRIi32
, d
->int32
);
1165 d
->formatted
= TAKE_PTR(p
);
1170 _cleanup_free_
char *p
;
1172 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1176 sprintf(p
, "%" PRIi64
, d
->int64
);
1177 d
->formatted
= TAKE_PTR(p
);
1182 _cleanup_free_
char *p
;
1184 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1188 sprintf(p
, "%u", d
->uint_val
);
1189 d
->formatted
= TAKE_PTR(p
);
1193 case TABLE_UINT32
: {
1194 _cleanup_free_
char *p
;
1196 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1200 sprintf(p
, "%" PRIu32
, d
->uint32
);
1201 d
->formatted
= TAKE_PTR(p
);
1205 case TABLE_UINT64
: {
1206 _cleanup_free_
char *p
;
1208 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1212 sprintf(p
, "%" PRIu64
, d
->uint64
);
1213 d
->formatted
= TAKE_PTR(p
);
1217 case TABLE_PERCENT
: {
1218 _cleanup_free_
char *p
;
1220 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1224 sprintf(p
, "%i%%" , d
->percent
);
1225 d
->formatted
= TAKE_PTR(p
);
1229 case TABLE_IFINDEX
: {
1230 _cleanup_free_
char *p
;
1231 char name
[IF_NAMESIZE
+ 1];
1233 if (format_ifname(d
->ifindex
, name
)) {
1238 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1242 d
->formatted
= TAKE_PTR(p
);
1247 case TABLE_IN6_ADDR
: {
1248 _cleanup_free_
char *p
= NULL
;
1250 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1251 &d
->address
, &p
) < 0)
1254 d
->formatted
= TAKE_PTR(p
);
1259 assert_not_reached("Unexpected type?");
1262 return d
->formatted
;
1265 static int table_data_requested_width(TableData
*d
, size_t *ret
) {
1269 t
= table_data_format(d
);
1273 l
= utf8_console_width(t
);
1274 if (l
== (size_t) -1)
1277 if (d
->maximum_width
!= (size_t) -1 && l
> d
->maximum_width
)
1278 l
= d
->maximum_width
;
1280 if (l
< d
->minimum_width
)
1281 l
= d
->minimum_width
;
1287 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1288 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1289 _cleanup_free_
char *clickable
= NULL
;
1295 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1298 assert(percent
<= 100);
1300 old_length
= strlen(str
);
1303 r
= terminal_urlify(url
, str
, &clickable
);
1307 clickable_length
= strlen(clickable
);
1309 clickable_length
= old_length
;
1311 /* Determine current width on screen */
1313 while (p
< str
+ old_length
) {
1316 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1317 p
++, w
++; /* count invalid chars as 1 */
1321 p
= utf8_next_char(p
);
1322 w
+= unichar_iswide(c
) ? 2 : 1;
1325 /* Already wider than the target, if so, don't do anything */
1326 if (w
>= new_length
)
1327 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1329 /* How much spaces shall we add? An how much on the left side? */
1330 space
= new_length
- w
;
1331 lspace
= space
* percent
/ 100U;
1333 ret
= new(char, space
+ clickable_length
+ 1);
1337 for (i
= 0; i
< lspace
; i
++)
1339 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1340 for (i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1343 ret
[space
+ clickable_length
] = 0;
1347 int table_print(Table
*t
, FILE *f
) {
1348 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1349 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1351 _cleanup_free_
size_t *sorted
= NULL
;
1352 uint64_t *column_weight
, weight_sum
;
1360 /* Ensure we have no incomplete rows */
1361 assert(t
->n_cells
% t
->n_columns
== 0);
1363 n_rows
= t
->n_cells
/ t
->n_columns
;
1364 assert(n_rows
> 0); /* at least the header row must be complete */
1367 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1369 sorted
= new(size_t, n_rows
);
1373 for (i
= 0; i
< n_rows
; i
++)
1374 sorted
[i
] = i
* t
->n_columns
;
1376 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1380 display_columns
= t
->n_display_map
;
1382 display_columns
= t
->n_columns
;
1384 assert(display_columns
> 0);
1386 minimum_width
= newa(size_t, display_columns
);
1387 maximum_width
= newa(size_t, display_columns
);
1388 requested_width
= newa(size_t, display_columns
);
1389 width
= newa(size_t, display_columns
);
1390 column_weight
= newa0(uint64_t, display_columns
);
1392 for (j
= 0; j
< display_columns
; j
++) {
1393 minimum_width
[j
] = 1;
1394 maximum_width
[j
] = (size_t) -1;
1395 requested_width
[j
] = (size_t) -1;
1398 /* First pass: determine column sizes */
1399 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1402 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1403 * hence we don't care for sorted[] during the first pass. */
1404 row
= t
->data
+ i
* t
->n_columns
;
1406 for (j
= 0; j
< display_columns
; j
++) {
1410 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1412 r
= table_data_requested_width(d
, &req
);
1416 /* Determine the biggest width that any cell in this column would like to have */
1417 if (requested_width
[j
] == (size_t) -1 ||
1418 requested_width
[j
] < req
)
1419 requested_width
[j
] = req
;
1421 /* Determine the minimum width any cell in this column needs */
1422 if (minimum_width
[j
] < d
->minimum_width
)
1423 minimum_width
[j
] = d
->minimum_width
;
1425 /* Determine the maximum width any cell in this column needs */
1426 if (d
->maximum_width
!= (size_t) -1 &&
1427 (maximum_width
[j
] == (size_t) -1 ||
1428 maximum_width
[j
] > d
->maximum_width
))
1429 maximum_width
[j
] = d
->maximum_width
;
1431 /* Determine the full columns weight */
1432 column_weight
[j
] += d
->weight
;
1436 /* One space between each column */
1437 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1439 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1441 for (j
= 0; j
< display_columns
; j
++) {
1442 weight_sum
+= column_weight
[j
];
1444 table_minimum_width
+= minimum_width
[j
];
1446 if (maximum_width
[j
] == (size_t) -1)
1447 table_maximum_width
= (size_t) -1;
1449 table_maximum_width
+= maximum_width
[j
];
1451 table_requested_width
+= requested_width
[j
];
1454 /* Calculate effective table width */
1455 if (t
->width
== (size_t) -1)
1456 table_effective_width
= pager_have() ? table_requested_width
: MIN(table_requested_width
, columns());
1458 table_effective_width
= t
->width
;
1460 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1461 table_effective_width
= table_maximum_width
;
1463 if (table_effective_width
< table_minimum_width
)
1464 table_effective_width
= table_minimum_width
;
1466 if (table_effective_width
>= table_requested_width
) {
1469 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1470 * each column with what it asked for and the distribute the rest. */
1472 extra
= table_effective_width
- table_requested_width
;
1474 for (j
= 0; j
< display_columns
; j
++) {
1477 if (weight_sum
== 0)
1478 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1480 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1482 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1483 width
[j
] = maximum_width
[j
];
1485 if (width
[j
] < minimum_width
[j
])
1486 width
[j
] = minimum_width
[j
];
1488 assert(width
[j
] >= requested_width
[j
]);
1489 delta
= width
[j
] - requested_width
[j
];
1491 /* Subtract what we just added from the rest */
1497 assert(weight_sum
>= column_weight
[j
]);
1498 weight_sum
-= column_weight
[j
];
1502 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1503 * with the minimum they need, and then distribute anything left. */
1504 bool finalize
= false;
1507 extra
= table_effective_width
- table_minimum_width
;
1509 for (j
= 0; j
< display_columns
; j
++)
1510 width
[j
] = (size_t) -1;
1513 bool restart
= false;
1515 for (j
= 0; j
< display_columns
; j
++) {
1518 /* Did this column already get something assigned? If so, let's skip to the next */
1519 if (width
[j
] != (size_t) -1)
1522 if (weight_sum
== 0)
1523 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
1525 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1527 if (w
>= requested_width
[j
]) {
1528 /* Never give more than requested. If we hit a column like this, there's more
1529 * space to allocate to other columns which means we need to restart the
1530 * iteration. However, if we hit a column like this, let's assign it the space
1531 * it wanted for good early.*/
1533 w
= requested_width
[j
];
1536 } else if (!finalize
)
1541 assert(w
>= minimum_width
[j
]);
1542 delta
= w
- minimum_width
[j
];
1544 assert(delta
<= extra
);
1547 assert(weight_sum
>= column_weight
[j
]);
1548 weight_sum
-= column_weight
[j
];
1550 if (restart
&& !finalize
)
1562 /* Second pass: show output */
1563 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1567 row
= t
->data
+ sorted
[i
];
1569 row
= t
->data
+ i
* t
->n_columns
;
1571 for (j
= 0; j
< display_columns
; j
++) {
1572 _cleanup_free_
char *buffer
= NULL
;
1577 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1579 field
= table_data_format(d
);
1583 l
= utf8_console_width(field
);
1585 /* Field is wider than allocated space. Let's ellipsize */
1587 buffer
= ellipsize(field
, width
[j
], d
->ellipsize_percent
);
1593 } else if (l
< width
[j
]) {
1594 /* Field is shorter than allocated space. Let's align with spaces */
1596 buffer
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
1603 if (l
>= width
[j
] && d
->url
) {
1604 _cleanup_free_
char *clickable
= NULL
;
1606 r
= terminal_urlify(d
->url
, field
, &clickable
);
1610 free_and_replace(buffer
, clickable
);
1614 if (row
== t
->data
) /* underline header line fully, including the column separator */
1615 fputs(ansi_underline(), f
);
1618 fputc(' ', f
); /* column separator */
1620 if (d
->color
&& colors_enabled()) {
1621 if (row
== t
->data
) /* first undo header underliner */
1622 fputs(ANSI_NORMAL
, f
);
1629 if (colors_enabled() && (d
->color
|| row
== t
->data
))
1630 fputs(ANSI_NORMAL
, f
);
1636 return fflush_and_check(f
);
1639 int table_format(Table
*t
, char **ret
) {
1640 _cleanup_fclose_
FILE *f
= NULL
;
1645 f
= open_memstream_unlocked(&buf
, &sz
);
1649 r
= table_print(t
, f
);
1660 size_t table_get_rows(Table
*t
) {
1664 assert(t
->n_columns
> 0);
1665 return t
->n_cells
/ t
->n_columns
;
1668 size_t table_get_columns(Table
*t
) {
1672 assert(t
->n_columns
> 0);
1673 return t
->n_columns
;
1676 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
1678 assert(column
< t
->n_columns
);
1680 if (!t
->reverse_map
) {
1684 t
->reverse_map
= new0(bool, t
->n_columns
);
1685 if (!t
->reverse_map
)
1689 t
->reverse_map
[column
] = b
;
1693 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
1698 if (column
>= t
->n_columns
)
1701 i
= row
* t
->n_columns
+ column
;
1702 if (i
>= t
->n_cells
)
1705 return TABLE_INDEX_TO_CELL(i
);
1708 const void *table_get(Table
*t
, TableCell
*cell
) {
1713 d
= table_get_data(t
, cell
);
1720 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
1723 cell
= table_get_cell(t
, row
, column
);
1727 return table_get(t
, cell
);
1730 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
1735 return json_variant_new_null(ret
);
1738 return json_variant_new_string(ret
, d
->string
);
1741 return json_variant_new_boolean(ret
, d
->boolean
);
1743 case TABLE_TIMESTAMP
:
1744 case TABLE_TIMESTAMP_UTC
:
1745 case TABLE_TIMESTAMP_RELATIVE
:
1746 if (d
->timestamp
== USEC_INFINITY
)
1747 return json_variant_new_null(ret
);
1749 return json_variant_new_unsigned(ret
, d
->timestamp
);
1751 case TABLE_TIMESPAN
:
1752 case TABLE_TIMESPAN_MSEC
:
1753 if (d
->timespan
== USEC_INFINITY
)
1754 return json_variant_new_null(ret
);
1756 return json_variant_new_unsigned(ret
, d
->timespan
);
1760 if (d
->size
== (size_t) -1)
1761 return json_variant_new_null(ret
);
1763 return json_variant_new_unsigned(ret
, d
->size
);
1766 return json_variant_new_integer(ret
, d
->int_val
);
1769 return json_variant_new_integer(ret
, d
->int32
);
1772 return json_variant_new_integer(ret
, d
->int64
);
1775 return json_variant_new_unsigned(ret
, d
->uint_val
);
1778 return json_variant_new_unsigned(ret
, d
->uint32
);
1781 return json_variant_new_unsigned(ret
, d
->uint64
);
1784 return json_variant_new_integer(ret
, d
->percent
);
1787 return json_variant_new_integer(ret
, d
->ifindex
);
1790 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
1792 case TABLE_IN6_ADDR
:
1793 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1800 int table_to_json(Table
*t
, JsonVariant
**ret
) {
1801 JsonVariant
**rows
= NULL
, **elements
= NULL
;
1802 _cleanup_free_
size_t *sorted
= NULL
;
1803 size_t n_rows
, i
, j
, display_columns
;
1808 /* Ensure we have no incomplete rows */
1809 assert(t
->n_cells
% t
->n_columns
== 0);
1811 n_rows
= t
->n_cells
/ t
->n_columns
;
1812 assert(n_rows
> 0); /* at least the header row must be complete */
1815 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1817 sorted
= new(size_t, n_rows
);
1823 for (i
= 0; i
< n_rows
; i
++)
1824 sorted
[i
] = i
* t
->n_columns
;
1826 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1830 display_columns
= t
->n_display_map
;
1832 display_columns
= t
->n_columns
;
1833 assert(display_columns
> 0);
1835 elements
= new0(JsonVariant
*, display_columns
* 2);
1841 for (j
= 0; j
< display_columns
; j
++) {
1844 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
1846 r
= table_data_to_json(d
, elements
+ j
*2);
1851 rows
= new0(JsonVariant
*, n_rows
-1);
1857 for (i
= 1; i
< n_rows
; i
++) {
1861 row
= t
->data
+ sorted
[i
];
1863 row
= t
->data
+ i
* t
->n_columns
;
1865 for (j
= 0; j
< display_columns
; j
++) {
1869 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1872 elements
[k
] = json_variant_unref(elements
[k
]);
1874 r
= table_data_to_json(d
, elements
+ k
);
1879 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
1884 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
1888 json_variant_unref_many(rows
, n_rows
-1);
1893 json_variant_unref_many(elements
, display_columns
*2);
1900 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
1901 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
1909 r
= table_to_json(t
, &v
);
1913 json_variant_dump(v
, flags
, f
, NULL
);
1915 return fflush_and_check(f
);