1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "alloc-util.h"
12 #include "format-table.h"
13 #include "format-util.h"
15 #include "id128-util.h"
16 #include "in-addr-util.h"
17 #include "locale-util.h"
18 #include "memory-util.h"
20 #include "parse-util.h"
21 #include "path-util.h"
22 #include "pretty-print.h"
23 #include "sort-util.h"
24 #include "string-util.h"
26 #include "terminal-util.h"
27 #include "time-util.h"
31 #define DEFAULT_WEIGHT 100
34 A few notes on implementation details:
36 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
37 table. It can be easily converted to an index number and back.
39 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
40 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
41 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
42 outside only sees Table and TableCell.
44 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
47 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
48 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
49 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
50 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
52 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
53 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
54 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
55 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
58 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
59 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
60 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
63 typedef struct TableData
{
67 size_t minimum_width
; /* minimum width for the column */
68 size_t maximum_width
; /* maximum width for the column */
69 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
70 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
71 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
73 bool uppercase
; /* Uppercase string on display */
75 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 */
76 const char *rgap_color
; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
77 char *url
; /* A URL to use for a clickable hyperlink */
78 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
81 uint8_t data
[0]; /* data is generic array */
98 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
100 union in_addr_union address
;
102 /* … add more here as we start supporting more cell data types … */
106 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
111 i
= PTR_TO_SIZE(cell
);
117 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
118 assert(index
!= (size_t) -1);
119 return SIZE_TO_PTR(index
+ 1);
126 bool header
; /* Whether to show the header row? */
127 size_t width
; /* If == 0 format this as wide as necessary. If (size_t) -1 format this to console
128 * width or less wide, but not wider. Otherwise the width to format this table in. */
129 size_t cell_height_max
; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If (size_t) -1 then no limit is set, the default. == 0 is not allowed.) */
134 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 */
135 size_t n_display_map
;
137 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
145 Table
*table_new_raw(size_t n_columns
) {
146 _cleanup_(table_unrefp
) Table
*t
= NULL
;
148 assert(n_columns
> 0);
154 *t
= (struct Table
) {
155 .n_columns
= n_columns
,
157 .width
= (size_t) -1,
158 .cell_height_max
= (size_t) -1,
164 Table
*table_new_internal(const char *first_header
, ...) {
165 _cleanup_(table_unrefp
) Table
*t
= NULL
;
166 size_t n_columns
= 1;
171 assert(first_header
);
173 va_start(ap
, first_header
);
175 h
= va_arg(ap
, const char*);
183 t
= table_new_raw(n_columns
);
187 va_start(ap
, first_header
);
188 for (h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
191 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
197 /* Make the table header uppercase */
198 r
= table_set_uppercase(t
, cell
, true);
206 assert(t
->n_columns
== t
->n_cells
);
210 static TableData
*table_data_free(TableData
*d
) {
216 if (d
->type
== TABLE_STRV
)
222 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
223 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
225 Table
*table_unref(Table
*t
) {
231 for (i
= 0; i
< t
->n_cells
; i
++)
232 table_data_unref(t
->data
[i
]);
235 free(t
->display_map
);
237 free(t
->reverse_map
);
238 free(t
->empty_string
);
243 static size_t table_data_size(TableDataType type
, const void *data
) {
252 return strlen(data
) + 1;
255 return sizeof(char **);
260 case TABLE_TIMESTAMP
:
261 case TABLE_TIMESTAMP_UTC
:
262 case TABLE_TIMESTAMP_RELATIVE
:
264 case TABLE_TIMESPAN_MSEC
:
265 return sizeof(usec_t
);
271 return sizeof(uint64_t);
275 return sizeof(uint32_t);
279 return sizeof(uint16_t);
283 return sizeof(uint8_t);
292 return sizeof(struct in_addr
);
295 return sizeof(struct in6_addr
);
299 return sizeof(sd_id128_t
);
302 assert_not_reached("Uh? Unexpected cell type");
306 static bool table_data_matches(
310 size_t minimum_width
,
311 size_t maximum_width
,
313 unsigned align_percent
,
314 unsigned ellipsize_percent
) {
322 if (d
->minimum_width
!= minimum_width
)
325 if (d
->maximum_width
!= maximum_width
)
328 if (d
->weight
!= weight
)
331 if (d
->align_percent
!= align_percent
)
334 if (d
->ellipsize_percent
!= ellipsize_percent
)
337 /* If a color/url/uppercase flag is set, refuse to merge */
338 if (d
->color
|| d
->rgap_color
)
345 k
= table_data_size(type
, data
);
346 l
= table_data_size(d
->type
, d
->data
);
350 return memcmp_safe(data
, d
->data
, l
) == 0;
353 static TableData
*table_data_new(
356 size_t minimum_width
,
357 size_t maximum_width
,
359 unsigned align_percent
,
360 unsigned ellipsize_percent
) {
362 _cleanup_free_ TableData
*d
= NULL
;
365 data_size
= table_data_size(type
, data
);
367 d
= malloc0(offsetof(TableData
, data
) + data_size
);
373 d
->minimum_width
= minimum_width
;
374 d
->maximum_width
= maximum_width
;
376 d
->align_percent
= align_percent
;
377 d
->ellipsize_percent
= ellipsize_percent
;
379 if (type
== TABLE_STRV
) {
380 d
->strv
= strv_copy(data
);
384 memcpy_safe(d
->data
, data
, data_size
);
389 int table_add_cell_full(
391 TableCell
**ret_cell
,
394 size_t minimum_width
,
395 size_t maximum_width
,
397 unsigned align_percent
,
398 unsigned ellipsize_percent
) {
400 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
405 assert(type
< _TABLE_DATA_TYPE_MAX
);
407 /* Special rule: patch NULL data fields to the empty field */
411 /* Determine the cell adjacent to the current one, but one row up */
412 if (t
->n_cells
>= t
->n_columns
)
413 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
417 /* If formatting parameters are left unspecified, copy from the previous row */
418 if (minimum_width
== (size_t) -1)
419 minimum_width
= p
? p
->minimum_width
: 1;
421 if (weight
== (unsigned) -1)
422 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
424 if (align_percent
== (unsigned) -1)
425 align_percent
= p
? p
->align_percent
: 0;
427 if (ellipsize_percent
== (unsigned) -1)
428 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
430 assert(align_percent
<= 100);
431 assert(ellipsize_percent
<= 100);
433 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
434 * formatting. Let's see if we can reuse the cell data and ref it once more. */
436 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
437 d
= table_data_ref(p
);
439 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
444 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
448 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
450 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
455 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
456 _cleanup_free_
char *buffer
= NULL
;
460 va_start(ap
, format
);
461 r
= vasprintf(&buffer
, format
, ap
);
466 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
469 int table_fill_empty(Table
*t
, size_t until_column
) {
474 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
475 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
477 if (until_column
>= t
->n_columns
)
481 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
485 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
490 int table_dup_cell(Table
*t
, TableCell
*cell
) {
495 /* Add the data of the specified cell a second time as a new cell to the end. */
497 i
= TABLE_CELL_TO_INDEX(cell
);
501 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
504 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
508 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
509 _cleanup_free_
char *curl
= NULL
;
515 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
516 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
518 i
= TABLE_CELL_TO_INDEX(cell
);
522 assert_se(od
= t
->data
[i
]);
526 assert(od
->n_ref
> 1);
529 curl
= strdup(od
->url
);
541 od
->ellipsize_percent
);
545 nd
->color
= od
->color
;
546 nd
->rgap_color
= od
->rgap_color
;
547 nd
->url
= TAKE_PTR(curl
);
548 nd
->uppercase
= od
->uppercase
;
550 table_data_unref(od
);
553 assert(nd
->n_ref
== 1);
558 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
564 /* Get the data object of the specified cell, or NULL if it doesn't exist */
566 i
= TABLE_CELL_TO_INDEX(cell
);
571 assert(t
->data
[i
]->n_ref
> 0);
576 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
582 if (minimum_width
== (size_t) -1)
585 r
= table_dedup_cell(t
, cell
);
589 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
593 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
599 r
= table_dedup_cell(t
, cell
);
603 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
607 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
613 if (weight
== (unsigned) -1)
614 weight
= DEFAULT_WEIGHT
;
616 r
= table_dedup_cell(t
, cell
);
620 table_get_data(t
, cell
)->weight
= weight
;
624 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
630 if (percent
== (unsigned) -1)
633 assert(percent
<= 100);
635 r
= table_dedup_cell(t
, cell
);
639 table_get_data(t
, cell
)->align_percent
= percent
;
643 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
649 if (percent
== (unsigned) -1)
652 assert(percent
<= 100);
654 r
= table_dedup_cell(t
, cell
);
658 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
662 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
668 r
= table_dedup_cell(t
, cell
);
672 table_get_data(t
, cell
)->color
= empty_to_null(color
);
676 int table_set_rgap_color(Table
*t
, TableCell
*cell
, const char *color
) {
682 r
= table_dedup_cell(t
, cell
);
686 table_get_data(t
, cell
)->rgap_color
= empty_to_null(color
);
690 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
691 _cleanup_free_
char *copy
= NULL
;
703 r
= table_dedup_cell(t
, cell
);
707 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
710 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
717 r
= table_dedup_cell(t
, cell
);
721 assert_se(d
= table_get_data(t
, cell
));
723 if (d
->uppercase
== b
)
726 d
->formatted
= mfree(d
->formatted
);
731 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
732 _cleanup_free_
char *curl
= NULL
;
739 i
= TABLE_CELL_TO_INDEX(cell
);
743 assert_se(od
= t
->data
[i
]);
746 curl
= strdup(od
->url
);
758 od
->ellipsize_percent
);
762 nd
->color
= od
->color
;
763 nd
->rgap_color
= od
->rgap_color
;
764 nd
->url
= TAKE_PTR(curl
);
765 nd
->uppercase
= od
->uppercase
;
767 table_data_unref(od
);
773 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
776 TableCell
*last_cell
= NULL
;
780 assert(first_type
>= 0);
781 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
785 va_start(ap
, first_type
);
804 union in_addr_union address
;
816 data
= va_arg(ap
, const char *);
820 data
= va_arg(ap
, char * const *);
824 buffer
.b
= va_arg(ap
, int);
828 case TABLE_TIMESTAMP
:
829 case TABLE_TIMESTAMP_UTC
:
830 case TABLE_TIMESTAMP_RELATIVE
:
832 case TABLE_TIMESPAN_MSEC
:
833 buffer
.usec
= va_arg(ap
, usec_t
);
839 buffer
.size
= va_arg(ap
, uint64_t);
844 buffer
.int_val
= va_arg(ap
, int);
845 data
= &buffer
.int_val
;
849 int x
= va_arg(ap
, int);
850 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
858 int x
= va_arg(ap
, int);
859 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
862 data
= &buffer
.int16
;
867 buffer
.int32
= va_arg(ap
, int32_t);
868 data
= &buffer
.int32
;
872 buffer
.int64
= va_arg(ap
, int64_t);
873 data
= &buffer
.int64
;
877 buffer
.uint_val
= va_arg(ap
, unsigned);
878 data
= &buffer
.uint_val
;
882 unsigned x
= va_arg(ap
, unsigned);
883 assert(x
<= UINT8_MAX
);
886 data
= &buffer
.uint8
;
891 unsigned x
= va_arg(ap
, unsigned);
892 assert(x
<= UINT16_MAX
);
895 data
= &buffer
.uint16
;
900 buffer
.uint32
= va_arg(ap
, uint32_t);
901 data
= &buffer
.uint32
;
905 buffer
.uint64
= va_arg(ap
, uint64_t);
906 data
= &buffer
.uint64
;
910 buffer
.percent
= va_arg(ap
, int);
911 data
= &buffer
.percent
;
915 buffer
.ifindex
= va_arg(ap
, int);
916 data
= &buffer
.ifindex
;
920 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
921 data
= &buffer
.address
.in
;
925 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
926 data
= &buffer
.address
.in6
;
931 buffer
.id128
= va_arg(ap
, sd_id128_t
);
932 data
= &buffer
.id128
;
935 case TABLE_SET_MINIMUM_WIDTH
: {
936 size_t w
= va_arg(ap
, size_t);
938 r
= table_set_minimum_width(t
, last_cell
, w
);
942 case TABLE_SET_MAXIMUM_WIDTH
: {
943 size_t w
= va_arg(ap
, size_t);
944 r
= table_set_maximum_width(t
, last_cell
, w
);
948 case TABLE_SET_WEIGHT
: {
949 unsigned w
= va_arg(ap
, unsigned);
950 r
= table_set_weight(t
, last_cell
, w
);
954 case TABLE_SET_ALIGN_PERCENT
: {
955 unsigned p
= va_arg(ap
, unsigned);
956 r
= table_set_align_percent(t
, last_cell
, p
);
960 case TABLE_SET_ELLIPSIZE_PERCENT
: {
961 unsigned p
= va_arg(ap
, unsigned);
962 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
966 case TABLE_SET_COLOR
: {
967 const char *c
= va_arg(ap
, const char*);
968 r
= table_set_color(t
, last_cell
, c
);
972 case TABLE_SET_RGAP_COLOR
: {
973 const char *c
= va_arg(ap
, const char*);
974 r
= table_set_rgap_color(t
, last_cell
, c
);
978 case TABLE_SET_BOTH_COLORS
: {
979 const char *c
= va_arg(ap
, const char*);
981 r
= table_set_color(t
, last_cell
, c
);
987 r
= table_set_rgap_color(t
, last_cell
, c
);
991 case TABLE_SET_URL
: {
992 const char *u
= va_arg(ap
, const char*);
993 r
= table_set_url(t
, last_cell
, u
);
997 case TABLE_SET_UPPERCASE
: {
998 int u
= va_arg(ap
, int);
999 r
= table_set_uppercase(t
, last_cell
, u
);
1003 case _TABLE_DATA_TYPE_MAX
:
1004 /* Used as end marker */
1009 assert_not_reached("Uh? Unexpected data type.");
1012 if (type
< _TABLE_DATA_TYPE_MAX
)
1013 r
= table_add_cell(t
, &last_cell
, type
, data
);
1020 type
= va_arg(ap
, TableDataType
);
1024 void table_set_header(Table
*t
, bool b
) {
1030 void table_set_width(Table
*t
, size_t width
) {
1036 void table_set_cell_height_max(Table
*t
, size_t height
) {
1038 assert(height
>= 1 || height
== (size_t) -1);
1040 t
->cell_height_max
= height
;
1043 int table_set_empty_string(Table
*t
, const char *empty
) {
1046 return free_and_strdup(&t
->empty_string
, empty
);
1049 int table_set_display_all(Table
*t
) {
1054 allocated
= t
->n_display_map
;
1056 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, allocated
)))
1059 for (size_t i
= 0; i
< t
->n_columns
; i
++)
1060 t
->display_map
[i
] = i
;
1062 t
->n_display_map
= t
->n_columns
;
1067 int table_set_display(Table
*t
, size_t first_column
, ...) {
1068 size_t allocated
, column
;
1073 allocated
= t
->n_display_map
;
1074 column
= first_column
;
1076 va_start(ap
, first_column
);
1078 assert(column
< t
->n_columns
);
1080 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
1085 t
->display_map
[t
->n_display_map
++] = column
;
1087 column
= va_arg(ap
, size_t);
1088 if (column
== (size_t) -1)
1097 int table_set_sort(Table
*t
, size_t first_column
, ...) {
1098 size_t allocated
, column
;
1103 allocated
= t
->n_sort_map
;
1104 column
= first_column
;
1106 va_start(ap
, first_column
);
1108 assert(column
< t
->n_columns
);
1110 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1115 t
->sort_map
[t
->n_sort_map
++] = column
;
1117 column
= va_arg(ap
, size_t);
1118 if (column
== (size_t) -1)
1126 int table_hide_column_from_display(Table
*t
, size_t column
) {
1127 size_t allocated
, cur
= 0;
1131 assert(column
< t
->n_columns
);
1133 /* If the display map is empty, initialize it with all available columns */
1134 if (!t
->display_map
) {
1135 r
= table_set_display_all(t
);
1140 allocated
= t
->n_display_map
;
1142 for (size_t i
= 0; i
< allocated
; i
++) {
1143 if (t
->display_map
[i
] == column
)
1146 t
->display_map
[cur
++] = t
->display_map
[i
];
1149 t
->n_display_map
= cur
;
1154 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1158 if (a
->type
== b
->type
) {
1160 /* We only define ordering for cells of the same data type. If cells with different data types are
1161 * compared we follow the order the cells were originally added in */
1166 return strcmp(a
->string
, b
->string
);
1169 return path_compare(a
->string
, b
->string
);
1172 return strv_compare(a
->strv
, b
->strv
);
1175 if (!a
->boolean
&& b
->boolean
)
1177 if (a
->boolean
&& !b
->boolean
)
1181 case TABLE_TIMESTAMP
:
1182 case TABLE_TIMESTAMP_UTC
:
1183 case TABLE_TIMESTAMP_RELATIVE
:
1184 return CMP(a
->timestamp
, b
->timestamp
);
1186 case TABLE_TIMESPAN
:
1187 case TABLE_TIMESPAN_MSEC
:
1188 return CMP(a
->timespan
, b
->timespan
);
1192 return CMP(a
->size
, b
->size
);
1195 return CMP(a
->int_val
, b
->int_val
);
1198 return CMP(a
->int8
, b
->int8
);
1201 return CMP(a
->int16
, b
->int16
);
1204 return CMP(a
->int32
, b
->int32
);
1207 return CMP(a
->int64
, b
->int64
);
1210 return CMP(a
->uint_val
, b
->uint_val
);
1213 return CMP(a
->uint8
, b
->uint8
);
1216 return CMP(a
->uint16
, b
->uint16
);
1219 return CMP(a
->uint32
, b
->uint32
);
1222 return CMP(a
->uint64
, b
->uint64
);
1225 return CMP(a
->percent
, b
->percent
);
1228 return CMP(a
->ifindex
, b
->ifindex
);
1231 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1233 case TABLE_IN6_ADDR
:
1234 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1238 return memcmp(&a
->id128
, &b
->id128
, sizeof(sd_id128_t
));
1245 /* Generic fallback using the original order in which the cells where added. */
1246 return CMP(index_a
, index_b
);
1249 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1254 assert(t
->sort_map
);
1256 /* Make sure the header stays at the beginning */
1257 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1259 if (*a
< t
->n_columns
)
1261 if (*b
< t
->n_columns
)
1264 /* Order other lines by the sorting map */
1265 for (i
= 0; i
< t
->n_sort_map
; i
++) {
1268 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1269 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1271 r
= cell_data_compare(d
, *a
, dd
, *b
);
1273 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1276 /* Order identical lines by the order there were originally added in */
1280 static const char *table_data_format(Table
*t
, TableData
*d
, bool avoid_uppercasing
) {
1284 return d
->formatted
;
1288 return strempty(t
->empty_string
);
1292 if (d
->uppercase
&& !avoid_uppercasing
) {
1295 d
->formatted
= new(char, strlen(d
->string
) + 1);
1299 for (p
= d
->string
, q
= d
->formatted
; *p
; p
++, q
++)
1300 *q
= (char) toupper((unsigned char) *p
);
1303 return d
->formatted
;
1311 if (strv_isempty(d
->strv
))
1312 return strempty(t
->empty_string
);
1314 p
= strv_join(d
->strv
, "\n");
1323 return yes_no(d
->boolean
);
1325 case TABLE_TIMESTAMP
:
1326 case TABLE_TIMESTAMP_UTC
:
1327 case TABLE_TIMESTAMP_RELATIVE
: {
1328 _cleanup_free_
char *p
;
1331 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1335 if (d
->type
== TABLE_TIMESTAMP
)
1336 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1337 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1338 ret
= format_timestamp_style(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
, TIMESTAMP_UTC
);
1340 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1344 d
->formatted
= TAKE_PTR(p
);
1348 case TABLE_TIMESPAN
:
1349 case TABLE_TIMESPAN_MSEC
: {
1350 _cleanup_free_
char *p
;
1352 p
= new(char, FORMAT_TIMESPAN_MAX
);
1356 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1357 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1360 d
->formatted
= TAKE_PTR(p
);
1365 _cleanup_free_
char *p
;
1367 p
= new(char, FORMAT_BYTES_MAX
);
1371 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1374 d
->formatted
= TAKE_PTR(p
);
1379 _cleanup_free_
char *p
;
1382 p
= new(char, FORMAT_BYTES_MAX
+2);
1386 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1390 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1392 d
->formatted
= TAKE_PTR(p
);
1397 _cleanup_free_
char *p
;
1399 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1403 sprintf(p
, "%i", d
->int_val
);
1404 d
->formatted
= TAKE_PTR(p
);
1409 _cleanup_free_
char *p
;
1411 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1415 sprintf(p
, "%" PRIi8
, d
->int8
);
1416 d
->formatted
= TAKE_PTR(p
);
1421 _cleanup_free_
char *p
;
1423 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1427 sprintf(p
, "%" PRIi16
, d
->int16
);
1428 d
->formatted
= TAKE_PTR(p
);
1433 _cleanup_free_
char *p
;
1435 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1439 sprintf(p
, "%" PRIi32
, d
->int32
);
1440 d
->formatted
= TAKE_PTR(p
);
1445 _cleanup_free_
char *p
;
1447 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1451 sprintf(p
, "%" PRIi64
, d
->int64
);
1452 d
->formatted
= TAKE_PTR(p
);
1457 _cleanup_free_
char *p
;
1459 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1463 sprintf(p
, "%u", d
->uint_val
);
1464 d
->formatted
= TAKE_PTR(p
);
1469 _cleanup_free_
char *p
;
1471 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1475 sprintf(p
, "%" PRIu8
, d
->uint8
);
1476 d
->formatted
= TAKE_PTR(p
);
1480 case TABLE_UINT16
: {
1481 _cleanup_free_
char *p
;
1483 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1487 sprintf(p
, "%" PRIu16
, d
->uint16
);
1488 d
->formatted
= TAKE_PTR(p
);
1492 case TABLE_UINT32
: {
1493 _cleanup_free_
char *p
;
1495 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1499 sprintf(p
, "%" PRIu32
, d
->uint32
);
1500 d
->formatted
= TAKE_PTR(p
);
1504 case TABLE_UINT64
: {
1505 _cleanup_free_
char *p
;
1507 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1511 sprintf(p
, "%" PRIu64
, d
->uint64
);
1512 d
->formatted
= TAKE_PTR(p
);
1516 case TABLE_PERCENT
: {
1517 _cleanup_free_
char *p
;
1519 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1523 sprintf(p
, "%i%%" , d
->percent
);
1524 d
->formatted
= TAKE_PTR(p
);
1528 case TABLE_IFINDEX
: {
1529 _cleanup_free_
char *p
= NULL
;
1530 char name
[IF_NAMESIZE
+ 1];
1532 if (format_ifname(d
->ifindex
, name
)) {
1537 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1541 d
->formatted
= TAKE_PTR(p
);
1546 case TABLE_IN6_ADDR
: {
1547 _cleanup_free_
char *p
= NULL
;
1549 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1550 &d
->address
, &p
) < 0)
1553 d
->formatted
= TAKE_PTR(p
);
1560 p
= new(char, SD_ID128_STRING_MAX
);
1564 d
->formatted
= sd_id128_to_string(d
->id128
, p
);
1571 p
= new(char, ID128_UUID_STRING_MAX
);
1575 d
->formatted
= id128_to_uuid_string(d
->id128
, p
);
1580 assert_not_reached("Unexpected type?");
1583 return d
->formatted
;
1586 static int console_width_height(
1589 size_t *ret_height
) {
1591 size_t max_width
= 0, height
= 0;
1596 /* Determine the width and height in console character cells the specified string needs. */
1601 p
= strchr(s
, '\n');
1603 _cleanup_free_
char *c
= NULL
;
1605 c
= strndup(s
, p
- s
);
1609 k
= utf8_console_width(c
);
1612 k
= utf8_console_width(s
);
1615 if (k
== (size_t) -1)
1621 } while (!isempty(s
));
1624 *ret_width
= max_width
;
1627 *ret_height
= height
;
1632 static int table_data_requested_width_height(
1636 size_t *ret_height
) {
1638 _cleanup_free_
char *truncated
= NULL
;
1639 bool truncation_applied
= false;
1640 size_t width
, height
;
1644 t
= table_data_format(table
, d
, false);
1648 if (table
->cell_height_max
!= (size_t) -1) {
1649 r
= string_truncate_lines(t
, table
->cell_height_max
, &truncated
);
1653 truncation_applied
= true;
1658 r
= console_width_height(t
, &width
, &height
);
1662 if (d
->maximum_width
!= (size_t) -1 && width
> d
->maximum_width
)
1663 width
= d
->maximum_width
;
1665 if (width
< d
->minimum_width
)
1666 width
= d
->minimum_width
;
1671 *ret_height
= height
;
1673 return truncation_applied
;
1676 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1677 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1678 _cleanup_free_
char *clickable
= NULL
;
1684 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1687 assert(percent
<= 100);
1689 old_length
= strlen(str
);
1692 r
= terminal_urlify(url
, str
, &clickable
);
1696 clickable_length
= strlen(clickable
);
1698 clickable_length
= old_length
;
1700 /* Determine current width on screen */
1702 while (p
< str
+ old_length
) {
1705 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1706 p
++, w
++; /* count invalid chars as 1 */
1710 p
= utf8_next_char(p
);
1711 w
+= unichar_iswide(c
) ? 2 : 1;
1714 /* Already wider than the target, if so, don't do anything */
1715 if (w
>= new_length
)
1716 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1718 /* How much spaces shall we add? An how much on the left side? */
1719 space
= new_length
- w
;
1720 lspace
= space
* percent
/ 100U;
1722 ret
= new(char, space
+ clickable_length
+ 1);
1726 for (i
= 0; i
< lspace
; i
++)
1728 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1729 for (i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1732 ret
[space
+ clickable_length
] = 0;
1736 static bool table_data_isempty(TableData
*d
) {
1739 if (d
->type
== TABLE_EMPTY
)
1742 /* Let's also consider an empty strv as truly empty. */
1743 if (d
->type
== TABLE_STRV
)
1744 return strv_isempty(d
->strv
);
1746 /* Note that an empty string we do not consider empty here! */
1750 static const char* table_data_color(TableData
*d
) {
1756 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1757 if (table_data_isempty(d
))
1763 static const char* table_data_rgap_color(TableData
*d
) {
1767 return d
->rgap_color
;
1772 int table_print(Table
*t
, FILE *f
) {
1773 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1774 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1776 _cleanup_free_
size_t *sorted
= NULL
;
1777 uint64_t *column_weight
, weight_sum
;
1785 /* Ensure we have no incomplete rows */
1786 assert(t
->n_cells
% t
->n_columns
== 0);
1788 n_rows
= t
->n_cells
/ t
->n_columns
;
1789 assert(n_rows
> 0); /* at least the header row must be complete */
1792 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1794 sorted
= new(size_t, n_rows
);
1798 for (i
= 0; i
< n_rows
; i
++)
1799 sorted
[i
] = i
* t
->n_columns
;
1801 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1805 display_columns
= t
->n_display_map
;
1807 display_columns
= t
->n_columns
;
1809 assert(display_columns
> 0);
1811 minimum_width
= newa(size_t, display_columns
);
1812 maximum_width
= newa(size_t, display_columns
);
1813 requested_width
= newa(size_t, display_columns
);
1814 width
= newa(size_t, display_columns
);
1815 column_weight
= newa0(uint64_t, display_columns
);
1817 for (j
= 0; j
< display_columns
; j
++) {
1818 minimum_width
[j
] = 1;
1819 maximum_width
[j
] = (size_t) -1;
1820 requested_width
[j
] = (size_t) -1;
1823 /* First pass: determine column sizes */
1824 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1827 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1828 * hence we don't care for sorted[] during the first pass. */
1829 row
= t
->data
+ i
* t
->n_columns
;
1831 for (j
= 0; j
< display_columns
; j
++) {
1833 size_t req_width
, req_height
;
1835 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1837 r
= table_data_requested_width_height(t
, d
, &req_width
, &req_height
);
1840 if (r
> 0) { /* Truncated because too many lines? */
1841 _cleanup_free_
char *last
= NULL
;
1844 /* If we are going to show only the first few lines of a cell that has
1845 * multiple make sure that we have enough space horizontally to show an
1846 * ellipsis. Hence, let's figure out the last line, and account for its
1847 * length plus ellipsis. */
1849 field
= table_data_format(t
, d
, false);
1853 assert_se(t
->cell_height_max
> 0);
1854 r
= string_extract_line(field
, t
->cell_height_max
-1, &last
);
1858 req_width
= MAX(req_width
,
1859 utf8_console_width(last
) +
1860 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS
)));
1863 /* Determine the biggest width that any cell in this column would like to have */
1864 if (requested_width
[j
] == (size_t) -1 ||
1865 requested_width
[j
] < req_width
)
1866 requested_width
[j
] = req_width
;
1868 /* Determine the minimum width any cell in this column needs */
1869 if (minimum_width
[j
] < d
->minimum_width
)
1870 minimum_width
[j
] = d
->minimum_width
;
1872 /* Determine the maximum width any cell in this column needs */
1873 if (d
->maximum_width
!= (size_t) -1 &&
1874 (maximum_width
[j
] == (size_t) -1 ||
1875 maximum_width
[j
] > d
->maximum_width
))
1876 maximum_width
[j
] = d
->maximum_width
;
1878 /* Determine the full columns weight */
1879 column_weight
[j
] += d
->weight
;
1883 /* One space between each column */
1884 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1886 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1888 for (j
= 0; j
< display_columns
; j
++) {
1889 weight_sum
+= column_weight
[j
];
1891 table_minimum_width
+= minimum_width
[j
];
1893 if (maximum_width
[j
] == (size_t) -1)
1894 table_maximum_width
= (size_t) -1;
1896 table_maximum_width
+= maximum_width
[j
];
1898 table_requested_width
+= requested_width
[j
];
1901 /* Calculate effective table width */
1902 if (t
->width
!= 0 && t
->width
!= (size_t) -1)
1903 table_effective_width
= t
->width
;
1904 else if (t
->width
== 0 || pager_have() || !isatty(STDOUT_FILENO
))
1905 table_effective_width
= table_requested_width
;
1907 table_effective_width
= MIN(table_requested_width
, columns());
1909 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1910 table_effective_width
= table_maximum_width
;
1912 if (table_effective_width
< table_minimum_width
)
1913 table_effective_width
= table_minimum_width
;
1915 if (table_effective_width
>= table_requested_width
) {
1918 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1919 * each column with what it asked for and the distribute the rest. */
1921 extra
= table_effective_width
- table_requested_width
;
1923 for (j
= 0; j
< display_columns
; j
++) {
1926 if (weight_sum
== 0)
1927 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1929 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1931 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1932 width
[j
] = maximum_width
[j
];
1934 if (width
[j
] < minimum_width
[j
])
1935 width
[j
] = minimum_width
[j
];
1937 assert(width
[j
] >= requested_width
[j
]);
1938 delta
= width
[j
] - requested_width
[j
];
1940 /* Subtract what we just added from the rest */
1946 assert(weight_sum
>= column_weight
[j
]);
1947 weight_sum
-= column_weight
[j
];
1951 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1952 * with the minimum they need, and then distribute anything left. */
1953 bool finalize
= false;
1956 extra
= table_effective_width
- table_minimum_width
;
1958 for (j
= 0; j
< display_columns
; j
++)
1959 width
[j
] = (size_t) -1;
1962 bool restart
= false;
1964 for (j
= 0; j
< display_columns
; j
++) {
1967 /* Did this column already get something assigned? If so, let's skip to the next */
1968 if (width
[j
] != (size_t) -1)
1971 if (weight_sum
== 0)
1972 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
1974 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1976 if (w
>= requested_width
[j
]) {
1977 /* Never give more than requested. If we hit a column like this, there's more
1978 * space to allocate to other columns which means we need to restart the
1979 * iteration. However, if we hit a column like this, let's assign it the space
1980 * it wanted for good early.*/
1982 w
= requested_width
[j
];
1985 } else if (!finalize
)
1990 assert(w
>= minimum_width
[j
]);
1991 delta
= w
- minimum_width
[j
];
1993 assert(delta
<= extra
);
1996 assert(weight_sum
>= column_weight
[j
]);
1997 weight_sum
-= column_weight
[j
];
1999 if (restart
&& !finalize
)
2011 /* Second pass: show output */
2012 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
2013 size_t n_subline
= 0;
2018 row
= t
->data
+ sorted
[i
];
2020 row
= t
->data
+ i
* t
->n_columns
;
2023 const char *gap_color
= NULL
;
2024 more_sublines
= false;
2026 for (j
= 0; j
< display_columns
; j
++) {
2027 _cleanup_free_
char *buffer
= NULL
, *extracted
= NULL
;
2028 bool lines_truncated
= false;
2029 const char *field
, *color
= NULL
;
2033 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2035 field
= table_data_format(t
, d
, false);
2039 r
= string_extract_line(field
, n_subline
, &extracted
);
2043 /* There are more lines to come */
2044 if ((t
->cell_height_max
== (size_t) -1 || n_subline
+ 1 < t
->cell_height_max
))
2045 more_sublines
= true; /* There are more lines to come */
2047 lines_truncated
= true;
2052 l
= utf8_console_width(field
);
2054 /* Field is wider than allocated space. Let's ellipsize */
2056 buffer
= ellipsize(field
, width
[j
], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2057 lines_truncated
? 100 : d
->ellipsize_percent
);
2063 if (lines_truncated
) {
2064 _cleanup_free_
char *padded
= NULL
;
2066 /* We truncated more lines of this cell, let's add an
2067 * ellipsis. We first append it, but that might make our
2068 * string grow above what we have space for, hence ellipsize
2069 * right after. This will truncate the ellipsis and add a new
2072 padded
= strjoin(field
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
2076 buffer
= ellipsize(padded
, width
[j
], 100);
2081 l
= utf8_console_width(field
);
2085 _cleanup_free_
char *aligned
= NULL
;
2086 /* Field is shorter than allocated space. Let's align with spaces */
2088 aligned
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
2092 free_and_replace(buffer
, aligned
);
2097 if (l
>= width
[j
] && d
->url
) {
2098 _cleanup_free_
char *clickable
= NULL
;
2100 r
= terminal_urlify(d
->url
, field
, &clickable
);
2104 free_and_replace(buffer
, clickable
);
2108 if (colors_enabled()) {
2110 fputs(gap_color
, f
);
2111 else if (row
== t
->data
) /* underline header line fully, including the column separator */
2112 fputs(ansi_underline(), f
);
2116 fputc(' ', f
); /* column separator left of cell */
2118 if (colors_enabled()) {
2119 color
= table_data_color(d
);
2121 /* Undo gap color */
2122 if (gap_color
|| (color
&& row
== t
->data
))
2123 fputs(ANSI_NORMAL
, f
);
2127 else if (gap_color
&& row
== t
->data
) /* underline header line cell */
2128 fputs(ansi_underline(), f
);
2133 if (colors_enabled() && (color
|| row
== t
->data
))
2134 fputs(ANSI_NORMAL
, f
);
2136 gap_color
= table_data_rgap_color(d
);
2141 } while (more_sublines
);
2144 return fflush_and_check(f
);
2147 int table_format(Table
*t
, char **ret
) {
2148 _cleanup_fclose_
FILE *f
= NULL
;
2153 f
= open_memstream_unlocked(&buf
, &sz
);
2157 r
= table_print(t
, f
);
2168 size_t table_get_rows(Table
*t
) {
2172 assert(t
->n_columns
> 0);
2173 return t
->n_cells
/ t
->n_columns
;
2176 size_t table_get_columns(Table
*t
) {
2180 assert(t
->n_columns
> 0);
2181 return t
->n_columns
;
2184 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
2186 assert(column
< t
->n_columns
);
2188 if (!t
->reverse_map
) {
2192 t
->reverse_map
= new0(bool, t
->n_columns
);
2193 if (!t
->reverse_map
)
2197 t
->reverse_map
[column
] = b
;
2201 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
2206 if (column
>= t
->n_columns
)
2209 i
= row
* t
->n_columns
+ column
;
2210 if (i
>= t
->n_cells
)
2213 return TABLE_INDEX_TO_CELL(i
);
2216 const void *table_get(Table
*t
, TableCell
*cell
) {
2221 d
= table_get_data(t
, cell
);
2228 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
2231 cell
= table_get_cell(t
, row
, column
);
2235 return table_get(t
, cell
);
2238 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
2243 return json_variant_new_null(ret
);
2247 return json_variant_new_string(ret
, d
->string
);
2250 return json_variant_new_array_strv(ret
, d
->strv
);
2253 return json_variant_new_boolean(ret
, d
->boolean
);
2255 case TABLE_TIMESTAMP
:
2256 case TABLE_TIMESTAMP_UTC
:
2257 case TABLE_TIMESTAMP_RELATIVE
:
2258 if (d
->timestamp
== USEC_INFINITY
)
2259 return json_variant_new_null(ret
);
2261 return json_variant_new_unsigned(ret
, d
->timestamp
);
2263 case TABLE_TIMESPAN
:
2264 case TABLE_TIMESPAN_MSEC
:
2265 if (d
->timespan
== USEC_INFINITY
)
2266 return json_variant_new_null(ret
);
2268 return json_variant_new_unsigned(ret
, d
->timespan
);
2272 if (d
->size
== (uint64_t) -1)
2273 return json_variant_new_null(ret
);
2275 return json_variant_new_unsigned(ret
, d
->size
);
2278 return json_variant_new_integer(ret
, d
->int_val
);
2281 return json_variant_new_integer(ret
, d
->int8
);
2284 return json_variant_new_integer(ret
, d
->int16
);
2287 return json_variant_new_integer(ret
, d
->int32
);
2290 return json_variant_new_integer(ret
, d
->int64
);
2293 return json_variant_new_unsigned(ret
, d
->uint_val
);
2296 return json_variant_new_unsigned(ret
, d
->uint8
);
2299 return json_variant_new_unsigned(ret
, d
->uint16
);
2302 return json_variant_new_unsigned(ret
, d
->uint32
);
2305 return json_variant_new_unsigned(ret
, d
->uint64
);
2308 return json_variant_new_integer(ret
, d
->percent
);
2311 return json_variant_new_integer(ret
, d
->ifindex
);
2314 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
2316 case TABLE_IN6_ADDR
:
2317 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
2320 char buf
[SD_ID128_STRING_MAX
];
2321 return json_variant_new_string(ret
, sd_id128_to_string(d
->id128
, buf
));
2325 char buf
[ID128_UUID_STRING_MAX
];
2326 return json_variant_new_string(ret
, id128_to_uuid_string(d
->id128
, buf
));
2334 static char* string_to_json_field_name(const char *f
) {
2337 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2338 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2339 * underscores and leave everything as is. */
2345 for (x
= c
; *x
; x
++)
2352 int table_to_json(Table
*t
, JsonVariant
**ret
) {
2353 JsonVariant
**rows
= NULL
, **elements
= NULL
;
2354 _cleanup_free_
size_t *sorted
= NULL
;
2355 size_t n_rows
, i
, j
, display_columns
;
2360 /* Ensure we have no incomplete rows */
2361 assert(t
->n_cells
% t
->n_columns
== 0);
2363 n_rows
= t
->n_cells
/ t
->n_columns
;
2364 assert(n_rows
> 0); /* at least the header row must be complete */
2367 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2369 sorted
= new(size_t, n_rows
);
2375 for (i
= 0; i
< n_rows
; i
++)
2376 sorted
[i
] = i
* t
->n_columns
;
2378 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2382 display_columns
= t
->n_display_map
;
2384 display_columns
= t
->n_columns
;
2385 assert(display_columns
> 0);
2387 elements
= new0(JsonVariant
*, display_columns
* 2);
2393 for (j
= 0; j
< display_columns
; j
++) {
2394 _cleanup_free_
char *mangled
= NULL
;
2395 const char *formatted
;
2398 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2400 /* Field names must be strings, hence format whatever we got here as a string first */
2401 formatted
= table_data_format(t
, d
, true);
2407 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2408 mangled
= string_to_json_field_name(formatted
);
2414 r
= json_variant_new_string(elements
+ j
*2, mangled
);
2419 rows
= new0(JsonVariant
*, n_rows
-1);
2425 for (i
= 1; i
< n_rows
; i
++) {
2429 row
= t
->data
+ sorted
[i
];
2431 row
= t
->data
+ i
* t
->n_columns
;
2433 for (j
= 0; j
< display_columns
; j
++) {
2437 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2440 elements
[k
] = json_variant_unref(elements
[k
]);
2442 r
= table_data_to_json(d
, elements
+ k
);
2447 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2452 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2456 json_variant_unref_many(rows
, n_rows
-1);
2461 json_variant_unref_many(elements
, display_columns
*2);
2468 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2469 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2477 r
= table_to_json(t
, &v
);
2481 json_variant_dump(v
, flags
, f
, NULL
);
2483 return fflush_and_check(f
);