1 /* SPDX-License-Identifier: LGPL-2.1+ */
7 #include "alloc-util.h"
10 #include "format-table.h"
11 #include "format-util.h"
13 #include "in-addr-util.h"
14 #include "memory-util.h"
16 #include "parse-util.h"
17 #include "path-util.h"
18 #include "pretty-print.h"
19 #include "sort-util.h"
20 #include "string-util.h"
22 #include "terminal-util.h"
23 #include "time-util.h"
27 #define DEFAULT_WEIGHT 100
30 A few notes on implementation details:
32 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
33 table. It can be easily converted to an index number and back.
35 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
36 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
37 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
38 outside only sees Table and TableCell.
40 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
43 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
44 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
45 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
46 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
48 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
49 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
50 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
51 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
54 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
55 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
56 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
59 typedef struct TableData
{
63 size_t minimum_width
; /* minimum width for the column */
64 size_t maximum_width
; /* maximum width for the column */
65 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
66 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
67 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
69 bool uppercase
; /* Uppercase string on display */
71 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 */
72 char *url
; /* A URL to use for a clickable hyperlink */
73 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
76 uint8_t data
[0]; /* data is generic array */
92 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
94 union in_addr_union address
;
95 /* … add more here as we start supporting more cell data types … */
99 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
104 i
= PTR_TO_SIZE(cell
);
110 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
111 assert(index
!= (size_t) -1);
112 return SIZE_TO_PTR(index
+ 1);
119 bool header
; /* Whether to show the header row? */
120 size_t width
; /* If != (size_t) -1 the width to format this table in */
125 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 */
126 size_t n_display_map
;
128 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
136 Table
*table_new_raw(size_t n_columns
) {
137 _cleanup_(table_unrefp
) Table
*t
= NULL
;
139 assert(n_columns
> 0);
145 *t
= (struct Table
) {
146 .n_columns
= n_columns
,
148 .width
= (size_t) -1,
154 Table
*table_new_internal(const char *first_header
, ...) {
155 _cleanup_(table_unrefp
) Table
*t
= NULL
;
156 size_t n_columns
= 1;
161 assert(first_header
);
163 va_start(ap
, first_header
);
165 h
= va_arg(ap
, const char*);
173 t
= table_new_raw(n_columns
);
177 va_start(ap
, first_header
);
178 for (h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
181 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
187 /* Make the table header uppercase */
188 r
= table_set_uppercase(t
, cell
, true);
196 assert(t
->n_columns
== t
->n_cells
);
200 static TableData
*table_data_free(TableData
*d
) {
209 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
210 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
212 Table
*table_unref(Table
*t
) {
218 for (i
= 0; i
< t
->n_cells
; i
++)
219 table_data_unref(t
->data
[i
]);
222 free(t
->display_map
);
224 free(t
->reverse_map
);
225 free(t
->empty_string
);
230 static size_t table_data_size(TableDataType type
, const void *data
) {
239 return strlen(data
) + 1;
244 case TABLE_TIMESTAMP
:
245 case TABLE_TIMESTAMP_UTC
:
246 case TABLE_TIMESTAMP_RELATIVE
:
248 case TABLE_TIMESPAN_MSEC
:
249 return sizeof(usec_t
);
255 return sizeof(uint64_t);
259 return sizeof(uint32_t);
263 return sizeof(uint16_t);
267 return sizeof(uint8_t);
276 return sizeof(struct in_addr
);
279 return sizeof(struct in6_addr
);
282 assert_not_reached("Uh? Unexpected cell type");
286 static bool table_data_matches(
290 size_t minimum_width
,
291 size_t maximum_width
,
293 unsigned align_percent
,
294 unsigned ellipsize_percent
) {
302 if (d
->minimum_width
!= minimum_width
)
305 if (d
->maximum_width
!= maximum_width
)
308 if (d
->weight
!= weight
)
311 if (d
->align_percent
!= align_percent
)
314 if (d
->ellipsize_percent
!= ellipsize_percent
)
317 /* If a color/url/uppercase flag is set, refuse to merge */
325 k
= table_data_size(type
, data
);
326 l
= table_data_size(d
->type
, d
->data
);
331 return memcmp_safe(data
, d
->data
, l
) == 0;
334 static TableData
*table_data_new(
337 size_t minimum_width
,
338 size_t maximum_width
,
340 unsigned align_percent
,
341 unsigned ellipsize_percent
) {
346 data_size
= table_data_size(type
, data
);
348 d
= malloc0(offsetof(TableData
, data
) + data_size
);
354 d
->minimum_width
= minimum_width
;
355 d
->maximum_width
= maximum_width
;
357 d
->align_percent
= align_percent
;
358 d
->ellipsize_percent
= ellipsize_percent
;
359 memcpy_safe(d
->data
, data
, data_size
);
364 int table_add_cell_full(
366 TableCell
**ret_cell
,
369 size_t minimum_width
,
370 size_t maximum_width
,
372 unsigned align_percent
,
373 unsigned ellipsize_percent
) {
375 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
380 assert(type
< _TABLE_DATA_TYPE_MAX
);
382 /* Special rule: patch NULL data fields to the empty field */
386 /* Determine the cell adjacent to the current one, but one row up */
387 if (t
->n_cells
>= t
->n_columns
)
388 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
392 /* If formatting parameters are left unspecified, copy from the previous row */
393 if (minimum_width
== (size_t) -1)
394 minimum_width
= p
? p
->minimum_width
: 1;
396 if (weight
== (unsigned) -1)
397 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
399 if (align_percent
== (unsigned) -1)
400 align_percent
= p
? p
->align_percent
: 0;
402 if (ellipsize_percent
== (unsigned) -1)
403 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
405 assert(align_percent
<= 100);
406 assert(ellipsize_percent
<= 100);
408 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
409 * formatting. Let's see if we can reuse the cell data and ref it once more. */
411 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
412 d
= table_data_ref(p
);
414 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
419 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
423 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
425 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
430 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
431 _cleanup_free_
char *buffer
= NULL
;
435 va_start(ap
, format
);
436 r
= vasprintf(&buffer
, format
, ap
);
441 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
444 int table_fill_empty(Table
*t
, size_t until_column
) {
449 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
450 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
452 if (until_column
>= t
->n_columns
)
456 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
460 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
465 int table_dup_cell(Table
*t
, TableCell
*cell
) {
470 /* Add the data of the specified cell a second time as a new cell to the end. */
472 i
= TABLE_CELL_TO_INDEX(cell
);
476 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
479 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
483 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
484 _cleanup_free_
char *curl
= NULL
;
490 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
491 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
493 i
= TABLE_CELL_TO_INDEX(cell
);
497 assert_se(od
= t
->data
[i
]);
501 assert(od
->n_ref
> 1);
504 curl
= strdup(od
->url
);
516 od
->ellipsize_percent
);
520 nd
->color
= od
->color
;
521 nd
->url
= TAKE_PTR(curl
);
522 nd
->uppercase
= od
->uppercase
;
524 table_data_unref(od
);
527 assert(nd
->n_ref
== 1);
532 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
538 /* Get the data object of the specified cell, or NULL if it doesn't exist */
540 i
= TABLE_CELL_TO_INDEX(cell
);
545 assert(t
->data
[i
]->n_ref
> 0);
550 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
556 if (minimum_width
== (size_t) -1)
559 r
= table_dedup_cell(t
, cell
);
563 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
567 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
573 r
= table_dedup_cell(t
, cell
);
577 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
581 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
587 if (weight
== (unsigned) -1)
588 weight
= DEFAULT_WEIGHT
;
590 r
= table_dedup_cell(t
, cell
);
594 table_get_data(t
, cell
)->weight
= weight
;
598 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
604 if (percent
== (unsigned) -1)
607 assert(percent
<= 100);
609 r
= table_dedup_cell(t
, cell
);
613 table_get_data(t
, cell
)->align_percent
= percent
;
617 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
623 if (percent
== (unsigned) -1)
626 assert(percent
<= 100);
628 r
= table_dedup_cell(t
, cell
);
632 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
636 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
642 r
= table_dedup_cell(t
, cell
);
646 table_get_data(t
, cell
)->color
= empty_to_null(color
);
650 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
651 _cleanup_free_
char *copy
= NULL
;
663 r
= table_dedup_cell(t
, cell
);
667 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
670 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
677 r
= table_dedup_cell(t
, cell
);
681 assert_se(d
= table_get_data(t
, cell
));
683 if (d
->uppercase
== b
)
686 d
->formatted
= mfree(d
->formatted
);
691 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
692 _cleanup_free_
char *curl
= NULL
;
699 i
= TABLE_CELL_TO_INDEX(cell
);
703 assert_se(od
= t
->data
[i
]);
706 curl
= strdup(od
->url
);
718 od
->ellipsize_percent
);
722 nd
->color
= od
->color
;
723 nd
->url
= TAKE_PTR(curl
);
724 nd
->uppercase
= od
->uppercase
;
726 table_data_unref(od
);
732 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
735 TableCell
*last_cell
= NULL
;
739 assert(first_type
>= 0);
740 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
744 va_start(ap
, first_type
);
763 union in_addr_union address
;
774 data
= va_arg(ap
, const char *);
778 buffer
.b
= va_arg(ap
, int);
782 case TABLE_TIMESTAMP
:
783 case TABLE_TIMESTAMP_UTC
:
784 case TABLE_TIMESTAMP_RELATIVE
:
786 case TABLE_TIMESPAN_MSEC
:
787 buffer
.usec
= va_arg(ap
, usec_t
);
793 buffer
.size
= va_arg(ap
, uint64_t);
798 buffer
.int_val
= va_arg(ap
, int);
799 data
= &buffer
.int_val
;
803 int x
= va_arg(ap
, int);
804 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
812 int x
= va_arg(ap
, int);
813 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
816 data
= &buffer
.int16
;
821 buffer
.int32
= va_arg(ap
, int32_t);
822 data
= &buffer
.int32
;
826 buffer
.int64
= va_arg(ap
, int64_t);
827 data
= &buffer
.int64
;
831 buffer
.uint_val
= va_arg(ap
, unsigned);
832 data
= &buffer
.uint_val
;
836 unsigned x
= va_arg(ap
, unsigned);
837 assert(x
<= UINT8_MAX
);
840 data
= &buffer
.uint8
;
845 unsigned x
= va_arg(ap
, unsigned);
846 assert(x
<= UINT16_MAX
);
849 data
= &buffer
.uint16
;
854 buffer
.uint32
= va_arg(ap
, uint32_t);
855 data
= &buffer
.uint32
;
859 buffer
.uint64
= va_arg(ap
, uint64_t);
860 data
= &buffer
.uint64
;
864 buffer
.percent
= va_arg(ap
, int);
865 data
= &buffer
.percent
;
869 buffer
.ifindex
= va_arg(ap
, int);
870 data
= &buffer
.ifindex
;
874 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
875 data
= &buffer
.address
.in
;
879 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
880 data
= &buffer
.address
.in6
;
883 case TABLE_SET_MINIMUM_WIDTH
: {
884 size_t w
= va_arg(ap
, size_t);
886 r
= table_set_minimum_width(t
, last_cell
, w
);
890 case TABLE_SET_MAXIMUM_WIDTH
: {
891 size_t w
= va_arg(ap
, size_t);
892 r
= table_set_maximum_width(t
, last_cell
, w
);
896 case TABLE_SET_WEIGHT
: {
897 unsigned w
= va_arg(ap
, unsigned);
898 r
= table_set_weight(t
, last_cell
, w
);
902 case TABLE_SET_ALIGN_PERCENT
: {
903 unsigned p
= va_arg(ap
, unsigned);
904 r
= table_set_align_percent(t
, last_cell
, p
);
908 case TABLE_SET_ELLIPSIZE_PERCENT
: {
909 unsigned p
= va_arg(ap
, unsigned);
910 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
914 case TABLE_SET_COLOR
: {
915 const char *c
= va_arg(ap
, const char*);
916 r
= table_set_color(t
, last_cell
, c
);
920 case TABLE_SET_URL
: {
921 const char *u
= va_arg(ap
, const char*);
922 r
= table_set_url(t
, last_cell
, u
);
926 case TABLE_SET_UPPERCASE
: {
927 int u
= va_arg(ap
, int);
928 r
= table_set_uppercase(t
, last_cell
, u
);
932 case _TABLE_DATA_TYPE_MAX
:
933 /* Used as end marker */
938 assert_not_reached("Uh? Unexpected data type.");
941 if (type
< _TABLE_DATA_TYPE_MAX
)
942 r
= table_add_cell(t
, &last_cell
, type
, data
);
949 type
= va_arg(ap
, TableDataType
);
953 void table_set_header(Table
*t
, bool b
) {
959 void table_set_width(Table
*t
, size_t width
) {
965 int table_set_empty_string(Table
*t
, const char *empty
) {
968 return free_and_strdup(&t
->empty_string
, empty
);
971 int table_set_display(Table
*t
, size_t first_column
, ...) {
972 size_t allocated
, column
;
977 allocated
= t
->n_display_map
;
978 column
= first_column
;
980 va_start(ap
, first_column
);
982 assert(column
< t
->n_columns
);
984 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
989 t
->display_map
[t
->n_display_map
++] = column
;
991 column
= va_arg(ap
, size_t);
992 if (column
== (size_t) -1)
1001 int table_set_sort(Table
*t
, size_t first_column
, ...) {
1002 size_t allocated
, column
;
1007 allocated
= t
->n_sort_map
;
1008 column
= first_column
;
1010 va_start(ap
, first_column
);
1012 assert(column
< t
->n_columns
);
1014 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1019 t
->sort_map
[t
->n_sort_map
++] = column
;
1021 column
= va_arg(ap
, size_t);
1022 if (column
== (size_t) -1)
1030 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1034 if (a
->type
== b
->type
) {
1036 /* We only define ordering for cells of the same data type. If cells with different data types are
1037 * compared we follow the order the cells were originally added in */
1042 return strcmp(a
->string
, b
->string
);
1045 return path_compare(a
->string
, b
->string
);
1048 if (!a
->boolean
&& b
->boolean
)
1050 if (a
->boolean
&& !b
->boolean
)
1054 case TABLE_TIMESTAMP
:
1055 case TABLE_TIMESTAMP_UTC
:
1056 case TABLE_TIMESTAMP_RELATIVE
:
1057 return CMP(a
->timestamp
, b
->timestamp
);
1059 case TABLE_TIMESPAN
:
1060 case TABLE_TIMESPAN_MSEC
:
1061 return CMP(a
->timespan
, b
->timespan
);
1065 return CMP(a
->size
, b
->size
);
1068 return CMP(a
->int_val
, b
->int_val
);
1071 return CMP(a
->int8
, b
->int8
);
1074 return CMP(a
->int16
, b
->int16
);
1077 return CMP(a
->int32
, b
->int32
);
1080 return CMP(a
->int64
, b
->int64
);
1083 return CMP(a
->uint_val
, b
->uint_val
);
1086 return CMP(a
->uint8
, b
->uint8
);
1089 return CMP(a
->uint16
, b
->uint16
);
1092 return CMP(a
->uint32
, b
->uint32
);
1095 return CMP(a
->uint64
, b
->uint64
);
1098 return CMP(a
->percent
, b
->percent
);
1101 return CMP(a
->ifindex
, b
->ifindex
);
1104 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1106 case TABLE_IN6_ADDR
:
1107 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1114 /* Generic fallback using the original order in which the cells where added. */
1115 return CMP(index_a
, index_b
);
1118 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1123 assert(t
->sort_map
);
1125 /* Make sure the header stays at the beginning */
1126 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1128 if (*a
< t
->n_columns
)
1130 if (*b
< t
->n_columns
)
1133 /* Order other lines by the sorting map */
1134 for (i
= 0; i
< t
->n_sort_map
; i
++) {
1137 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1138 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1140 r
= cell_data_compare(d
, *a
, dd
, *b
);
1142 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1145 /* Order identical lines by the order there were originally added in */
1149 static const char *table_data_format(Table
*t
, TableData
*d
) {
1153 return d
->formatted
;
1157 return strempty(t
->empty_string
);
1164 d
->formatted
= new(char, strlen(d
->string
) + 1);
1168 for (p
= d
->string
, q
= d
->formatted
; *p
; p
++, q
++)
1169 *q
= (char) toupper((unsigned char) *p
);
1172 return d
->formatted
;
1178 return yes_no(d
->boolean
);
1180 case TABLE_TIMESTAMP
:
1181 case TABLE_TIMESTAMP_UTC
:
1182 case TABLE_TIMESTAMP_RELATIVE
: {
1183 _cleanup_free_
char *p
;
1186 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1190 if (d
->type
== TABLE_TIMESTAMP
)
1191 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1192 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1193 ret
= format_timestamp_utc(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1195 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1199 d
->formatted
= TAKE_PTR(p
);
1203 case TABLE_TIMESPAN
:
1204 case TABLE_TIMESPAN_MSEC
: {
1205 _cleanup_free_
char *p
;
1207 p
= new(char, FORMAT_TIMESPAN_MAX
);
1211 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1212 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1215 d
->formatted
= TAKE_PTR(p
);
1220 _cleanup_free_
char *p
;
1222 p
= new(char, FORMAT_BYTES_MAX
);
1226 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1229 d
->formatted
= TAKE_PTR(p
);
1234 _cleanup_free_
char *p
;
1237 p
= new(char, FORMAT_BYTES_MAX
+2);
1241 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1245 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1247 d
->formatted
= TAKE_PTR(p
);
1252 _cleanup_free_
char *p
;
1254 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1258 sprintf(p
, "%i", d
->int_val
);
1259 d
->formatted
= TAKE_PTR(p
);
1264 _cleanup_free_
char *p
;
1266 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1270 sprintf(p
, "%" PRIi8
, d
->int8
);
1271 d
->formatted
= TAKE_PTR(p
);
1276 _cleanup_free_
char *p
;
1278 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1282 sprintf(p
, "%" PRIi16
, d
->int16
);
1283 d
->formatted
= TAKE_PTR(p
);
1288 _cleanup_free_
char *p
;
1290 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1294 sprintf(p
, "%" PRIi32
, d
->int32
);
1295 d
->formatted
= TAKE_PTR(p
);
1300 _cleanup_free_
char *p
;
1302 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1306 sprintf(p
, "%" PRIi64
, d
->int64
);
1307 d
->formatted
= TAKE_PTR(p
);
1312 _cleanup_free_
char *p
;
1314 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1318 sprintf(p
, "%u", d
->uint_val
);
1319 d
->formatted
= TAKE_PTR(p
);
1324 _cleanup_free_
char *p
;
1326 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1330 sprintf(p
, "%" PRIu8
, d
->uint8
);
1331 d
->formatted
= TAKE_PTR(p
);
1335 case TABLE_UINT16
: {
1336 _cleanup_free_
char *p
;
1338 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1342 sprintf(p
, "%" PRIu16
, d
->uint16
);
1343 d
->formatted
= TAKE_PTR(p
);
1347 case TABLE_UINT32
: {
1348 _cleanup_free_
char *p
;
1350 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1354 sprintf(p
, "%" PRIu32
, d
->uint32
);
1355 d
->formatted
= TAKE_PTR(p
);
1359 case TABLE_UINT64
: {
1360 _cleanup_free_
char *p
;
1362 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1366 sprintf(p
, "%" PRIu64
, d
->uint64
);
1367 d
->formatted
= TAKE_PTR(p
);
1371 case TABLE_PERCENT
: {
1372 _cleanup_free_
char *p
;
1374 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1378 sprintf(p
, "%i%%" , d
->percent
);
1379 d
->formatted
= TAKE_PTR(p
);
1383 case TABLE_IFINDEX
: {
1384 _cleanup_free_
char *p
= NULL
;
1385 char name
[IF_NAMESIZE
+ 1];
1387 if (format_ifname(d
->ifindex
, name
)) {
1392 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1396 d
->formatted
= TAKE_PTR(p
);
1401 case TABLE_IN6_ADDR
: {
1402 _cleanup_free_
char *p
= NULL
;
1404 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1405 &d
->address
, &p
) < 0)
1408 d
->formatted
= TAKE_PTR(p
);
1413 assert_not_reached("Unexpected type?");
1416 return d
->formatted
;
1419 static int table_data_requested_width(Table
*table
, TableData
*d
, size_t *ret
) {
1423 t
= table_data_format(table
, d
);
1427 l
= utf8_console_width(t
);
1428 if (l
== (size_t) -1)
1431 if (d
->maximum_width
!= (size_t) -1 && l
> d
->maximum_width
)
1432 l
= d
->maximum_width
;
1434 if (l
< d
->minimum_width
)
1435 l
= d
->minimum_width
;
1441 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1442 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1443 _cleanup_free_
char *clickable
= NULL
;
1449 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1452 assert(percent
<= 100);
1454 old_length
= strlen(str
);
1457 r
= terminal_urlify(url
, str
, &clickable
);
1461 clickable_length
= strlen(clickable
);
1463 clickable_length
= old_length
;
1465 /* Determine current width on screen */
1467 while (p
< str
+ old_length
) {
1470 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1471 p
++, w
++; /* count invalid chars as 1 */
1475 p
= utf8_next_char(p
);
1476 w
+= unichar_iswide(c
) ? 2 : 1;
1479 /* Already wider than the target, if so, don't do anything */
1480 if (w
>= new_length
)
1481 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1483 /* How much spaces shall we add? An how much on the left side? */
1484 space
= new_length
- w
;
1485 lspace
= space
* percent
/ 100U;
1487 ret
= new(char, space
+ clickable_length
+ 1);
1491 for (i
= 0; i
< lspace
; i
++)
1493 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1494 for (i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1497 ret
[space
+ clickable_length
] = 0;
1501 static const char* table_data_color(TableData
*d
) {
1507 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1508 if (d
->type
== TABLE_EMPTY
)
1514 int table_print(Table
*t
, FILE *f
) {
1515 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1516 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1518 _cleanup_free_
size_t *sorted
= NULL
;
1519 uint64_t *column_weight
, weight_sum
;
1527 /* Ensure we have no incomplete rows */
1528 assert(t
->n_cells
% t
->n_columns
== 0);
1530 n_rows
= t
->n_cells
/ t
->n_columns
;
1531 assert(n_rows
> 0); /* at least the header row must be complete */
1534 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1536 sorted
= new(size_t, n_rows
);
1540 for (i
= 0; i
< n_rows
; i
++)
1541 sorted
[i
] = i
* t
->n_columns
;
1543 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1547 display_columns
= t
->n_display_map
;
1549 display_columns
= t
->n_columns
;
1551 assert(display_columns
> 0);
1553 minimum_width
= newa(size_t, display_columns
);
1554 maximum_width
= newa(size_t, display_columns
);
1555 requested_width
= newa(size_t, display_columns
);
1556 width
= newa(size_t, display_columns
);
1557 column_weight
= newa0(uint64_t, display_columns
);
1559 for (j
= 0; j
< display_columns
; j
++) {
1560 minimum_width
[j
] = 1;
1561 maximum_width
[j
] = (size_t) -1;
1562 requested_width
[j
] = (size_t) -1;
1565 /* First pass: determine column sizes */
1566 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1569 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1570 * hence we don't care for sorted[] during the first pass. */
1571 row
= t
->data
+ i
* t
->n_columns
;
1573 for (j
= 0; j
< display_columns
; j
++) {
1577 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1579 r
= table_data_requested_width(t
, d
, &req
);
1583 /* Determine the biggest width that any cell in this column would like to have */
1584 if (requested_width
[j
] == (size_t) -1 ||
1585 requested_width
[j
] < req
)
1586 requested_width
[j
] = req
;
1588 /* Determine the minimum width any cell in this column needs */
1589 if (minimum_width
[j
] < d
->minimum_width
)
1590 minimum_width
[j
] = d
->minimum_width
;
1592 /* Determine the maximum width any cell in this column needs */
1593 if (d
->maximum_width
!= (size_t) -1 &&
1594 (maximum_width
[j
] == (size_t) -1 ||
1595 maximum_width
[j
] > d
->maximum_width
))
1596 maximum_width
[j
] = d
->maximum_width
;
1598 /* Determine the full columns weight */
1599 column_weight
[j
] += d
->weight
;
1603 /* One space between each column */
1604 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1606 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1608 for (j
= 0; j
< display_columns
; j
++) {
1609 weight_sum
+= column_weight
[j
];
1611 table_minimum_width
+= minimum_width
[j
];
1613 if (maximum_width
[j
] == (size_t) -1)
1614 table_maximum_width
= (size_t) -1;
1616 table_maximum_width
+= maximum_width
[j
];
1618 table_requested_width
+= requested_width
[j
];
1621 /* Calculate effective table width */
1622 if (t
->width
!= (size_t) -1)
1623 table_effective_width
= t
->width
;
1624 else if (pager_have() || !isatty(STDOUT_FILENO
))
1625 table_effective_width
= table_requested_width
;
1627 table_effective_width
= MIN(table_requested_width
, columns());
1629 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1630 table_effective_width
= table_maximum_width
;
1632 if (table_effective_width
< table_minimum_width
)
1633 table_effective_width
= table_minimum_width
;
1635 if (table_effective_width
>= table_requested_width
) {
1638 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1639 * each column with what it asked for and the distribute the rest. */
1641 extra
= table_effective_width
- table_requested_width
;
1643 for (j
= 0; j
< display_columns
; j
++) {
1646 if (weight_sum
== 0)
1647 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1649 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1651 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1652 width
[j
] = maximum_width
[j
];
1654 if (width
[j
] < minimum_width
[j
])
1655 width
[j
] = minimum_width
[j
];
1657 assert(width
[j
] >= requested_width
[j
]);
1658 delta
= width
[j
] - requested_width
[j
];
1660 /* Subtract what we just added from the rest */
1666 assert(weight_sum
>= column_weight
[j
]);
1667 weight_sum
-= column_weight
[j
];
1671 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1672 * with the minimum they need, and then distribute anything left. */
1673 bool finalize
= false;
1676 extra
= table_effective_width
- table_minimum_width
;
1678 for (j
= 0; j
< display_columns
; j
++)
1679 width
[j
] = (size_t) -1;
1682 bool restart
= false;
1684 for (j
= 0; j
< display_columns
; j
++) {
1687 /* Did this column already get something assigned? If so, let's skip to the next */
1688 if (width
[j
] != (size_t) -1)
1691 if (weight_sum
== 0)
1692 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
1694 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1696 if (w
>= requested_width
[j
]) {
1697 /* Never give more than requested. If we hit a column like this, there's more
1698 * space to allocate to other columns which means we need to restart the
1699 * iteration. However, if we hit a column like this, let's assign it the space
1700 * it wanted for good early.*/
1702 w
= requested_width
[j
];
1705 } else if (!finalize
)
1710 assert(w
>= minimum_width
[j
]);
1711 delta
= w
- minimum_width
[j
];
1713 assert(delta
<= extra
);
1716 assert(weight_sum
>= column_weight
[j
]);
1717 weight_sum
-= column_weight
[j
];
1719 if (restart
&& !finalize
)
1731 /* Second pass: show output */
1732 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1736 row
= t
->data
+ sorted
[i
];
1738 row
= t
->data
+ i
* t
->n_columns
;
1740 for (j
= 0; j
< display_columns
; j
++) {
1741 _cleanup_free_
char *buffer
= NULL
;
1746 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1748 field
= table_data_format(t
, d
);
1752 l
= utf8_console_width(field
);
1754 /* Field is wider than allocated space. Let's ellipsize */
1756 buffer
= ellipsize(field
, width
[j
], d
->ellipsize_percent
);
1762 } else if (l
< width
[j
]) {
1763 /* Field is shorter than allocated space. Let's align with spaces */
1765 buffer
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
1772 if (l
>= width
[j
] && d
->url
) {
1773 _cleanup_free_
char *clickable
= NULL
;
1775 r
= terminal_urlify(d
->url
, field
, &clickable
);
1779 free_and_replace(buffer
, clickable
);
1783 if (row
== t
->data
) /* underline header line fully, including the column separator */
1784 fputs(ansi_underline(), f
);
1787 fputc(' ', f
); /* column separator */
1789 if (table_data_color(d
) && colors_enabled()) {
1790 if (row
== t
->data
) /* first undo header underliner */
1791 fputs(ANSI_NORMAL
, f
);
1793 fputs(table_data_color(d
), f
);
1798 if (colors_enabled() && (table_data_color(d
) || row
== t
->data
))
1799 fputs(ANSI_NORMAL
, f
);
1805 return fflush_and_check(f
);
1808 int table_format(Table
*t
, char **ret
) {
1809 _cleanup_fclose_
FILE *f
= NULL
;
1814 f
= open_memstream_unlocked(&buf
, &sz
);
1818 r
= table_print(t
, f
);
1829 size_t table_get_rows(Table
*t
) {
1833 assert(t
->n_columns
> 0);
1834 return t
->n_cells
/ t
->n_columns
;
1837 size_t table_get_columns(Table
*t
) {
1841 assert(t
->n_columns
> 0);
1842 return t
->n_columns
;
1845 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
1847 assert(column
< t
->n_columns
);
1849 if (!t
->reverse_map
) {
1853 t
->reverse_map
= new0(bool, t
->n_columns
);
1854 if (!t
->reverse_map
)
1858 t
->reverse_map
[column
] = b
;
1862 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
1867 if (column
>= t
->n_columns
)
1870 i
= row
* t
->n_columns
+ column
;
1871 if (i
>= t
->n_cells
)
1874 return TABLE_INDEX_TO_CELL(i
);
1877 const void *table_get(Table
*t
, TableCell
*cell
) {
1882 d
= table_get_data(t
, cell
);
1889 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
1892 cell
= table_get_cell(t
, row
, column
);
1896 return table_get(t
, cell
);
1899 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
1904 return json_variant_new_null(ret
);
1908 return json_variant_new_string(ret
, d
->string
);
1911 return json_variant_new_boolean(ret
, d
->boolean
);
1913 case TABLE_TIMESTAMP
:
1914 case TABLE_TIMESTAMP_UTC
:
1915 case TABLE_TIMESTAMP_RELATIVE
:
1916 if (d
->timestamp
== USEC_INFINITY
)
1917 return json_variant_new_null(ret
);
1919 return json_variant_new_unsigned(ret
, d
->timestamp
);
1921 case TABLE_TIMESPAN
:
1922 case TABLE_TIMESPAN_MSEC
:
1923 if (d
->timespan
== USEC_INFINITY
)
1924 return json_variant_new_null(ret
);
1926 return json_variant_new_unsigned(ret
, d
->timespan
);
1930 if (d
->size
== (size_t) -1)
1931 return json_variant_new_null(ret
);
1933 return json_variant_new_unsigned(ret
, d
->size
);
1936 return json_variant_new_integer(ret
, d
->int_val
);
1939 return json_variant_new_integer(ret
, d
->int8
);
1942 return json_variant_new_integer(ret
, d
->int16
);
1945 return json_variant_new_integer(ret
, d
->int32
);
1948 return json_variant_new_integer(ret
, d
->int64
);
1951 return json_variant_new_unsigned(ret
, d
->uint_val
);
1954 return json_variant_new_unsigned(ret
, d
->uint8
);
1957 return json_variant_new_unsigned(ret
, d
->uint16
);
1960 return json_variant_new_unsigned(ret
, d
->uint32
);
1963 return json_variant_new_unsigned(ret
, d
->uint64
);
1966 return json_variant_new_integer(ret
, d
->percent
);
1969 return json_variant_new_integer(ret
, d
->ifindex
);
1972 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
1974 case TABLE_IN6_ADDR
:
1975 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1982 int table_to_json(Table
*t
, JsonVariant
**ret
) {
1983 JsonVariant
**rows
= NULL
, **elements
= NULL
;
1984 _cleanup_free_
size_t *sorted
= NULL
;
1985 size_t n_rows
, i
, j
, display_columns
;
1990 /* Ensure we have no incomplete rows */
1991 assert(t
->n_cells
% t
->n_columns
== 0);
1993 n_rows
= t
->n_cells
/ t
->n_columns
;
1994 assert(n_rows
> 0); /* at least the header row must be complete */
1997 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1999 sorted
= new(size_t, n_rows
);
2005 for (i
= 0; i
< n_rows
; i
++)
2006 sorted
[i
] = i
* t
->n_columns
;
2008 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2012 display_columns
= t
->n_display_map
;
2014 display_columns
= t
->n_columns
;
2015 assert(display_columns
> 0);
2017 elements
= new0(JsonVariant
*, display_columns
* 2);
2023 for (j
= 0; j
< display_columns
; j
++) {
2026 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2028 r
= table_data_to_json(d
, elements
+ j
*2);
2033 rows
= new0(JsonVariant
*, n_rows
-1);
2039 for (i
= 1; i
< n_rows
; i
++) {
2043 row
= t
->data
+ sorted
[i
];
2045 row
= t
->data
+ i
* t
->n_columns
;
2047 for (j
= 0; j
< display_columns
; j
++) {
2051 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2054 elements
[k
] = json_variant_unref(elements
[k
]);
2056 r
= table_data_to_json(d
, elements
+ k
);
2061 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2066 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2070 json_variant_unref_many(rows
, n_rows
-1);
2075 json_variant_unref_many(elements
, display_columns
*2);
2082 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2083 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2091 r
= table_to_json(t
, &v
);
2095 json_variant_dump(v
, flags
, f
, NULL
);
2097 return fflush_and_check(f
);