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 "pretty-print.h"
18 #include "sort-util.h"
19 #include "string-util.h"
21 #include "terminal-util.h"
22 #include "time-util.h"
26 #define DEFAULT_WEIGHT 100
29 A few notes on implementation details:
31 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
32 table. It can be easily converted to an index number and back.
34 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
35 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
36 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
37 outside only sees Table and TableCell.
39 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
42 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
43 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
44 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
45 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
47 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
48 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
49 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
50 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
53 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
54 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
55 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
58 typedef struct TableData
{
62 size_t minimum_width
; /* minimum width for the column */
63 size_t maximum_width
; /* maximum width for the column */
64 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
65 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
66 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
68 bool uppercase
; /* Uppercase string on display */
70 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 */
71 char *url
; /* A URL to use for a clickable hyperlink */
72 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
75 uint8_t data
[0]; /* data is generic array */
91 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
93 union in_addr_union address
;
94 /* … add more here as we start supporting more cell data types … */
98 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
103 i
= PTR_TO_SIZE(cell
);
109 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
110 assert(index
!= (size_t) -1);
111 return SIZE_TO_PTR(index
+ 1);
118 bool header
; /* Whether to show the header row? */
119 size_t width
; /* If != (size_t) -1 the width to format this table in */
124 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 */
125 size_t n_display_map
;
127 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
135 Table
*table_new_raw(size_t n_columns
) {
136 _cleanup_(table_unrefp
) Table
*t
= NULL
;
138 assert(n_columns
> 0);
144 *t
= (struct Table
) {
145 .n_columns
= n_columns
,
147 .width
= (size_t) -1,
153 Table
*table_new_internal(const char *first_header
, ...) {
154 _cleanup_(table_unrefp
) Table
*t
= NULL
;
155 size_t n_columns
= 1;
160 assert(first_header
);
162 va_start(ap
, first_header
);
164 h
= va_arg(ap
, const char*);
172 t
= table_new_raw(n_columns
);
176 va_start(ap
, first_header
);
177 for (h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
180 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
186 /* Make the table header uppercase */
187 r
= table_set_uppercase(t
, cell
, true);
195 assert(t
->n_columns
== t
->n_cells
);
199 static TableData
*table_data_free(TableData
*d
) {
208 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
209 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
211 Table
*table_unref(Table
*t
) {
217 for (i
= 0; i
< t
->n_cells
; i
++)
218 table_data_unref(t
->data
[i
]);
221 free(t
->display_map
);
223 free(t
->reverse_map
);
224 free(t
->empty_string
);
229 static size_t table_data_size(TableDataType type
, const void *data
) {
237 return strlen(data
) + 1;
242 case TABLE_TIMESTAMP
:
243 case TABLE_TIMESTAMP_UTC
:
244 case TABLE_TIMESTAMP_RELATIVE
:
246 case TABLE_TIMESPAN_MSEC
:
247 return sizeof(usec_t
);
253 return sizeof(uint64_t);
257 return sizeof(uint32_t);
261 return sizeof(uint16_t);
265 return sizeof(uint8_t);
274 return sizeof(struct in_addr
);
277 return sizeof(struct in6_addr
);
280 assert_not_reached("Uh? Unexpected cell type");
284 static bool table_data_matches(
288 size_t minimum_width
,
289 size_t maximum_width
,
291 unsigned align_percent
,
292 unsigned ellipsize_percent
) {
300 if (d
->minimum_width
!= minimum_width
)
303 if (d
->maximum_width
!= maximum_width
)
306 if (d
->weight
!= weight
)
309 if (d
->align_percent
!= align_percent
)
312 if (d
->ellipsize_percent
!= ellipsize_percent
)
315 /* If a color/url/uppercase flag is set, refuse to merge */
323 k
= table_data_size(type
, data
);
324 l
= table_data_size(d
->type
, d
->data
);
329 return memcmp_safe(data
, d
->data
, l
) == 0;
332 static TableData
*table_data_new(
335 size_t minimum_width
,
336 size_t maximum_width
,
338 unsigned align_percent
,
339 unsigned ellipsize_percent
) {
344 data_size
= table_data_size(type
, data
);
346 d
= malloc0(offsetof(TableData
, data
) + data_size
);
352 d
->minimum_width
= minimum_width
;
353 d
->maximum_width
= maximum_width
;
355 d
->align_percent
= align_percent
;
356 d
->ellipsize_percent
= ellipsize_percent
;
357 memcpy_safe(d
->data
, data
, data_size
);
362 int table_add_cell_full(
364 TableCell
**ret_cell
,
367 size_t minimum_width
,
368 size_t maximum_width
,
370 unsigned align_percent
,
371 unsigned ellipsize_percent
) {
373 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
378 assert(type
< _TABLE_DATA_TYPE_MAX
);
380 /* Special rule: patch NULL data fields to the empty field */
384 /* Determine the cell adjacent to the current one, but one row up */
385 if (t
->n_cells
>= t
->n_columns
)
386 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
390 /* If formatting parameters are left unspecified, copy from the previous row */
391 if (minimum_width
== (size_t) -1)
392 minimum_width
= p
? p
->minimum_width
: 1;
394 if (weight
== (unsigned) -1)
395 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
397 if (align_percent
== (unsigned) -1)
398 align_percent
= p
? p
->align_percent
: 0;
400 if (ellipsize_percent
== (unsigned) -1)
401 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
403 assert(align_percent
<= 100);
404 assert(ellipsize_percent
<= 100);
406 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
407 * formatting. Let's see if we can reuse the cell data and ref it once more. */
409 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
410 d
= table_data_ref(p
);
412 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
417 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
421 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
423 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
428 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
429 _cleanup_free_
char *buffer
= NULL
;
433 va_start(ap
, format
);
434 r
= vasprintf(&buffer
, format
, ap
);
439 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
442 int table_fill_empty(Table
*t
, size_t until_column
) {
447 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
448 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
450 if (until_column
>= t
->n_columns
)
454 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
458 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
463 int table_dup_cell(Table
*t
, TableCell
*cell
) {
468 /* Add the data of the specified cell a second time as a new cell to the end. */
470 i
= TABLE_CELL_TO_INDEX(cell
);
474 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
477 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
481 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
482 _cleanup_free_
char *curl
= NULL
;
488 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
489 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
491 i
= TABLE_CELL_TO_INDEX(cell
);
495 assert_se(od
= t
->data
[i
]);
499 assert(od
->n_ref
> 1);
502 curl
= strdup(od
->url
);
514 od
->ellipsize_percent
);
518 nd
->color
= od
->color
;
519 nd
->url
= TAKE_PTR(curl
);
520 nd
->uppercase
= od
->uppercase
;
522 table_data_unref(od
);
525 assert(nd
->n_ref
== 1);
530 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
536 /* Get the data object of the specified cell, or NULL if it doesn't exist */
538 i
= TABLE_CELL_TO_INDEX(cell
);
543 assert(t
->data
[i
]->n_ref
> 0);
548 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
554 if (minimum_width
== (size_t) -1)
557 r
= table_dedup_cell(t
, cell
);
561 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
565 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
571 r
= table_dedup_cell(t
, cell
);
575 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
579 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
585 if (weight
== (unsigned) -1)
586 weight
= DEFAULT_WEIGHT
;
588 r
= table_dedup_cell(t
, cell
);
592 table_get_data(t
, cell
)->weight
= weight
;
596 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
602 if (percent
== (unsigned) -1)
605 assert(percent
<= 100);
607 r
= table_dedup_cell(t
, cell
);
611 table_get_data(t
, cell
)->align_percent
= percent
;
615 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
621 if (percent
== (unsigned) -1)
624 assert(percent
<= 100);
626 r
= table_dedup_cell(t
, cell
);
630 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
634 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
640 r
= table_dedup_cell(t
, cell
);
644 table_get_data(t
, cell
)->color
= empty_to_null(color
);
648 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
649 _cleanup_free_
char *copy
= NULL
;
661 r
= table_dedup_cell(t
, cell
);
665 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
668 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
675 r
= table_dedup_cell(t
, cell
);
679 assert_se(d
= table_get_data(t
, cell
));
681 if (d
->uppercase
== b
)
684 d
->formatted
= mfree(d
->formatted
);
689 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
690 _cleanup_free_
char *curl
= NULL
;
697 i
= TABLE_CELL_TO_INDEX(cell
);
701 assert_se(od
= t
->data
[i
]);
704 curl
= strdup(od
->url
);
716 od
->ellipsize_percent
);
720 nd
->color
= od
->color
;
721 nd
->url
= TAKE_PTR(curl
);
722 nd
->uppercase
= od
->uppercase
;
724 table_data_unref(od
);
730 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
733 TableCell
*last_cell
= NULL
;
737 assert(first_type
>= 0);
738 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
742 va_start(ap
, first_type
);
761 union in_addr_union address
;
771 data
= va_arg(ap
, const char *);
775 buffer
.b
= va_arg(ap
, int);
779 case TABLE_TIMESTAMP
:
780 case TABLE_TIMESTAMP_UTC
:
781 case TABLE_TIMESTAMP_RELATIVE
:
783 case TABLE_TIMESPAN_MSEC
:
784 buffer
.usec
= va_arg(ap
, usec_t
);
790 buffer
.size
= va_arg(ap
, uint64_t);
795 buffer
.int_val
= va_arg(ap
, int);
796 data
= &buffer
.int_val
;
800 int x
= va_arg(ap
, int);
801 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
809 int x
= va_arg(ap
, int);
810 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
813 data
= &buffer
.int16
;
818 buffer
.int32
= va_arg(ap
, int32_t);
819 data
= &buffer
.int32
;
823 buffer
.int64
= va_arg(ap
, int64_t);
824 data
= &buffer
.int64
;
828 buffer
.uint_val
= va_arg(ap
, unsigned);
829 data
= &buffer
.uint_val
;
833 unsigned x
= va_arg(ap
, unsigned);
834 assert(x
<= UINT8_MAX
);
837 data
= &buffer
.uint8
;
842 unsigned x
= va_arg(ap
, unsigned);
843 assert(x
<= UINT16_MAX
);
846 data
= &buffer
.uint16
;
851 buffer
.uint32
= va_arg(ap
, uint32_t);
852 data
= &buffer
.uint32
;
856 buffer
.uint64
= va_arg(ap
, uint64_t);
857 data
= &buffer
.uint64
;
861 buffer
.percent
= va_arg(ap
, int);
862 data
= &buffer
.percent
;
866 buffer
.ifindex
= va_arg(ap
, int);
867 data
= &buffer
.ifindex
;
871 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
872 data
= &buffer
.address
.in
;
876 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
877 data
= &buffer
.address
.in6
;
880 case TABLE_SET_MINIMUM_WIDTH
: {
881 size_t w
= va_arg(ap
, size_t);
883 r
= table_set_minimum_width(t
, last_cell
, w
);
887 case TABLE_SET_MAXIMUM_WIDTH
: {
888 size_t w
= va_arg(ap
, size_t);
889 r
= table_set_maximum_width(t
, last_cell
, w
);
893 case TABLE_SET_WEIGHT
: {
894 unsigned w
= va_arg(ap
, unsigned);
895 r
= table_set_weight(t
, last_cell
, w
);
899 case TABLE_SET_ALIGN_PERCENT
: {
900 unsigned p
= va_arg(ap
, unsigned);
901 r
= table_set_align_percent(t
, last_cell
, p
);
905 case TABLE_SET_ELLIPSIZE_PERCENT
: {
906 unsigned p
= va_arg(ap
, unsigned);
907 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
911 case TABLE_SET_COLOR
: {
912 const char *c
= va_arg(ap
, const char*);
913 r
= table_set_color(t
, last_cell
, c
);
917 case TABLE_SET_URL
: {
918 const char *u
= va_arg(ap
, const char*);
919 r
= table_set_url(t
, last_cell
, u
);
923 case TABLE_SET_UPPERCASE
: {
924 int u
= va_arg(ap
, int);
925 r
= table_set_uppercase(t
, last_cell
, u
);
929 case _TABLE_DATA_TYPE_MAX
:
930 /* Used as end marker */
935 assert_not_reached("Uh? Unexpected data type.");
938 if (type
< _TABLE_DATA_TYPE_MAX
)
939 r
= table_add_cell(t
, &last_cell
, type
, data
);
946 type
= va_arg(ap
, TableDataType
);
950 void table_set_header(Table
*t
, bool b
) {
956 void table_set_width(Table
*t
, size_t width
) {
962 int table_set_empty_string(Table
*t
, const char *empty
) {
965 return free_and_strdup(&t
->empty_string
, empty
);
968 int table_set_display(Table
*t
, size_t first_column
, ...) {
969 size_t allocated
, column
;
974 allocated
= t
->n_display_map
;
975 column
= first_column
;
977 va_start(ap
, first_column
);
979 assert(column
< t
->n_columns
);
981 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
986 t
->display_map
[t
->n_display_map
++] = column
;
988 column
= va_arg(ap
, size_t);
989 if (column
== (size_t) -1)
998 int table_set_sort(Table
*t
, size_t first_column
, ...) {
999 size_t allocated
, column
;
1004 allocated
= t
->n_sort_map
;
1005 column
= first_column
;
1007 va_start(ap
, first_column
);
1009 assert(column
< t
->n_columns
);
1011 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1016 t
->sort_map
[t
->n_sort_map
++] = column
;
1018 column
= va_arg(ap
, size_t);
1019 if (column
== (size_t) -1)
1027 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1031 if (a
->type
== b
->type
) {
1033 /* We only define ordering for cells of the same data type. If cells with different data types are
1034 * compared we follow the order the cells were originally added in */
1039 return strcmp(a
->string
, b
->string
);
1042 if (!a
->boolean
&& b
->boolean
)
1044 if (a
->boolean
&& !b
->boolean
)
1048 case TABLE_TIMESTAMP
:
1049 case TABLE_TIMESTAMP_UTC
:
1050 case TABLE_TIMESTAMP_RELATIVE
:
1051 return CMP(a
->timestamp
, b
->timestamp
);
1053 case TABLE_TIMESPAN
:
1054 case TABLE_TIMESPAN_MSEC
:
1055 return CMP(a
->timespan
, b
->timespan
);
1059 return CMP(a
->size
, b
->size
);
1062 return CMP(a
->int_val
, b
->int_val
);
1065 return CMP(a
->int8
, b
->int8
);
1068 return CMP(a
->int16
, b
->int16
);
1071 return CMP(a
->int32
, b
->int32
);
1074 return CMP(a
->int64
, b
->int64
);
1077 return CMP(a
->uint_val
, b
->uint_val
);
1080 return CMP(a
->uint8
, b
->uint8
);
1083 return CMP(a
->uint16
, b
->uint16
);
1086 return CMP(a
->uint32
, b
->uint32
);
1089 return CMP(a
->uint64
, b
->uint64
);
1092 return CMP(a
->percent
, b
->percent
);
1095 return CMP(a
->ifindex
, b
->ifindex
);
1098 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1100 case TABLE_IN6_ADDR
:
1101 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1108 /* Generic fallback using the original order in which the cells where added. */
1109 return CMP(index_a
, index_b
);
1112 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1117 assert(t
->sort_map
);
1119 /* Make sure the header stays at the beginning */
1120 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1122 if (*a
< t
->n_columns
)
1124 if (*b
< t
->n_columns
)
1127 /* Order other lines by the sorting map */
1128 for (i
= 0; i
< t
->n_sort_map
; i
++) {
1131 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1132 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1134 r
= cell_data_compare(d
, *a
, dd
, *b
);
1136 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1139 /* Order identical lines by the order there were originally added in */
1143 static const char *table_data_format(Table
*t
, TableData
*d
) {
1147 return d
->formatted
;
1151 return strempty(t
->empty_string
);
1157 d
->formatted
= new(char, strlen(d
->string
) + 1);
1161 for (p
= d
->string
, q
= d
->formatted
; *p
; p
++, q
++)
1162 *q
= (char) toupper((unsigned char) *p
);
1165 return d
->formatted
;
1171 return yes_no(d
->boolean
);
1173 case TABLE_TIMESTAMP
:
1174 case TABLE_TIMESTAMP_UTC
:
1175 case TABLE_TIMESTAMP_RELATIVE
: {
1176 _cleanup_free_
char *p
;
1179 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1183 if (d
->type
== TABLE_TIMESTAMP
)
1184 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1185 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1186 ret
= format_timestamp_utc(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1188 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1192 d
->formatted
= TAKE_PTR(p
);
1196 case TABLE_TIMESPAN
:
1197 case TABLE_TIMESPAN_MSEC
: {
1198 _cleanup_free_
char *p
;
1200 p
= new(char, FORMAT_TIMESPAN_MAX
);
1204 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1205 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1208 d
->formatted
= TAKE_PTR(p
);
1213 _cleanup_free_
char *p
;
1215 p
= new(char, FORMAT_BYTES_MAX
);
1219 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1222 d
->formatted
= TAKE_PTR(p
);
1227 _cleanup_free_
char *p
;
1230 p
= new(char, FORMAT_BYTES_MAX
+2);
1234 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1238 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1240 d
->formatted
= TAKE_PTR(p
);
1245 _cleanup_free_
char *p
;
1247 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1251 sprintf(p
, "%i", d
->int_val
);
1252 d
->formatted
= TAKE_PTR(p
);
1257 _cleanup_free_
char *p
;
1259 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1263 sprintf(p
, "%" PRIi8
, d
->int8
);
1264 d
->formatted
= TAKE_PTR(p
);
1269 _cleanup_free_
char *p
;
1271 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1275 sprintf(p
, "%" PRIi16
, d
->int16
);
1276 d
->formatted
= TAKE_PTR(p
);
1281 _cleanup_free_
char *p
;
1283 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1287 sprintf(p
, "%" PRIi32
, d
->int32
);
1288 d
->formatted
= TAKE_PTR(p
);
1293 _cleanup_free_
char *p
;
1295 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1299 sprintf(p
, "%" PRIi64
, d
->int64
);
1300 d
->formatted
= TAKE_PTR(p
);
1305 _cleanup_free_
char *p
;
1307 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1311 sprintf(p
, "%u", d
->uint_val
);
1312 d
->formatted
= TAKE_PTR(p
);
1317 _cleanup_free_
char *p
;
1319 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1323 sprintf(p
, "%" PRIu8
, d
->uint8
);
1324 d
->formatted
= TAKE_PTR(p
);
1328 case TABLE_UINT16
: {
1329 _cleanup_free_
char *p
;
1331 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1335 sprintf(p
, "%" PRIu16
, d
->uint16
);
1336 d
->formatted
= TAKE_PTR(p
);
1340 case TABLE_UINT32
: {
1341 _cleanup_free_
char *p
;
1343 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1347 sprintf(p
, "%" PRIu32
, d
->uint32
);
1348 d
->formatted
= TAKE_PTR(p
);
1352 case TABLE_UINT64
: {
1353 _cleanup_free_
char *p
;
1355 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1359 sprintf(p
, "%" PRIu64
, d
->uint64
);
1360 d
->formatted
= TAKE_PTR(p
);
1364 case TABLE_PERCENT
: {
1365 _cleanup_free_
char *p
;
1367 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1371 sprintf(p
, "%i%%" , d
->percent
);
1372 d
->formatted
= TAKE_PTR(p
);
1376 case TABLE_IFINDEX
: {
1377 _cleanup_free_
char *p
= NULL
;
1378 char name
[IF_NAMESIZE
+ 1];
1380 if (format_ifname(d
->ifindex
, name
)) {
1385 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1389 d
->formatted
= TAKE_PTR(p
);
1394 case TABLE_IN6_ADDR
: {
1395 _cleanup_free_
char *p
= NULL
;
1397 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1398 &d
->address
, &p
) < 0)
1401 d
->formatted
= TAKE_PTR(p
);
1406 assert_not_reached("Unexpected type?");
1409 return d
->formatted
;
1412 static int table_data_requested_width(Table
*table
, TableData
*d
, size_t *ret
) {
1416 t
= table_data_format(table
, d
);
1420 l
= utf8_console_width(t
);
1421 if (l
== (size_t) -1)
1424 if (d
->maximum_width
!= (size_t) -1 && l
> d
->maximum_width
)
1425 l
= d
->maximum_width
;
1427 if (l
< d
->minimum_width
)
1428 l
= d
->minimum_width
;
1434 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1435 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1436 _cleanup_free_
char *clickable
= NULL
;
1442 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1445 assert(percent
<= 100);
1447 old_length
= strlen(str
);
1450 r
= terminal_urlify(url
, str
, &clickable
);
1454 clickable_length
= strlen(clickable
);
1456 clickable_length
= old_length
;
1458 /* Determine current width on screen */
1460 while (p
< str
+ old_length
) {
1463 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1464 p
++, w
++; /* count invalid chars as 1 */
1468 p
= utf8_next_char(p
);
1469 w
+= unichar_iswide(c
) ? 2 : 1;
1472 /* Already wider than the target, if so, don't do anything */
1473 if (w
>= new_length
)
1474 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1476 /* How much spaces shall we add? An how much on the left side? */
1477 space
= new_length
- w
;
1478 lspace
= space
* percent
/ 100U;
1480 ret
= new(char, space
+ clickable_length
+ 1);
1484 for (i
= 0; i
< lspace
; i
++)
1486 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1487 for (i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1490 ret
[space
+ clickable_length
] = 0;
1494 static const char* table_data_color(TableData
*d
) {
1500 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1501 if (d
->type
== TABLE_EMPTY
)
1507 int table_print(Table
*t
, FILE *f
) {
1508 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1509 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1511 _cleanup_free_
size_t *sorted
= NULL
;
1512 uint64_t *column_weight
, weight_sum
;
1520 /* Ensure we have no incomplete rows */
1521 assert(t
->n_cells
% t
->n_columns
== 0);
1523 n_rows
= t
->n_cells
/ t
->n_columns
;
1524 assert(n_rows
> 0); /* at least the header row must be complete */
1527 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1529 sorted
= new(size_t, n_rows
);
1533 for (i
= 0; i
< n_rows
; i
++)
1534 sorted
[i
] = i
* t
->n_columns
;
1536 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1540 display_columns
= t
->n_display_map
;
1542 display_columns
= t
->n_columns
;
1544 assert(display_columns
> 0);
1546 minimum_width
= newa(size_t, display_columns
);
1547 maximum_width
= newa(size_t, display_columns
);
1548 requested_width
= newa(size_t, display_columns
);
1549 width
= newa(size_t, display_columns
);
1550 column_weight
= newa0(uint64_t, display_columns
);
1552 for (j
= 0; j
< display_columns
; j
++) {
1553 minimum_width
[j
] = 1;
1554 maximum_width
[j
] = (size_t) -1;
1555 requested_width
[j
] = (size_t) -1;
1558 /* First pass: determine column sizes */
1559 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1562 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1563 * hence we don't care for sorted[] during the first pass. */
1564 row
= t
->data
+ i
* t
->n_columns
;
1566 for (j
= 0; j
< display_columns
; j
++) {
1570 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1572 r
= table_data_requested_width(t
, d
, &req
);
1576 /* Determine the biggest width that any cell in this column would like to have */
1577 if (requested_width
[j
] == (size_t) -1 ||
1578 requested_width
[j
] < req
)
1579 requested_width
[j
] = req
;
1581 /* Determine the minimum width any cell in this column needs */
1582 if (minimum_width
[j
] < d
->minimum_width
)
1583 minimum_width
[j
] = d
->minimum_width
;
1585 /* Determine the maximum width any cell in this column needs */
1586 if (d
->maximum_width
!= (size_t) -1 &&
1587 (maximum_width
[j
] == (size_t) -1 ||
1588 maximum_width
[j
] > d
->maximum_width
))
1589 maximum_width
[j
] = d
->maximum_width
;
1591 /* Determine the full columns weight */
1592 column_weight
[j
] += d
->weight
;
1596 /* One space between each column */
1597 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1599 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1601 for (j
= 0; j
< display_columns
; j
++) {
1602 weight_sum
+= column_weight
[j
];
1604 table_minimum_width
+= minimum_width
[j
];
1606 if (maximum_width
[j
] == (size_t) -1)
1607 table_maximum_width
= (size_t) -1;
1609 table_maximum_width
+= maximum_width
[j
];
1611 table_requested_width
+= requested_width
[j
];
1614 /* Calculate effective table width */
1615 if (t
->width
!= (size_t) -1)
1616 table_effective_width
= t
->width
;
1617 else if (pager_have() || !isatty(STDOUT_FILENO
))
1618 table_effective_width
= table_requested_width
;
1620 table_effective_width
= MIN(table_requested_width
, columns());
1622 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1623 table_effective_width
= table_maximum_width
;
1625 if (table_effective_width
< table_minimum_width
)
1626 table_effective_width
= table_minimum_width
;
1628 if (table_effective_width
>= table_requested_width
) {
1631 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1632 * each column with what it asked for and the distribute the rest. */
1634 extra
= table_effective_width
- table_requested_width
;
1636 for (j
= 0; j
< display_columns
; j
++) {
1639 if (weight_sum
== 0)
1640 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1642 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1644 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1645 width
[j
] = maximum_width
[j
];
1647 if (width
[j
] < minimum_width
[j
])
1648 width
[j
] = minimum_width
[j
];
1650 assert(width
[j
] >= requested_width
[j
]);
1651 delta
= width
[j
] - requested_width
[j
];
1653 /* Subtract what we just added from the rest */
1659 assert(weight_sum
>= column_weight
[j
]);
1660 weight_sum
-= column_weight
[j
];
1664 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1665 * with the minimum they need, and then distribute anything left. */
1666 bool finalize
= false;
1669 extra
= table_effective_width
- table_minimum_width
;
1671 for (j
= 0; j
< display_columns
; j
++)
1672 width
[j
] = (size_t) -1;
1675 bool restart
= false;
1677 for (j
= 0; j
< display_columns
; j
++) {
1680 /* Did this column already get something assigned? If so, let's skip to the next */
1681 if (width
[j
] != (size_t) -1)
1684 if (weight_sum
== 0)
1685 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
1687 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1689 if (w
>= requested_width
[j
]) {
1690 /* Never give more than requested. If we hit a column like this, there's more
1691 * space to allocate to other columns which means we need to restart the
1692 * iteration. However, if we hit a column like this, let's assign it the space
1693 * it wanted for good early.*/
1695 w
= requested_width
[j
];
1698 } else if (!finalize
)
1703 assert(w
>= minimum_width
[j
]);
1704 delta
= w
- minimum_width
[j
];
1706 assert(delta
<= extra
);
1709 assert(weight_sum
>= column_weight
[j
]);
1710 weight_sum
-= column_weight
[j
];
1712 if (restart
&& !finalize
)
1724 /* Second pass: show output */
1725 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1729 row
= t
->data
+ sorted
[i
];
1731 row
= t
->data
+ i
* t
->n_columns
;
1733 for (j
= 0; j
< display_columns
; j
++) {
1734 _cleanup_free_
char *buffer
= NULL
;
1739 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1741 field
= table_data_format(t
, d
);
1745 l
= utf8_console_width(field
);
1747 /* Field is wider than allocated space. Let's ellipsize */
1749 buffer
= ellipsize(field
, width
[j
], d
->ellipsize_percent
);
1755 } else if (l
< width
[j
]) {
1756 /* Field is shorter than allocated space. Let's align with spaces */
1758 buffer
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
1765 if (l
>= width
[j
] && d
->url
) {
1766 _cleanup_free_
char *clickable
= NULL
;
1768 r
= terminal_urlify(d
->url
, field
, &clickable
);
1772 free_and_replace(buffer
, clickable
);
1776 if (row
== t
->data
) /* underline header line fully, including the column separator */
1777 fputs(ansi_underline(), f
);
1780 fputc(' ', f
); /* column separator */
1782 if (table_data_color(d
) && colors_enabled()) {
1783 if (row
== t
->data
) /* first undo header underliner */
1784 fputs(ANSI_NORMAL
, f
);
1786 fputs(table_data_color(d
), f
);
1791 if (colors_enabled() && (table_data_color(d
) || row
== t
->data
))
1792 fputs(ANSI_NORMAL
, f
);
1798 return fflush_and_check(f
);
1801 int table_format(Table
*t
, char **ret
) {
1802 _cleanup_fclose_
FILE *f
= NULL
;
1807 f
= open_memstream_unlocked(&buf
, &sz
);
1811 r
= table_print(t
, f
);
1822 size_t table_get_rows(Table
*t
) {
1826 assert(t
->n_columns
> 0);
1827 return t
->n_cells
/ t
->n_columns
;
1830 size_t table_get_columns(Table
*t
) {
1834 assert(t
->n_columns
> 0);
1835 return t
->n_columns
;
1838 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
1840 assert(column
< t
->n_columns
);
1842 if (!t
->reverse_map
) {
1846 t
->reverse_map
= new0(bool, t
->n_columns
);
1847 if (!t
->reverse_map
)
1851 t
->reverse_map
[column
] = b
;
1855 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
1860 if (column
>= t
->n_columns
)
1863 i
= row
* t
->n_columns
+ column
;
1864 if (i
>= t
->n_cells
)
1867 return TABLE_INDEX_TO_CELL(i
);
1870 const void *table_get(Table
*t
, TableCell
*cell
) {
1875 d
= table_get_data(t
, cell
);
1882 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
1885 cell
= table_get_cell(t
, row
, column
);
1889 return table_get(t
, cell
);
1892 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
1897 return json_variant_new_null(ret
);
1900 return json_variant_new_string(ret
, d
->string
);
1903 return json_variant_new_boolean(ret
, d
->boolean
);
1905 case TABLE_TIMESTAMP
:
1906 case TABLE_TIMESTAMP_UTC
:
1907 case TABLE_TIMESTAMP_RELATIVE
:
1908 if (d
->timestamp
== USEC_INFINITY
)
1909 return json_variant_new_null(ret
);
1911 return json_variant_new_unsigned(ret
, d
->timestamp
);
1913 case TABLE_TIMESPAN
:
1914 case TABLE_TIMESPAN_MSEC
:
1915 if (d
->timespan
== USEC_INFINITY
)
1916 return json_variant_new_null(ret
);
1918 return json_variant_new_unsigned(ret
, d
->timespan
);
1922 if (d
->size
== (size_t) -1)
1923 return json_variant_new_null(ret
);
1925 return json_variant_new_unsigned(ret
, d
->size
);
1928 return json_variant_new_integer(ret
, d
->int_val
);
1931 return json_variant_new_integer(ret
, d
->int8
);
1934 return json_variant_new_integer(ret
, d
->int16
);
1937 return json_variant_new_integer(ret
, d
->int32
);
1940 return json_variant_new_integer(ret
, d
->int64
);
1943 return json_variant_new_unsigned(ret
, d
->uint_val
);
1946 return json_variant_new_unsigned(ret
, d
->uint8
);
1949 return json_variant_new_unsigned(ret
, d
->uint16
);
1952 return json_variant_new_unsigned(ret
, d
->uint32
);
1955 return json_variant_new_unsigned(ret
, d
->uint64
);
1958 return json_variant_new_integer(ret
, d
->percent
);
1961 return json_variant_new_integer(ret
, d
->ifindex
);
1964 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
1966 case TABLE_IN6_ADDR
:
1967 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1974 int table_to_json(Table
*t
, JsonVariant
**ret
) {
1975 JsonVariant
**rows
= NULL
, **elements
= NULL
;
1976 _cleanup_free_
size_t *sorted
= NULL
;
1977 size_t n_rows
, i
, j
, display_columns
;
1982 /* Ensure we have no incomplete rows */
1983 assert(t
->n_cells
% t
->n_columns
== 0);
1985 n_rows
= t
->n_cells
/ t
->n_columns
;
1986 assert(n_rows
> 0); /* at least the header row must be complete */
1989 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1991 sorted
= new(size_t, n_rows
);
1997 for (i
= 0; i
< n_rows
; i
++)
1998 sorted
[i
] = i
* t
->n_columns
;
2000 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2004 display_columns
= t
->n_display_map
;
2006 display_columns
= t
->n_columns
;
2007 assert(display_columns
> 0);
2009 elements
= new0(JsonVariant
*, display_columns
* 2);
2015 for (j
= 0; j
< display_columns
; j
++) {
2018 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2020 r
= table_data_to_json(d
, elements
+ j
*2);
2025 rows
= new0(JsonVariant
*, n_rows
-1);
2031 for (i
= 1; i
< n_rows
; i
++) {
2035 row
= t
->data
+ sorted
[i
];
2037 row
= t
->data
+ i
* t
->n_columns
;
2039 for (j
= 0; j
< display_columns
; j
++) {
2043 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2046 elements
[k
] = json_variant_unref(elements
[k
]);
2048 r
= table_data_to_json(d
, elements
+ k
);
2053 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2058 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2062 json_variant_unref_many(rows
, n_rows
-1);
2067 json_variant_unref_many(elements
, display_columns
*2);
2074 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2075 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2083 r
= table_to_json(t
, &v
);
2087 json_variant_dump(v
, flags
, f
, NULL
);
2089 return fflush_and_check(f
);