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 char *url
; /* A URL to use for a clickable hyperlink */
77 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
80 uint8_t data
[0]; /* data is generic array */
97 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
99 union in_addr_union address
;
101 /* … add more here as we start supporting more cell data types … */
105 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
110 i
= PTR_TO_SIZE(cell
);
116 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
117 assert(index
!= (size_t) -1);
118 return SIZE_TO_PTR(index
+ 1);
125 bool header
; /* Whether to show the header row? */
126 size_t width
; /* If == 0 format this as wide as necessary. If (size_t) -1 format this to console
127 * width or less wide, but not wider. Otherwise the width to format this table in. */
128 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.) */
133 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 */
134 size_t n_display_map
;
136 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
144 Table
*table_new_raw(size_t n_columns
) {
145 _cleanup_(table_unrefp
) Table
*t
= NULL
;
147 assert(n_columns
> 0);
153 *t
= (struct Table
) {
154 .n_columns
= n_columns
,
156 .width
= (size_t) -1,
157 .cell_height_max
= (size_t) -1,
163 Table
*table_new_internal(const char *first_header
, ...) {
164 _cleanup_(table_unrefp
) Table
*t
= NULL
;
165 size_t n_columns
= 1;
170 assert(first_header
);
172 va_start(ap
, first_header
);
174 h
= va_arg(ap
, const char*);
182 t
= table_new_raw(n_columns
);
186 va_start(ap
, first_header
);
187 for (h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
190 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
196 /* Make the table header uppercase */
197 r
= table_set_uppercase(t
, cell
, true);
205 assert(t
->n_columns
== t
->n_cells
);
209 static TableData
*table_data_free(TableData
*d
) {
215 if (d
->type
== TABLE_STRV
)
221 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
222 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
224 Table
*table_unref(Table
*t
) {
230 for (i
= 0; i
< t
->n_cells
; i
++)
231 table_data_unref(t
->data
[i
]);
234 free(t
->display_map
);
236 free(t
->reverse_map
);
237 free(t
->empty_string
);
242 static size_t table_data_size(TableDataType type
, const void *data
) {
251 return strlen(data
) + 1;
254 return sizeof(char **);
259 case TABLE_TIMESTAMP
:
260 case TABLE_TIMESTAMP_UTC
:
261 case TABLE_TIMESTAMP_RELATIVE
:
263 case TABLE_TIMESPAN_MSEC
:
264 return sizeof(usec_t
);
270 return sizeof(uint64_t);
274 return sizeof(uint32_t);
278 return sizeof(uint16_t);
282 return sizeof(uint8_t);
291 return sizeof(struct in_addr
);
294 return sizeof(struct in6_addr
);
298 return sizeof(sd_id128_t
);
301 assert_not_reached("Uh? Unexpected cell type");
305 static bool table_data_matches(
309 size_t minimum_width
,
310 size_t maximum_width
,
312 unsigned align_percent
,
313 unsigned ellipsize_percent
) {
321 if (d
->minimum_width
!= minimum_width
)
324 if (d
->maximum_width
!= maximum_width
)
327 if (d
->weight
!= weight
)
330 if (d
->align_percent
!= align_percent
)
333 if (d
->ellipsize_percent
!= ellipsize_percent
)
336 /* If a color/url/uppercase flag is set, refuse to merge */
344 k
= table_data_size(type
, data
);
345 l
= table_data_size(d
->type
, d
->data
);
349 return memcmp_safe(data
, d
->data
, l
) == 0;
352 static TableData
*table_data_new(
355 size_t minimum_width
,
356 size_t maximum_width
,
358 unsigned align_percent
,
359 unsigned ellipsize_percent
) {
361 _cleanup_free_ TableData
*d
= NULL
;
364 data_size
= table_data_size(type
, data
);
366 d
= malloc0(offsetof(TableData
, data
) + data_size
);
372 d
->minimum_width
= minimum_width
;
373 d
->maximum_width
= maximum_width
;
375 d
->align_percent
= align_percent
;
376 d
->ellipsize_percent
= ellipsize_percent
;
378 if (type
== TABLE_STRV
) {
379 d
->strv
= strv_copy(data
);
383 memcpy_safe(d
->data
, data
, data_size
);
388 int table_add_cell_full(
390 TableCell
**ret_cell
,
393 size_t minimum_width
,
394 size_t maximum_width
,
396 unsigned align_percent
,
397 unsigned ellipsize_percent
) {
399 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
404 assert(type
< _TABLE_DATA_TYPE_MAX
);
406 /* Special rule: patch NULL data fields to the empty field */
410 /* Determine the cell adjacent to the current one, but one row up */
411 if (t
->n_cells
>= t
->n_columns
)
412 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
416 /* If formatting parameters are left unspecified, copy from the previous row */
417 if (minimum_width
== (size_t) -1)
418 minimum_width
= p
? p
->minimum_width
: 1;
420 if (weight
== (unsigned) -1)
421 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
423 if (align_percent
== (unsigned) -1)
424 align_percent
= p
? p
->align_percent
: 0;
426 if (ellipsize_percent
== (unsigned) -1)
427 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
429 assert(align_percent
<= 100);
430 assert(ellipsize_percent
<= 100);
432 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
433 * formatting. Let's see if we can reuse the cell data and ref it once more. */
435 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
436 d
= table_data_ref(p
);
438 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
443 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
447 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
449 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
454 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
455 _cleanup_free_
char *buffer
= NULL
;
459 va_start(ap
, format
);
460 r
= vasprintf(&buffer
, format
, ap
);
465 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
468 int table_fill_empty(Table
*t
, size_t until_column
) {
473 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
474 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
476 if (until_column
>= t
->n_columns
)
480 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
484 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
489 int table_dup_cell(Table
*t
, TableCell
*cell
) {
494 /* Add the data of the specified cell a second time as a new cell to the end. */
496 i
= TABLE_CELL_TO_INDEX(cell
);
500 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
503 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
507 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
508 _cleanup_free_
char *curl
= NULL
;
514 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
515 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
517 i
= TABLE_CELL_TO_INDEX(cell
);
521 assert_se(od
= t
->data
[i
]);
525 assert(od
->n_ref
> 1);
528 curl
= strdup(od
->url
);
540 od
->ellipsize_percent
);
544 nd
->color
= od
->color
;
545 nd
->url
= TAKE_PTR(curl
);
546 nd
->uppercase
= od
->uppercase
;
548 table_data_unref(od
);
551 assert(nd
->n_ref
== 1);
556 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
562 /* Get the data object of the specified cell, or NULL if it doesn't exist */
564 i
= TABLE_CELL_TO_INDEX(cell
);
569 assert(t
->data
[i
]->n_ref
> 0);
574 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
580 if (minimum_width
== (size_t) -1)
583 r
= table_dedup_cell(t
, cell
);
587 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
591 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
597 r
= table_dedup_cell(t
, cell
);
601 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
605 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
611 if (weight
== (unsigned) -1)
612 weight
= DEFAULT_WEIGHT
;
614 r
= table_dedup_cell(t
, cell
);
618 table_get_data(t
, cell
)->weight
= weight
;
622 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
628 if (percent
== (unsigned) -1)
631 assert(percent
<= 100);
633 r
= table_dedup_cell(t
, cell
);
637 table_get_data(t
, cell
)->align_percent
= percent
;
641 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
647 if (percent
== (unsigned) -1)
650 assert(percent
<= 100);
652 r
= table_dedup_cell(t
, cell
);
656 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
660 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
666 r
= table_dedup_cell(t
, cell
);
670 table_get_data(t
, cell
)->color
= empty_to_null(color
);
674 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
675 _cleanup_free_
char *copy
= NULL
;
687 r
= table_dedup_cell(t
, cell
);
691 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
694 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
701 r
= table_dedup_cell(t
, cell
);
705 assert_se(d
= table_get_data(t
, cell
));
707 if (d
->uppercase
== b
)
710 d
->formatted
= mfree(d
->formatted
);
715 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
716 _cleanup_free_
char *curl
= NULL
;
723 i
= TABLE_CELL_TO_INDEX(cell
);
727 assert_se(od
= t
->data
[i
]);
730 curl
= strdup(od
->url
);
742 od
->ellipsize_percent
);
746 nd
->color
= od
->color
;
747 nd
->url
= TAKE_PTR(curl
);
748 nd
->uppercase
= od
->uppercase
;
750 table_data_unref(od
);
756 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
759 TableCell
*last_cell
= NULL
;
763 assert(first_type
>= 0);
764 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
768 va_start(ap
, first_type
);
787 union in_addr_union address
;
799 data
= va_arg(ap
, const char *);
803 data
= va_arg(ap
, char * const *);
807 buffer
.b
= va_arg(ap
, int);
811 case TABLE_TIMESTAMP
:
812 case TABLE_TIMESTAMP_UTC
:
813 case TABLE_TIMESTAMP_RELATIVE
:
815 case TABLE_TIMESPAN_MSEC
:
816 buffer
.usec
= va_arg(ap
, usec_t
);
822 buffer
.size
= va_arg(ap
, uint64_t);
827 buffer
.int_val
= va_arg(ap
, int);
828 data
= &buffer
.int_val
;
832 int x
= va_arg(ap
, int);
833 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
841 int x
= va_arg(ap
, int);
842 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
845 data
= &buffer
.int16
;
850 buffer
.int32
= va_arg(ap
, int32_t);
851 data
= &buffer
.int32
;
855 buffer
.int64
= va_arg(ap
, int64_t);
856 data
= &buffer
.int64
;
860 buffer
.uint_val
= va_arg(ap
, unsigned);
861 data
= &buffer
.uint_val
;
865 unsigned x
= va_arg(ap
, unsigned);
866 assert(x
<= UINT8_MAX
);
869 data
= &buffer
.uint8
;
874 unsigned x
= va_arg(ap
, unsigned);
875 assert(x
<= UINT16_MAX
);
878 data
= &buffer
.uint16
;
883 buffer
.uint32
= va_arg(ap
, uint32_t);
884 data
= &buffer
.uint32
;
888 buffer
.uint64
= va_arg(ap
, uint64_t);
889 data
= &buffer
.uint64
;
893 buffer
.percent
= va_arg(ap
, int);
894 data
= &buffer
.percent
;
898 buffer
.ifindex
= va_arg(ap
, int);
899 data
= &buffer
.ifindex
;
903 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
904 data
= &buffer
.address
.in
;
908 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
909 data
= &buffer
.address
.in6
;
914 buffer
.id128
= va_arg(ap
, sd_id128_t
);
915 data
= &buffer
.id128
;
918 case TABLE_SET_MINIMUM_WIDTH
: {
919 size_t w
= va_arg(ap
, size_t);
921 r
= table_set_minimum_width(t
, last_cell
, w
);
925 case TABLE_SET_MAXIMUM_WIDTH
: {
926 size_t w
= va_arg(ap
, size_t);
927 r
= table_set_maximum_width(t
, last_cell
, w
);
931 case TABLE_SET_WEIGHT
: {
932 unsigned w
= va_arg(ap
, unsigned);
933 r
= table_set_weight(t
, last_cell
, w
);
937 case TABLE_SET_ALIGN_PERCENT
: {
938 unsigned p
= va_arg(ap
, unsigned);
939 r
= table_set_align_percent(t
, last_cell
, p
);
943 case TABLE_SET_ELLIPSIZE_PERCENT
: {
944 unsigned p
= va_arg(ap
, unsigned);
945 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
949 case TABLE_SET_COLOR
: {
950 const char *c
= va_arg(ap
, const char*);
951 r
= table_set_color(t
, last_cell
, c
);
955 case TABLE_SET_URL
: {
956 const char *u
= va_arg(ap
, const char*);
957 r
= table_set_url(t
, last_cell
, u
);
961 case TABLE_SET_UPPERCASE
: {
962 int u
= va_arg(ap
, int);
963 r
= table_set_uppercase(t
, last_cell
, u
);
967 case _TABLE_DATA_TYPE_MAX
:
968 /* Used as end marker */
973 assert_not_reached("Uh? Unexpected data type.");
976 if (type
< _TABLE_DATA_TYPE_MAX
)
977 r
= table_add_cell(t
, &last_cell
, type
, data
);
984 type
= va_arg(ap
, TableDataType
);
988 void table_set_header(Table
*t
, bool b
) {
994 void table_set_width(Table
*t
, size_t width
) {
1000 void table_set_cell_height_max(Table
*t
, size_t height
) {
1002 assert(height
>= 1 || height
== (size_t) -1);
1004 t
->cell_height_max
= height
;
1007 int table_set_empty_string(Table
*t
, const char *empty
) {
1010 return free_and_strdup(&t
->empty_string
, empty
);
1013 int table_set_display_all(Table
*t
) {
1018 allocated
= t
->n_display_map
;
1020 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, allocated
)))
1023 for (size_t i
= 0; i
< t
->n_columns
; i
++)
1024 t
->display_map
[i
] = i
;
1026 t
->n_display_map
= t
->n_columns
;
1031 int table_set_display(Table
*t
, size_t first_column
, ...) {
1032 size_t allocated
, column
;
1037 allocated
= t
->n_display_map
;
1038 column
= first_column
;
1040 va_start(ap
, first_column
);
1042 assert(column
< t
->n_columns
);
1044 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
1049 t
->display_map
[t
->n_display_map
++] = column
;
1051 column
= va_arg(ap
, size_t);
1052 if (column
== (size_t) -1)
1061 int table_set_sort(Table
*t
, size_t first_column
, ...) {
1062 size_t allocated
, column
;
1067 allocated
= t
->n_sort_map
;
1068 column
= first_column
;
1070 va_start(ap
, first_column
);
1072 assert(column
< t
->n_columns
);
1074 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1079 t
->sort_map
[t
->n_sort_map
++] = column
;
1081 column
= va_arg(ap
, size_t);
1082 if (column
== (size_t) -1)
1090 int table_hide_column_from_display(Table
*t
, size_t column
) {
1091 size_t allocated
, cur
= 0;
1095 assert(column
< t
->n_columns
);
1097 /* If the display map is empty, initialize it with all available columns */
1098 if (!t
->display_map
) {
1099 r
= table_set_display_all(t
);
1104 allocated
= t
->n_display_map
;
1106 for (size_t i
= 0; i
< allocated
; i
++) {
1107 if (t
->display_map
[i
] == column
)
1110 t
->display_map
[cur
++] = t
->display_map
[i
];
1113 t
->n_display_map
= cur
;
1118 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1122 if (a
->type
== b
->type
) {
1124 /* We only define ordering for cells of the same data type. If cells with different data types are
1125 * compared we follow the order the cells were originally added in */
1130 return strcmp(a
->string
, b
->string
);
1133 return path_compare(a
->string
, b
->string
);
1136 return strv_compare(a
->strv
, b
->strv
);
1139 if (!a
->boolean
&& b
->boolean
)
1141 if (a
->boolean
&& !b
->boolean
)
1145 case TABLE_TIMESTAMP
:
1146 case TABLE_TIMESTAMP_UTC
:
1147 case TABLE_TIMESTAMP_RELATIVE
:
1148 return CMP(a
->timestamp
, b
->timestamp
);
1150 case TABLE_TIMESPAN
:
1151 case TABLE_TIMESPAN_MSEC
:
1152 return CMP(a
->timespan
, b
->timespan
);
1156 return CMP(a
->size
, b
->size
);
1159 return CMP(a
->int_val
, b
->int_val
);
1162 return CMP(a
->int8
, b
->int8
);
1165 return CMP(a
->int16
, b
->int16
);
1168 return CMP(a
->int32
, b
->int32
);
1171 return CMP(a
->int64
, b
->int64
);
1174 return CMP(a
->uint_val
, b
->uint_val
);
1177 return CMP(a
->uint8
, b
->uint8
);
1180 return CMP(a
->uint16
, b
->uint16
);
1183 return CMP(a
->uint32
, b
->uint32
);
1186 return CMP(a
->uint64
, b
->uint64
);
1189 return CMP(a
->percent
, b
->percent
);
1192 return CMP(a
->ifindex
, b
->ifindex
);
1195 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1197 case TABLE_IN6_ADDR
:
1198 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1202 return memcmp(&a
->id128
, &b
->id128
, sizeof(sd_id128_t
));
1209 /* Generic fallback using the original order in which the cells where added. */
1210 return CMP(index_a
, index_b
);
1213 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1218 assert(t
->sort_map
);
1220 /* Make sure the header stays at the beginning */
1221 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1223 if (*a
< t
->n_columns
)
1225 if (*b
< t
->n_columns
)
1228 /* Order other lines by the sorting map */
1229 for (i
= 0; i
< t
->n_sort_map
; i
++) {
1232 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1233 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1235 r
= cell_data_compare(d
, *a
, dd
, *b
);
1237 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1240 /* Order identical lines by the order there were originally added in */
1244 static const char *table_data_format(Table
*t
, TableData
*d
) {
1248 return d
->formatted
;
1252 return strempty(t
->empty_string
);
1259 d
->formatted
= new(char, strlen(d
->string
) + 1);
1263 for (p
= d
->string
, q
= d
->formatted
; *p
; p
++, q
++)
1264 *q
= (char) toupper((unsigned char) *p
);
1267 return d
->formatted
;
1275 p
= strv_join(d
->strv
, "\n");
1284 return yes_no(d
->boolean
);
1286 case TABLE_TIMESTAMP
:
1287 case TABLE_TIMESTAMP_UTC
:
1288 case TABLE_TIMESTAMP_RELATIVE
: {
1289 _cleanup_free_
char *p
;
1292 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1296 if (d
->type
== TABLE_TIMESTAMP
)
1297 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1298 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1299 ret
= format_timestamp_utc(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1301 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1305 d
->formatted
= TAKE_PTR(p
);
1309 case TABLE_TIMESPAN
:
1310 case TABLE_TIMESPAN_MSEC
: {
1311 _cleanup_free_
char *p
;
1313 p
= new(char, FORMAT_TIMESPAN_MAX
);
1317 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1318 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1321 d
->formatted
= TAKE_PTR(p
);
1326 _cleanup_free_
char *p
;
1328 p
= new(char, FORMAT_BYTES_MAX
);
1332 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1335 d
->formatted
= TAKE_PTR(p
);
1340 _cleanup_free_
char *p
;
1343 p
= new(char, FORMAT_BYTES_MAX
+2);
1347 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1351 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1353 d
->formatted
= TAKE_PTR(p
);
1358 _cleanup_free_
char *p
;
1360 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1364 sprintf(p
, "%i", d
->int_val
);
1365 d
->formatted
= TAKE_PTR(p
);
1370 _cleanup_free_
char *p
;
1372 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1376 sprintf(p
, "%" PRIi8
, d
->int8
);
1377 d
->formatted
= TAKE_PTR(p
);
1382 _cleanup_free_
char *p
;
1384 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1388 sprintf(p
, "%" PRIi16
, d
->int16
);
1389 d
->formatted
= TAKE_PTR(p
);
1394 _cleanup_free_
char *p
;
1396 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1400 sprintf(p
, "%" PRIi32
, d
->int32
);
1401 d
->formatted
= TAKE_PTR(p
);
1406 _cleanup_free_
char *p
;
1408 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1412 sprintf(p
, "%" PRIi64
, d
->int64
);
1413 d
->formatted
= TAKE_PTR(p
);
1418 _cleanup_free_
char *p
;
1420 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1424 sprintf(p
, "%u", d
->uint_val
);
1425 d
->formatted
= TAKE_PTR(p
);
1430 _cleanup_free_
char *p
;
1432 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1436 sprintf(p
, "%" PRIu8
, d
->uint8
);
1437 d
->formatted
= TAKE_PTR(p
);
1441 case TABLE_UINT16
: {
1442 _cleanup_free_
char *p
;
1444 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1448 sprintf(p
, "%" PRIu16
, d
->uint16
);
1449 d
->formatted
= TAKE_PTR(p
);
1453 case TABLE_UINT32
: {
1454 _cleanup_free_
char *p
;
1456 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1460 sprintf(p
, "%" PRIu32
, d
->uint32
);
1461 d
->formatted
= TAKE_PTR(p
);
1465 case TABLE_UINT64
: {
1466 _cleanup_free_
char *p
;
1468 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1472 sprintf(p
, "%" PRIu64
, d
->uint64
);
1473 d
->formatted
= TAKE_PTR(p
);
1477 case TABLE_PERCENT
: {
1478 _cleanup_free_
char *p
;
1480 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1484 sprintf(p
, "%i%%" , d
->percent
);
1485 d
->formatted
= TAKE_PTR(p
);
1489 case TABLE_IFINDEX
: {
1490 _cleanup_free_
char *p
= NULL
;
1491 char name
[IF_NAMESIZE
+ 1];
1493 if (format_ifname(d
->ifindex
, name
)) {
1498 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1502 d
->formatted
= TAKE_PTR(p
);
1507 case TABLE_IN6_ADDR
: {
1508 _cleanup_free_
char *p
= NULL
;
1510 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1511 &d
->address
, &p
) < 0)
1514 d
->formatted
= TAKE_PTR(p
);
1521 p
= new(char, SD_ID128_STRING_MAX
);
1525 d
->formatted
= sd_id128_to_string(d
->id128
, p
);
1532 p
= new(char, ID128_UUID_STRING_MAX
);
1536 d
->formatted
= id128_to_uuid_string(d
->id128
, p
);
1541 assert_not_reached("Unexpected type?");
1544 return d
->formatted
;
1547 static int console_width_height(
1550 size_t *ret_height
) {
1552 size_t max_width
= 0, height
= 0;
1557 /* Determine the width and height in console character cells the specified string needs. */
1562 p
= strchr(s
, '\n');
1564 _cleanup_free_
char *c
= NULL
;
1566 c
= strndup(s
, p
- s
);
1570 k
= utf8_console_width(c
);
1573 k
= utf8_console_width(s
);
1576 if (k
== (size_t) -1)
1582 } while (!isempty(s
));
1585 *ret_width
= max_width
;
1588 *ret_height
= height
;
1593 static int table_data_requested_width_height(
1597 size_t *ret_height
) {
1599 _cleanup_free_
char *truncated
= NULL
;
1600 bool truncation_applied
= false;
1601 size_t width
, height
;
1605 t
= table_data_format(table
, d
);
1609 if (table
->cell_height_max
!= (size_t) -1) {
1610 r
= string_truncate_lines(t
, table
->cell_height_max
, &truncated
);
1614 truncation_applied
= true;
1619 r
= console_width_height(t
, &width
, &height
);
1623 if (d
->maximum_width
!= (size_t) -1 && width
> d
->maximum_width
)
1624 width
= d
->maximum_width
;
1626 if (width
< d
->minimum_width
)
1627 width
= d
->minimum_width
;
1632 *ret_height
= height
;
1634 return truncation_applied
;
1637 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1638 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1639 _cleanup_free_
char *clickable
= NULL
;
1645 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1648 assert(percent
<= 100);
1650 old_length
= strlen(str
);
1653 r
= terminal_urlify(url
, str
, &clickable
);
1657 clickable_length
= strlen(clickable
);
1659 clickable_length
= old_length
;
1661 /* Determine current width on screen */
1663 while (p
< str
+ old_length
) {
1666 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1667 p
++, w
++; /* count invalid chars as 1 */
1671 p
= utf8_next_char(p
);
1672 w
+= unichar_iswide(c
) ? 2 : 1;
1675 /* Already wider than the target, if so, don't do anything */
1676 if (w
>= new_length
)
1677 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1679 /* How much spaces shall we add? An how much on the left side? */
1680 space
= new_length
- w
;
1681 lspace
= space
* percent
/ 100U;
1683 ret
= new(char, space
+ clickable_length
+ 1);
1687 for (i
= 0; i
< lspace
; i
++)
1689 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1690 for (i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1693 ret
[space
+ clickable_length
] = 0;
1697 static const char* table_data_color(TableData
*d
) {
1703 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1704 if (d
->type
== TABLE_EMPTY
)
1710 int table_print(Table
*t
, FILE *f
) {
1711 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1712 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1714 _cleanup_free_
size_t *sorted
= NULL
;
1715 uint64_t *column_weight
, weight_sum
;
1723 /* Ensure we have no incomplete rows */
1724 assert(t
->n_cells
% t
->n_columns
== 0);
1726 n_rows
= t
->n_cells
/ t
->n_columns
;
1727 assert(n_rows
> 0); /* at least the header row must be complete */
1730 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1732 sorted
= new(size_t, n_rows
);
1736 for (i
= 0; i
< n_rows
; i
++)
1737 sorted
[i
] = i
* t
->n_columns
;
1739 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1743 display_columns
= t
->n_display_map
;
1745 display_columns
= t
->n_columns
;
1747 assert(display_columns
> 0);
1749 minimum_width
= newa(size_t, display_columns
);
1750 maximum_width
= newa(size_t, display_columns
);
1751 requested_width
= newa(size_t, display_columns
);
1752 width
= newa(size_t, display_columns
);
1753 column_weight
= newa0(uint64_t, display_columns
);
1755 for (j
= 0; j
< display_columns
; j
++) {
1756 minimum_width
[j
] = 1;
1757 maximum_width
[j
] = (size_t) -1;
1758 requested_width
[j
] = (size_t) -1;
1761 /* First pass: determine column sizes */
1762 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1765 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1766 * hence we don't care for sorted[] during the first pass. */
1767 row
= t
->data
+ i
* t
->n_columns
;
1769 for (j
= 0; j
< display_columns
; j
++) {
1771 size_t req_width
, req_height
;
1773 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1775 r
= table_data_requested_width_height(t
, d
, &req_width
, &req_height
);
1778 if (r
> 0) { /* Truncated because too many lines? */
1779 _cleanup_free_
char *last
= NULL
;
1782 /* If we are going to show only the first few lines of a cell that has
1783 * multiple make sure that we have enough space horizontally to show an
1784 * ellipsis. Hence, let's figure out the last line, and account for its
1785 * length plus ellipsis. */
1787 field
= table_data_format(t
, d
);
1791 assert_se(t
->cell_height_max
> 0);
1792 r
= string_extract_line(field
, t
->cell_height_max
-1, &last
);
1796 req_width
= MAX(req_width
,
1797 utf8_console_width(last
) +
1798 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS
)));
1801 /* Determine the biggest width that any cell in this column would like to have */
1802 if (requested_width
[j
] == (size_t) -1 ||
1803 requested_width
[j
] < req_width
)
1804 requested_width
[j
] = req_width
;
1806 /* Determine the minimum width any cell in this column needs */
1807 if (minimum_width
[j
] < d
->minimum_width
)
1808 minimum_width
[j
] = d
->minimum_width
;
1810 /* Determine the maximum width any cell in this column needs */
1811 if (d
->maximum_width
!= (size_t) -1 &&
1812 (maximum_width
[j
] == (size_t) -1 ||
1813 maximum_width
[j
] > d
->maximum_width
))
1814 maximum_width
[j
] = d
->maximum_width
;
1816 /* Determine the full columns weight */
1817 column_weight
[j
] += d
->weight
;
1821 /* One space between each column */
1822 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1824 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1826 for (j
= 0; j
< display_columns
; j
++) {
1827 weight_sum
+= column_weight
[j
];
1829 table_minimum_width
+= minimum_width
[j
];
1831 if (maximum_width
[j
] == (size_t) -1)
1832 table_maximum_width
= (size_t) -1;
1834 table_maximum_width
+= maximum_width
[j
];
1836 table_requested_width
+= requested_width
[j
];
1839 /* Calculate effective table width */
1840 if (t
->width
!= 0 && t
->width
!= (size_t) -1)
1841 table_effective_width
= t
->width
;
1842 else if (t
->width
== 0 || pager_have() || !isatty(STDOUT_FILENO
))
1843 table_effective_width
= table_requested_width
;
1845 table_effective_width
= MIN(table_requested_width
, columns());
1847 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1848 table_effective_width
= table_maximum_width
;
1850 if (table_effective_width
< table_minimum_width
)
1851 table_effective_width
= table_minimum_width
;
1853 if (table_effective_width
>= table_requested_width
) {
1856 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1857 * each column with what it asked for and the distribute the rest. */
1859 extra
= table_effective_width
- table_requested_width
;
1861 for (j
= 0; j
< display_columns
; j
++) {
1864 if (weight_sum
== 0)
1865 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1867 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1869 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1870 width
[j
] = maximum_width
[j
];
1872 if (width
[j
] < minimum_width
[j
])
1873 width
[j
] = minimum_width
[j
];
1875 assert(width
[j
] >= requested_width
[j
]);
1876 delta
= width
[j
] - requested_width
[j
];
1878 /* Subtract what we just added from the rest */
1884 assert(weight_sum
>= column_weight
[j
]);
1885 weight_sum
-= column_weight
[j
];
1889 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1890 * with the minimum they need, and then distribute anything left. */
1891 bool finalize
= false;
1894 extra
= table_effective_width
- table_minimum_width
;
1896 for (j
= 0; j
< display_columns
; j
++)
1897 width
[j
] = (size_t) -1;
1900 bool restart
= false;
1902 for (j
= 0; j
< display_columns
; j
++) {
1905 /* Did this column already get something assigned? If so, let's skip to the next */
1906 if (width
[j
] != (size_t) -1)
1909 if (weight_sum
== 0)
1910 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
1912 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1914 if (w
>= requested_width
[j
]) {
1915 /* Never give more than requested. If we hit a column like this, there's more
1916 * space to allocate to other columns which means we need to restart the
1917 * iteration. However, if we hit a column like this, let's assign it the space
1918 * it wanted for good early.*/
1920 w
= requested_width
[j
];
1923 } else if (!finalize
)
1928 assert(w
>= minimum_width
[j
]);
1929 delta
= w
- minimum_width
[j
];
1931 assert(delta
<= extra
);
1934 assert(weight_sum
>= column_weight
[j
]);
1935 weight_sum
-= column_weight
[j
];
1937 if (restart
&& !finalize
)
1949 /* Second pass: show output */
1950 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1951 size_t n_subline
= 0;
1956 row
= t
->data
+ sorted
[i
];
1958 row
= t
->data
+ i
* t
->n_columns
;
1961 more_sublines
= false;
1963 for (j
= 0; j
< display_columns
; j
++) {
1964 _cleanup_free_
char *buffer
= NULL
, *extracted
= NULL
;
1965 bool lines_truncated
= false;
1970 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1972 field
= table_data_format(t
, d
);
1976 r
= string_extract_line(field
, n_subline
, &extracted
);
1980 /* There are more lines to come */
1981 if ((t
->cell_height_max
== (size_t) -1 || n_subline
+ 1 < t
->cell_height_max
))
1982 more_sublines
= true; /* There are more lines to come */
1984 lines_truncated
= true;
1989 l
= utf8_console_width(field
);
1991 /* Field is wider than allocated space. Let's ellipsize */
1993 buffer
= ellipsize(field
, width
[j
], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
1994 lines_truncated
? 100 : d
->ellipsize_percent
);
2000 if (lines_truncated
) {
2001 _cleanup_free_
char *padded
= NULL
;
2003 /* We truncated more lines of this cell, let's add an
2004 * ellipsis. We first append it, but thta might make our
2005 * string grow above what we have space for, hence ellipsize
2006 * right after. This will truncate the ellipsis and add a new
2009 padded
= strjoin(field
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
2013 buffer
= ellipsize(padded
, width
[j
], 100);
2018 l
= utf8_console_width(field
);
2022 _cleanup_free_
char *aligned
= NULL
;
2023 /* Field is shorter than allocated space. Let's align with spaces */
2025 aligned
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
2029 free_and_replace(buffer
, aligned
);
2034 if (l
>= width
[j
] && d
->url
) {
2035 _cleanup_free_
char *clickable
= NULL
;
2037 r
= terminal_urlify(d
->url
, field
, &clickable
);
2041 free_and_replace(buffer
, clickable
);
2045 if (row
== t
->data
) /* underline header line fully, including the column separator */
2046 fputs(ansi_underline(), f
);
2049 fputc(' ', f
); /* column separator */
2051 if (table_data_color(d
) && colors_enabled()) {
2052 if (row
== t
->data
) /* first undo header underliner */
2053 fputs(ANSI_NORMAL
, f
);
2055 fputs(table_data_color(d
), f
);
2060 if (colors_enabled() && (table_data_color(d
) || row
== t
->data
))
2061 fputs(ANSI_NORMAL
, f
);
2066 } while (more_sublines
);
2069 return fflush_and_check(f
);
2072 int table_format(Table
*t
, char **ret
) {
2073 _cleanup_fclose_
FILE *f
= NULL
;
2078 f
= open_memstream_unlocked(&buf
, &sz
);
2082 r
= table_print(t
, f
);
2093 size_t table_get_rows(Table
*t
) {
2097 assert(t
->n_columns
> 0);
2098 return t
->n_cells
/ t
->n_columns
;
2101 size_t table_get_columns(Table
*t
) {
2105 assert(t
->n_columns
> 0);
2106 return t
->n_columns
;
2109 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
2111 assert(column
< t
->n_columns
);
2113 if (!t
->reverse_map
) {
2117 t
->reverse_map
= new0(bool, t
->n_columns
);
2118 if (!t
->reverse_map
)
2122 t
->reverse_map
[column
] = b
;
2126 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
2131 if (column
>= t
->n_columns
)
2134 i
= row
* t
->n_columns
+ column
;
2135 if (i
>= t
->n_cells
)
2138 return TABLE_INDEX_TO_CELL(i
);
2141 const void *table_get(Table
*t
, TableCell
*cell
) {
2146 d
= table_get_data(t
, cell
);
2153 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
2156 cell
= table_get_cell(t
, row
, column
);
2160 return table_get(t
, cell
);
2163 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
2168 return json_variant_new_null(ret
);
2172 return json_variant_new_string(ret
, d
->string
);
2175 return json_variant_new_array_strv(ret
, d
->strv
);
2178 return json_variant_new_boolean(ret
, d
->boolean
);
2180 case TABLE_TIMESTAMP
:
2181 case TABLE_TIMESTAMP_UTC
:
2182 case TABLE_TIMESTAMP_RELATIVE
:
2183 if (d
->timestamp
== USEC_INFINITY
)
2184 return json_variant_new_null(ret
);
2186 return json_variant_new_unsigned(ret
, d
->timestamp
);
2188 case TABLE_TIMESPAN
:
2189 case TABLE_TIMESPAN_MSEC
:
2190 if (d
->timespan
== USEC_INFINITY
)
2191 return json_variant_new_null(ret
);
2193 return json_variant_new_unsigned(ret
, d
->timespan
);
2197 if (d
->size
== (size_t) -1)
2198 return json_variant_new_null(ret
);
2200 return json_variant_new_unsigned(ret
, d
->size
);
2203 return json_variant_new_integer(ret
, d
->int_val
);
2206 return json_variant_new_integer(ret
, d
->int8
);
2209 return json_variant_new_integer(ret
, d
->int16
);
2212 return json_variant_new_integer(ret
, d
->int32
);
2215 return json_variant_new_integer(ret
, d
->int64
);
2218 return json_variant_new_unsigned(ret
, d
->uint_val
);
2221 return json_variant_new_unsigned(ret
, d
->uint8
);
2224 return json_variant_new_unsigned(ret
, d
->uint16
);
2227 return json_variant_new_unsigned(ret
, d
->uint32
);
2230 return json_variant_new_unsigned(ret
, d
->uint64
);
2233 return json_variant_new_integer(ret
, d
->percent
);
2236 return json_variant_new_integer(ret
, d
->ifindex
);
2239 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
2241 case TABLE_IN6_ADDR
:
2242 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
2245 char buf
[SD_ID128_STRING_MAX
];
2246 return json_variant_new_string(ret
, sd_id128_to_string(d
->id128
, buf
));
2250 char buf
[ID128_UUID_STRING_MAX
];
2251 return json_variant_new_string(ret
, id128_to_uuid_string(d
->id128
, buf
));
2259 int table_to_json(Table
*t
, JsonVariant
**ret
) {
2260 JsonVariant
**rows
= NULL
, **elements
= NULL
;
2261 _cleanup_free_
size_t *sorted
= NULL
;
2262 size_t n_rows
, i
, j
, display_columns
;
2267 /* Ensure we have no incomplete rows */
2268 assert(t
->n_cells
% t
->n_columns
== 0);
2270 n_rows
= t
->n_cells
/ t
->n_columns
;
2271 assert(n_rows
> 0); /* at least the header row must be complete */
2274 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2276 sorted
= new(size_t, n_rows
);
2282 for (i
= 0; i
< n_rows
; i
++)
2283 sorted
[i
] = i
* t
->n_columns
;
2285 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2289 display_columns
= t
->n_display_map
;
2291 display_columns
= t
->n_columns
;
2292 assert(display_columns
> 0);
2294 elements
= new0(JsonVariant
*, display_columns
* 2);
2300 for (j
= 0; j
< display_columns
; j
++) {
2303 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2305 r
= table_data_to_json(d
, elements
+ j
*2);
2310 rows
= new0(JsonVariant
*, n_rows
-1);
2316 for (i
= 1; i
< n_rows
; i
++) {
2320 row
= t
->data
+ sorted
[i
];
2322 row
= t
->data
+ i
* t
->n_columns
;
2324 for (j
= 0; j
< display_columns
; j
++) {
2328 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2331 elements
[k
] = json_variant_unref(elements
[k
]);
2333 r
= table_data_to_json(d
, elements
+ k
);
2338 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2343 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2347 json_variant_unref_many(rows
, n_rows
-1);
2352 json_variant_unref_many(elements
, display_columns
*2);
2359 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2360 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2368 r
= table_to_json(t
, &v
);
2372 json_variant_dump(v
, flags
, f
, NULL
);
2374 return fflush_and_check(f
);