]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
table: add more basic types
[thirdparty/systemd.git] / src / shared / format-table.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <ctype.h>
4
5 #include "alloc-util.h"
6 #include "fd-util.h"
7 #include "fileio.h"
8 #include "format-table.h"
9 #include "gunicode.h"
10 #include "memory-util.h"
11 #include "pager.h"
12 #include "parse-util.h"
13 #include "pretty-print.h"
14 #include "sort-util.h"
15 #include "string-util.h"
16 #include "terminal-util.h"
17 #include "time-util.h"
18 #include "utf8.h"
19 #include "util.h"
20
21 #define DEFAULT_WEIGHT 100
22
23 /*
24 A few notes on implementation details:
25
26 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
27 table. It can be easily converted to an index number and back.
28
29 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
30 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
31 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
32 outside only sees Table and TableCell.
33
34 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
35 previous one.
36
37 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
38 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
39 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
40 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
41
42 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
43 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
44 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
45 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
46 instead.
47
48 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
49 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
50 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
51 */
52
53 typedef struct TableData {
54 unsigned n_ref;
55 TableDataType type;
56
57 size_t minimum_width; /* minimum width for the column */
58 size_t maximum_width; /* maximum width for the column */
59 unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
60 unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
61 unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
62
63 bool uppercase; /* Uppercase string on display */
64
65 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 */
66 char *url; /* A URL to use for a clickable hyperlink */
67 char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
68
69 union {
70 uint8_t data[0]; /* data is generic array */
71 bool boolean;
72 usec_t timestamp;
73 usec_t timespan;
74 uint64_t size;
75 char string[0];
76 int int_val;
77 int32_t int32;
78 int64_t int64;
79 unsigned uint_val;
80 uint32_t uint32;
81 uint64_t uint64;
82 int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
83 /* … add more here as we start supporting more cell data types … */
84 };
85 } TableData;
86
87 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
88 size_t i;
89
90 assert(cell);
91
92 i = PTR_TO_SIZE(cell);
93 assert(i > 0);
94
95 return i-1;
96 }
97
98 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
99 assert(index != (size_t) -1);
100 return SIZE_TO_PTR(index + 1);
101 }
102
103 struct Table {
104 size_t n_columns;
105 size_t n_cells;
106
107 bool header; /* Whether to show the header row? */
108 size_t width; /* If != (size_t) -1 the width to format this table in */
109
110 TableData **data;
111 size_t n_allocated;
112
113 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 */
114 size_t n_display_map;
115
116 size_t *sort_map; /* The columns to order rows by, in order of preference. */
117 size_t n_sort_map;
118
119 bool *reverse_map;
120 };
121
122 Table *table_new_raw(size_t n_columns) {
123 _cleanup_(table_unrefp) Table *t = NULL;
124
125 assert(n_columns > 0);
126
127 t = new(Table, 1);
128 if (!t)
129 return NULL;
130
131 *t = (struct Table) {
132 .n_columns = n_columns,
133 .header = true,
134 .width = (size_t) -1,
135 };
136
137 return TAKE_PTR(t);
138 }
139
140 Table *table_new_internal(const char *first_header, ...) {
141 _cleanup_(table_unrefp) Table *t = NULL;
142 size_t n_columns = 1;
143 const char *h;
144 va_list ap;
145 int r;
146
147 assert(first_header);
148
149 va_start(ap, first_header);
150 for (;;) {
151 h = va_arg(ap, const char*);
152 if (!h)
153 break;
154
155 n_columns++;
156 }
157 va_end(ap);
158
159 t = table_new_raw(n_columns);
160 if (!t)
161 return NULL;
162
163 va_start(ap, first_header);
164 for (h = first_header; h; h = va_arg(ap, const char*)) {
165 TableCell *cell;
166
167 r = table_add_cell(t, &cell, TABLE_STRING, h);
168 if (r < 0) {
169 va_end(ap);
170 return NULL;
171 }
172
173 /* Make the table header uppercase */
174 r = table_set_uppercase(t, cell, true);
175 if (r < 0) {
176 va_end(ap);
177 return NULL;
178 }
179 }
180 va_end(ap);
181
182 assert(t->n_columns == t->n_cells);
183 return TAKE_PTR(t);
184 }
185
186 static TableData *table_data_free(TableData *d) {
187 assert(d);
188
189 free(d->formatted);
190 free(d->url);
191
192 return mfree(d);
193 }
194
195 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
196 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
197
198 Table *table_unref(Table *t) {
199 size_t i;
200
201 if (!t)
202 return NULL;
203
204 for (i = 0; i < t->n_cells; i++)
205 table_data_unref(t->data[i]);
206
207 free(t->data);
208 free(t->display_map);
209 free(t->sort_map);
210 free(t->reverse_map);
211
212 return mfree(t);
213 }
214
215 static size_t table_data_size(TableDataType type, const void *data) {
216
217 switch (type) {
218
219 case TABLE_EMPTY:
220 return 0;
221
222 case TABLE_STRING:
223 return strlen(data) + 1;
224
225 case TABLE_BOOLEAN:
226 return sizeof(bool);
227
228 case TABLE_TIMESTAMP:
229 case TABLE_TIMESPAN:
230 return sizeof(usec_t);
231
232 case TABLE_SIZE:
233 case TABLE_INT64:
234 case TABLE_UINT64:
235 return sizeof(uint64_t);
236
237 case TABLE_INT32:
238 case TABLE_UINT32:
239 return sizeof(uint32_t);
240
241 case TABLE_INT:
242 case TABLE_UINT:
243 case TABLE_PERCENT:
244 return sizeof(int);
245
246 default:
247 assert_not_reached("Uh? Unexpected cell type");
248 }
249 }
250
251 static bool table_data_matches(
252 TableData *d,
253 TableDataType type,
254 const void *data,
255 size_t minimum_width,
256 size_t maximum_width,
257 unsigned weight,
258 unsigned align_percent,
259 unsigned ellipsize_percent) {
260
261 size_t k, l;
262 assert(d);
263
264 if (d->type != type)
265 return false;
266
267 if (d->minimum_width != minimum_width)
268 return false;
269
270 if (d->maximum_width != maximum_width)
271 return false;
272
273 if (d->weight != weight)
274 return false;
275
276 if (d->align_percent != align_percent)
277 return false;
278
279 if (d->ellipsize_percent != ellipsize_percent)
280 return false;
281
282 /* If a color/url/uppercase flag is set, refuse to merge */
283 if (d->color)
284 return false;
285 if (d->url)
286 return false;
287 if (d->uppercase)
288 return false;
289
290 k = table_data_size(type, data);
291 l = table_data_size(d->type, d->data);
292
293 if (k != l)
294 return false;
295
296 return memcmp_safe(data, d->data, l) == 0;
297 }
298
299 static TableData *table_data_new(
300 TableDataType type,
301 const void *data,
302 size_t minimum_width,
303 size_t maximum_width,
304 unsigned weight,
305 unsigned align_percent,
306 unsigned ellipsize_percent) {
307
308 size_t data_size;
309 TableData *d;
310
311 data_size = table_data_size(type, data);
312
313 d = malloc0(offsetof(TableData, data) + data_size);
314 if (!d)
315 return NULL;
316
317 d->n_ref = 1;
318 d->type = type;
319 d->minimum_width = minimum_width;
320 d->maximum_width = maximum_width;
321 d->weight = weight;
322 d->align_percent = align_percent;
323 d->ellipsize_percent = ellipsize_percent;
324 memcpy_safe(d->data, data, data_size);
325
326 return d;
327 }
328
329 int table_add_cell_full(
330 Table *t,
331 TableCell **ret_cell,
332 TableDataType type,
333 const void *data,
334 size_t minimum_width,
335 size_t maximum_width,
336 unsigned weight,
337 unsigned align_percent,
338 unsigned ellipsize_percent) {
339
340 _cleanup_(table_data_unrefp) TableData *d = NULL;
341 TableData *p;
342
343 assert(t);
344 assert(type >= 0);
345 assert(type < _TABLE_DATA_TYPE_MAX);
346
347 /* Determine the cell adjacent to the current one, but one row up */
348 if (t->n_cells >= t->n_columns)
349 assert_se(p = t->data[t->n_cells - t->n_columns]);
350 else
351 p = NULL;
352
353 /* If formatting parameters are left unspecified, copy from the previous row */
354 if (minimum_width == (size_t) -1)
355 minimum_width = p ? p->minimum_width : 1;
356
357 if (weight == (unsigned) -1)
358 weight = p ? p->weight : DEFAULT_WEIGHT;
359
360 if (align_percent == (unsigned) -1)
361 align_percent = p ? p->align_percent : 0;
362
363 if (ellipsize_percent == (unsigned) -1)
364 ellipsize_percent = p ? p->ellipsize_percent : 100;
365
366 assert(align_percent <= 100);
367 assert(ellipsize_percent <= 100);
368
369 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
370 * formatting. Let's see if we can reuse the cell data and ref it once more. */
371
372 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
373 d = table_data_ref(p);
374 else {
375 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
376 if (!d)
377 return -ENOMEM;
378 }
379
380 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
381 return -ENOMEM;
382
383 if (ret_cell)
384 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
385
386 t->data[t->n_cells++] = TAKE_PTR(d);
387
388 return 0;
389 }
390
391 int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
392 _cleanup_free_ char *buffer = NULL;
393 va_list ap;
394 int r;
395
396 va_start(ap, format);
397 r = vasprintf(&buffer, format, ap);
398 va_end(ap);
399 if (r < 0)
400 return -ENOMEM;
401
402 return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
403 }
404
405 int table_dup_cell(Table *t, TableCell *cell) {
406 size_t i;
407
408 assert(t);
409
410 /* Add the data of the specified cell a second time as a new cell to the end. */
411
412 i = TABLE_CELL_TO_INDEX(cell);
413 if (i >= t->n_cells)
414 return -ENXIO;
415
416 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
417 return -ENOMEM;
418
419 t->data[t->n_cells++] = table_data_ref(t->data[i]);
420 return 0;
421 }
422
423 static int table_dedup_cell(Table *t, TableCell *cell) {
424 _cleanup_free_ char *curl = NULL;
425 TableData *nd, *od;
426 size_t i;
427
428 assert(t);
429
430 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
431 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
432
433 i = TABLE_CELL_TO_INDEX(cell);
434 if (i >= t->n_cells)
435 return -ENXIO;
436
437 assert_se(od = t->data[i]);
438 if (od->n_ref == 1)
439 return 0;
440
441 assert(od->n_ref > 1);
442
443 if (od->url) {
444 curl = strdup(od->url);
445 if (!curl)
446 return -ENOMEM;
447 }
448
449 nd = table_data_new(
450 od->type,
451 od->data,
452 od->minimum_width,
453 od->maximum_width,
454 od->weight,
455 od->align_percent,
456 od->ellipsize_percent);
457 if (!nd)
458 return -ENOMEM;
459
460 nd->color = od->color;
461 nd->url = TAKE_PTR(curl);
462 nd->uppercase = od->uppercase;
463
464 table_data_unref(od);
465 t->data[i] = nd;
466
467 assert(nd->n_ref == 1);
468
469 return 1;
470 }
471
472 static TableData *table_get_data(Table *t, TableCell *cell) {
473 size_t i;
474
475 assert(t);
476 assert(cell);
477
478 /* Get the data object of the specified cell, or NULL if it doesn't exist */
479
480 i = TABLE_CELL_TO_INDEX(cell);
481 if (i >= t->n_cells)
482 return NULL;
483
484 assert(t->data[i]);
485 assert(t->data[i]->n_ref > 0);
486
487 return t->data[i];
488 }
489
490 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
491 int r;
492
493 assert(t);
494 assert(cell);
495
496 if (minimum_width == (size_t) -1)
497 minimum_width = 1;
498
499 r = table_dedup_cell(t, cell);
500 if (r < 0)
501 return r;
502
503 table_get_data(t, cell)->minimum_width = minimum_width;
504 return 0;
505 }
506
507 int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
508 int r;
509
510 assert(t);
511 assert(cell);
512
513 r = table_dedup_cell(t, cell);
514 if (r < 0)
515 return r;
516
517 table_get_data(t, cell)->maximum_width = maximum_width;
518 return 0;
519 }
520
521 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
522 int r;
523
524 assert(t);
525 assert(cell);
526
527 if (weight == (unsigned) -1)
528 weight = DEFAULT_WEIGHT;
529
530 r = table_dedup_cell(t, cell);
531 if (r < 0)
532 return r;
533
534 table_get_data(t, cell)->weight = weight;
535 return 0;
536 }
537
538 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
539 int r;
540
541 assert(t);
542 assert(cell);
543
544 if (percent == (unsigned) -1)
545 percent = 0;
546
547 assert(percent <= 100);
548
549 r = table_dedup_cell(t, cell);
550 if (r < 0)
551 return r;
552
553 table_get_data(t, cell)->align_percent = percent;
554 return 0;
555 }
556
557 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
558 int r;
559
560 assert(t);
561 assert(cell);
562
563 if (percent == (unsigned) -1)
564 percent = 100;
565
566 assert(percent <= 100);
567
568 r = table_dedup_cell(t, cell);
569 if (r < 0)
570 return r;
571
572 table_get_data(t, cell)->ellipsize_percent = percent;
573 return 0;
574 }
575
576 int table_set_color(Table *t, TableCell *cell, const char *color) {
577 int r;
578
579 assert(t);
580 assert(cell);
581
582 r = table_dedup_cell(t, cell);
583 if (r < 0)
584 return r;
585
586 table_get_data(t, cell)->color = empty_to_null(color);
587 return 0;
588 }
589
590 int table_set_url(Table *t, TableCell *cell, const char *url) {
591 _cleanup_free_ char *copy = NULL;
592 int r;
593
594 assert(t);
595 assert(cell);
596
597 if (url) {
598 copy = strdup(url);
599 if (!copy)
600 return -ENOMEM;
601 }
602
603 r = table_dedup_cell(t, cell);
604 if (r < 0)
605 return r;
606
607 return free_and_replace(table_get_data(t, cell)->url, copy);
608 }
609
610 int table_set_uppercase(Table *t, TableCell *cell, bool b) {
611 TableData *d;
612 int r;
613
614 assert(t);
615 assert(cell);
616
617 r = table_dedup_cell(t, cell);
618 if (r < 0)
619 return r;
620
621 assert_se(d = table_get_data(t, cell));
622
623 if (d->uppercase == b)
624 return 0;
625
626 d->formatted = mfree(d->formatted);
627 d->uppercase = b;
628 return 1;
629 }
630
631 int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
632 _cleanup_free_ char *curl = NULL;
633 TableData *nd, *od;
634 size_t i;
635
636 assert(t);
637 assert(cell);
638
639 i = TABLE_CELL_TO_INDEX(cell);
640 if (i >= t->n_cells)
641 return -ENXIO;
642
643 assert_se(od = t->data[i]);
644
645 if (od->url) {
646 curl = strdup(od->url);
647 if (!curl)
648 return -ENOMEM;
649 }
650
651 nd = table_data_new(
652 type,
653 data,
654 od->minimum_width,
655 od->maximum_width,
656 od->weight,
657 od->align_percent,
658 od->ellipsize_percent);
659 if (!nd)
660 return -ENOMEM;
661
662 nd->color = od->color;
663 nd->url = TAKE_PTR(curl);
664 nd->uppercase = od->uppercase;
665
666 table_data_unref(od);
667 t->data[i] = nd;
668
669 return 0;
670 }
671
672 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
673 TableDataType type;
674 va_list ap;
675 int r;
676
677 assert(t);
678 assert(first_type >= 0);
679 assert(first_type < _TABLE_DATA_TYPE_MAX);
680
681 type = first_type;
682
683 va_start(ap, first_type);
684 for (;;) {
685 const void *data;
686 union {
687 uint64_t size;
688 usec_t usec;
689 int int_val;
690 int32_t int32;
691 int64_t int64;
692 unsigned uint_val;
693 uint32_t uint32;
694 uint64_t uint64;
695 int percent;
696 bool b;
697 } buffer;
698
699 switch (type) {
700
701 case TABLE_EMPTY:
702 data = NULL;
703 break;
704
705 case TABLE_STRING:
706 data = va_arg(ap, const char *);
707 break;
708
709 case TABLE_BOOLEAN:
710 buffer.b = va_arg(ap, int);
711 data = &buffer.b;
712 break;
713
714 case TABLE_TIMESTAMP:
715 case TABLE_TIMESPAN:
716 buffer.usec = va_arg(ap, usec_t);
717 data = &buffer.usec;
718 break;
719
720 case TABLE_SIZE:
721 buffer.size = va_arg(ap, uint64_t);
722 data = &buffer.size;
723 break;
724
725 case TABLE_INT:
726 buffer.int_val = va_arg(ap, int);
727 data = &buffer.int_val;
728 break;
729
730 case TABLE_INT32:
731 buffer.int32 = va_arg(ap, int32_t);
732 data = &buffer.int32;
733 break;
734
735 case TABLE_INT64:
736 buffer.int64 = va_arg(ap, int64_t);
737 data = &buffer.int64;
738 break;
739
740 case TABLE_UINT:
741 buffer.uint_val = va_arg(ap, unsigned);
742 data = &buffer.uint_val;
743 break;
744
745 case TABLE_UINT32:
746 buffer.uint32 = va_arg(ap, uint32_t);
747 data = &buffer.uint32;
748 break;
749
750 case TABLE_UINT64:
751 buffer.uint64 = va_arg(ap, uint64_t);
752 data = &buffer.uint64;
753 break;
754
755 case TABLE_PERCENT:
756 buffer.percent = va_arg(ap, int);
757 data = &buffer.percent;
758 break;
759
760 case _TABLE_DATA_TYPE_MAX:
761 /* Used as end marker */
762 va_end(ap);
763 return 0;
764
765 default:
766 assert_not_reached("Uh? Unexpected data type.");
767 }
768
769 r = table_add_cell(t, NULL, type, data);
770 if (r < 0) {
771 va_end(ap);
772 return r;
773 }
774
775 type = va_arg(ap, TableDataType);
776 }
777 }
778
779 void table_set_header(Table *t, bool b) {
780 assert(t);
781
782 t->header = b;
783 }
784
785 void table_set_width(Table *t, size_t width) {
786 assert(t);
787
788 t->width = width;
789 }
790
791 int table_set_display(Table *t, size_t first_column, ...) {
792 size_t allocated, column;
793 va_list ap;
794
795 assert(t);
796
797 allocated = t->n_display_map;
798 column = first_column;
799
800 va_start(ap, first_column);
801 for (;;) {
802 assert(column < t->n_columns);
803
804 if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
805 va_end(ap);
806 return -ENOMEM;
807 }
808
809 t->display_map[t->n_display_map++] = column;
810
811 column = va_arg(ap, size_t);
812 if (column == (size_t) -1)
813 break;
814
815 }
816 va_end(ap);
817
818 return 0;
819 }
820
821 int table_set_sort(Table *t, size_t first_column, ...) {
822 size_t allocated, column;
823 va_list ap;
824
825 assert(t);
826
827 allocated = t->n_sort_map;
828 column = first_column;
829
830 va_start(ap, first_column);
831 for (;;) {
832 assert(column < t->n_columns);
833
834 if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
835 va_end(ap);
836 return -ENOMEM;
837 }
838
839 t->sort_map[t->n_sort_map++] = column;
840
841 column = va_arg(ap, size_t);
842 if (column == (size_t) -1)
843 break;
844 }
845 va_end(ap);
846
847 return 0;
848 }
849
850 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
851 assert(a);
852 assert(b);
853
854 if (a->type == b->type) {
855
856 /* We only define ordering for cells of the same data type. If cells with different data types are
857 * compared we follow the order the cells were originally added in */
858
859 switch (a->type) {
860
861 case TABLE_STRING:
862 return strcmp(a->string, b->string);
863
864 case TABLE_BOOLEAN:
865 if (!a->boolean && b->boolean)
866 return -1;
867 if (a->boolean && !b->boolean)
868 return 1;
869 return 0;
870
871 case TABLE_TIMESTAMP:
872 return CMP(a->timestamp, b->timestamp);
873
874 case TABLE_TIMESPAN:
875 return CMP(a->timespan, b->timespan);
876
877 case TABLE_SIZE:
878 return CMP(a->size, b->size);
879
880 case TABLE_INT:
881 return CMP(a->int_val, b->int_val);
882
883 case TABLE_INT32:
884 return CMP(a->int32, b->int32);
885
886 case TABLE_INT64:
887 return CMP(a->int64, b->int64);
888
889 case TABLE_UINT:
890 return CMP(a->uint_val, b->uint_val);
891
892 case TABLE_UINT32:
893 return CMP(a->uint32, b->uint32);
894
895 case TABLE_UINT64:
896 return CMP(a->uint64, b->uint64);
897
898 case TABLE_PERCENT:
899 return CMP(a->percent, b->percent);
900
901 default:
902 ;
903 }
904 }
905
906 /* Generic fallback using the original order in which the cells where added. */
907 return CMP(index_a, index_b);
908 }
909
910 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
911 size_t i;
912 int r;
913
914 assert(t);
915 assert(t->sort_map);
916
917 /* Make sure the header stays at the beginning */
918 if (*a < t->n_columns && *b < t->n_columns)
919 return 0;
920 if (*a < t->n_columns)
921 return -1;
922 if (*b < t->n_columns)
923 return 1;
924
925 /* Order other lines by the sorting map */
926 for (i = 0; i < t->n_sort_map; i++) {
927 TableData *d, *dd;
928
929 d = t->data[*a + t->sort_map[i]];
930 dd = t->data[*b + t->sort_map[i]];
931
932 r = cell_data_compare(d, *a, dd, *b);
933 if (r != 0)
934 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
935 }
936
937 /* Order identical lines by the order there were originally added in */
938 return CMP(*a, *b);
939 }
940
941 static const char *table_data_format(TableData *d) {
942 assert(d);
943
944 if (d->formatted)
945 return d->formatted;
946
947 switch (d->type) {
948 case TABLE_EMPTY:
949 return "";
950
951 case TABLE_STRING:
952 if (d->uppercase) {
953 char *p, *q;
954
955 d->formatted = new(char, strlen(d->string) + 1);
956 if (!d->formatted)
957 return NULL;
958
959 for (p = d->string, q = d->formatted; *p; p++, q++)
960 *q = (char) toupper((unsigned char) *p);
961 *q = 0;
962
963 return d->formatted;
964 }
965
966 return d->string;
967
968 case TABLE_BOOLEAN:
969 return yes_no(d->boolean);
970
971 case TABLE_TIMESTAMP: {
972 _cleanup_free_ char *p;
973
974 p = new(char, FORMAT_TIMESTAMP_MAX);
975 if (!p)
976 return NULL;
977
978 if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
979 return "n/a";
980
981 d->formatted = TAKE_PTR(p);
982 break;
983 }
984
985 case TABLE_TIMESPAN: {
986 _cleanup_free_ char *p;
987
988 p = new(char, FORMAT_TIMESPAN_MAX);
989 if (!p)
990 return NULL;
991
992 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan, 0))
993 return "n/a";
994
995 d->formatted = TAKE_PTR(p);
996 break;
997 }
998
999 case TABLE_SIZE: {
1000 _cleanup_free_ char *p;
1001
1002 p = new(char, FORMAT_BYTES_MAX);
1003 if (!p)
1004 return NULL;
1005
1006 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1007 return "n/a";
1008
1009 d->formatted = TAKE_PTR(p);
1010 break;
1011 }
1012
1013 case TABLE_INT: {
1014 _cleanup_free_ char *p;
1015
1016 p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1017 if (!p)
1018 return NULL;
1019
1020 sprintf(p, "%i", d->int_val);
1021 d->formatted = TAKE_PTR(p);
1022 break;
1023 }
1024
1025 case TABLE_INT32: {
1026 _cleanup_free_ char *p;
1027
1028 p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1029 if (!p)
1030 return NULL;
1031
1032 sprintf(p, "%" PRIi32, d->int32);
1033 d->formatted = TAKE_PTR(p);
1034 break;
1035 }
1036
1037 case TABLE_INT64: {
1038 _cleanup_free_ char *p;
1039
1040 p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1041 if (!p)
1042 return NULL;
1043
1044 sprintf(p, "%" PRIi64, d->int64);
1045 d->formatted = TAKE_PTR(p);
1046 break;
1047 }
1048
1049 case TABLE_UINT: {
1050 _cleanup_free_ char *p;
1051
1052 p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1053 if (!p)
1054 return NULL;
1055
1056 sprintf(p, "%u", d->uint_val);
1057 d->formatted = TAKE_PTR(p);
1058 break;
1059 }
1060
1061 case TABLE_UINT32: {
1062 _cleanup_free_ char *p;
1063
1064 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1065 if (!p)
1066 return NULL;
1067
1068 sprintf(p, "%" PRIu32, d->uint32);
1069 d->formatted = TAKE_PTR(p);
1070 break;
1071 }
1072
1073 case TABLE_UINT64: {
1074 _cleanup_free_ char *p;
1075
1076 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1077 if (!p)
1078 return NULL;
1079
1080 sprintf(p, "%" PRIu64, d->uint64);
1081 d->formatted = TAKE_PTR(p);
1082 break;
1083 }
1084
1085 case TABLE_PERCENT: {
1086 _cleanup_free_ char *p;
1087
1088 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1089 if (!p)
1090 return NULL;
1091
1092 sprintf(p, "%i%%" , d->percent);
1093 d->formatted = TAKE_PTR(p);
1094 break;
1095 }
1096
1097 default:
1098 assert_not_reached("Unexpected type?");
1099 }
1100
1101 return d->formatted;
1102 }
1103
1104 static int table_data_requested_width(TableData *d, size_t *ret) {
1105 const char *t;
1106 size_t l;
1107
1108 t = table_data_format(d);
1109 if (!t)
1110 return -ENOMEM;
1111
1112 l = utf8_console_width(t);
1113 if (l == (size_t) -1)
1114 return -EINVAL;
1115
1116 if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
1117 l = d->maximum_width;
1118
1119 if (l < d->minimum_width)
1120 l = d->minimum_width;
1121
1122 *ret = l;
1123 return 0;
1124 }
1125
1126 static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1127 size_t w = 0, space, lspace, old_length, clickable_length;
1128 _cleanup_free_ char *clickable = NULL;
1129 const char *p;
1130 char *ret;
1131 size_t i;
1132 int r;
1133
1134 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1135
1136 assert(str);
1137 assert(percent <= 100);
1138
1139 old_length = strlen(str);
1140
1141 if (url) {
1142 r = terminal_urlify(url, str, &clickable);
1143 if (r < 0)
1144 return NULL;
1145
1146 clickable_length = strlen(clickable);
1147 } else
1148 clickable_length = old_length;
1149
1150 /* Determine current width on screen */
1151 p = str;
1152 while (p < str + old_length) {
1153 char32_t c;
1154
1155 if (utf8_encoded_to_unichar(p, &c) < 0) {
1156 p++, w++; /* count invalid chars as 1 */
1157 continue;
1158 }
1159
1160 p = utf8_next_char(p);
1161 w += unichar_iswide(c) ? 2 : 1;
1162 }
1163
1164 /* Already wider than the target, if so, don't do anything */
1165 if (w >= new_length)
1166 return clickable ? TAKE_PTR(clickable) : strdup(str);
1167
1168 /* How much spaces shall we add? An how much on the left side? */
1169 space = new_length - w;
1170 lspace = space * percent / 100U;
1171
1172 ret = new(char, space + clickable_length + 1);
1173 if (!ret)
1174 return NULL;
1175
1176 for (i = 0; i < lspace; i++)
1177 ret[i] = ' ';
1178 memcpy(ret + lspace, clickable ?: str, clickable_length);
1179 for (i = lspace + clickable_length; i < space + clickable_length; i++)
1180 ret[i] = ' ';
1181
1182 ret[space + clickable_length] = 0;
1183 return ret;
1184 }
1185
1186 int table_print(Table *t, FILE *f) {
1187 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1188 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1189 *width;
1190 _cleanup_free_ size_t *sorted = NULL;
1191 uint64_t *column_weight, weight_sum;
1192 int r;
1193
1194 assert(t);
1195
1196 if (!f)
1197 f = stdout;
1198
1199 /* Ensure we have no incomplete rows */
1200 assert(t->n_cells % t->n_columns == 0);
1201
1202 n_rows = t->n_cells / t->n_columns;
1203 assert(n_rows > 0); /* at least the header row must be complete */
1204
1205 if (t->sort_map) {
1206 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1207
1208 sorted = new(size_t, n_rows);
1209 if (!sorted)
1210 return -ENOMEM;
1211
1212 for (i = 0; i < n_rows; i++)
1213 sorted[i] = i * t->n_columns;
1214
1215 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1216 }
1217
1218 if (t->display_map)
1219 display_columns = t->n_display_map;
1220 else
1221 display_columns = t->n_columns;
1222
1223 assert(display_columns > 0);
1224
1225 minimum_width = newa(size_t, display_columns);
1226 maximum_width = newa(size_t, display_columns);
1227 requested_width = newa(size_t, display_columns);
1228 width = newa(size_t, display_columns);
1229 column_weight = newa0(uint64_t, display_columns);
1230
1231 for (j = 0; j < display_columns; j++) {
1232 minimum_width[j] = 1;
1233 maximum_width[j] = (size_t) -1;
1234 requested_width[j] = (size_t) -1;
1235 }
1236
1237 /* First pass: determine column sizes */
1238 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1239 TableData **row;
1240
1241 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1242 * hence we don't care for sorted[] during the first pass. */
1243 row = t->data + i * t->n_columns;
1244
1245 for (j = 0; j < display_columns; j++) {
1246 TableData *d;
1247 size_t req;
1248
1249 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1250
1251 r = table_data_requested_width(d, &req);
1252 if (r < 0)
1253 return r;
1254
1255 /* Determine the biggest width that any cell in this column would like to have */
1256 if (requested_width[j] == (size_t) -1 ||
1257 requested_width[j] < req)
1258 requested_width[j] = req;
1259
1260 /* Determine the minimum width any cell in this column needs */
1261 if (minimum_width[j] < d->minimum_width)
1262 minimum_width[j] = d->minimum_width;
1263
1264 /* Determine the maximum width any cell in this column needs */
1265 if (d->maximum_width != (size_t) -1 &&
1266 (maximum_width[j] == (size_t) -1 ||
1267 maximum_width[j] > d->maximum_width))
1268 maximum_width[j] = d->maximum_width;
1269
1270 /* Determine the full columns weight */
1271 column_weight[j] += d->weight;
1272 }
1273 }
1274
1275 /* One space between each column */
1276 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1277
1278 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1279 weight_sum = 0;
1280 for (j = 0; j < display_columns; j++) {
1281 weight_sum += column_weight[j];
1282
1283 table_minimum_width += minimum_width[j];
1284
1285 if (maximum_width[j] == (size_t) -1)
1286 table_maximum_width = (size_t) -1;
1287 else
1288 table_maximum_width += maximum_width[j];
1289
1290 table_requested_width += requested_width[j];
1291 }
1292
1293 /* Calculate effective table width */
1294 if (t->width == (size_t) -1)
1295 table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1296 else
1297 table_effective_width = t->width;
1298
1299 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1300 table_effective_width = table_maximum_width;
1301
1302 if (table_effective_width < table_minimum_width)
1303 table_effective_width = table_minimum_width;
1304
1305 if (table_effective_width >= table_requested_width) {
1306 size_t extra;
1307
1308 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1309 * each column with what it asked for and the distribute the rest. */
1310
1311 extra = table_effective_width - table_requested_width;
1312
1313 for (j = 0; j < display_columns; j++) {
1314 size_t delta;
1315
1316 if (weight_sum == 0)
1317 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1318 else
1319 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1320
1321 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1322 width[j] = maximum_width[j];
1323
1324 if (width[j] < minimum_width[j])
1325 width[j] = minimum_width[j];
1326
1327 assert(width[j] >= requested_width[j]);
1328 delta = width[j] - requested_width[j];
1329
1330 /* Subtract what we just added from the rest */
1331 if (extra > delta)
1332 extra -= delta;
1333 else
1334 extra = 0;
1335
1336 assert(weight_sum >= column_weight[j]);
1337 weight_sum -= column_weight[j];
1338 }
1339
1340 } else {
1341 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1342 * with the minimum they need, and then distribute anything left. */
1343 bool finalize = false;
1344 size_t extra;
1345
1346 extra = table_effective_width - table_minimum_width;
1347
1348 for (j = 0; j < display_columns; j++)
1349 width[j] = (size_t) -1;
1350
1351 for (;;) {
1352 bool restart = false;
1353
1354 for (j = 0; j < display_columns; j++) {
1355 size_t delta, w;
1356
1357 /* Did this column already get something assigned? If so, let's skip to the next */
1358 if (width[j] != (size_t) -1)
1359 continue;
1360
1361 if (weight_sum == 0)
1362 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1363 else
1364 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1365
1366 if (w >= requested_width[j]) {
1367 /* Never give more than requested. If we hit a column like this, there's more
1368 * space to allocate to other columns which means we need to restart the
1369 * iteration. However, if we hit a column like this, let's assign it the space
1370 * it wanted for good early.*/
1371
1372 w = requested_width[j];
1373 restart = true;
1374
1375 } else if (!finalize)
1376 continue;
1377
1378 width[j] = w;
1379
1380 assert(w >= minimum_width[j]);
1381 delta = w - minimum_width[j];
1382
1383 assert(delta <= extra);
1384 extra -= delta;
1385
1386 assert(weight_sum >= column_weight[j]);
1387 weight_sum -= column_weight[j];
1388
1389 if (restart && !finalize)
1390 break;
1391 }
1392
1393 if (finalize)
1394 break;
1395
1396 if (!restart)
1397 finalize = true;
1398 }
1399 }
1400
1401 /* Second pass: show output */
1402 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1403 TableData **row;
1404
1405 if (sorted)
1406 row = t->data + sorted[i];
1407 else
1408 row = t->data + i * t->n_columns;
1409
1410 for (j = 0; j < display_columns; j++) {
1411 _cleanup_free_ char *buffer = NULL;
1412 const char *field;
1413 TableData *d;
1414 size_t l;
1415
1416 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1417
1418 field = table_data_format(d);
1419 if (!field)
1420 return -ENOMEM;
1421
1422 l = utf8_console_width(field);
1423 if (l > width[j]) {
1424 /* Field is wider than allocated space. Let's ellipsize */
1425
1426 buffer = ellipsize(field, width[j], d->ellipsize_percent);
1427 if (!buffer)
1428 return -ENOMEM;
1429
1430 field = buffer;
1431
1432 } else if (l < width[j]) {
1433 /* Field is shorter than allocated space. Let's align with spaces */
1434
1435 buffer = align_string_mem(field, d->url, width[j], d->align_percent);
1436 if (!buffer)
1437 return -ENOMEM;
1438
1439 field = buffer;
1440 }
1441
1442 if (l >= width[j] && d->url) {
1443 _cleanup_free_ char *clickable = NULL;
1444
1445 r = terminal_urlify(d->url, field, &clickable);
1446 if (r < 0)
1447 return r;
1448
1449 free_and_replace(buffer, clickable);
1450 field = buffer;
1451 }
1452
1453 if (row == t->data) /* underline header line fully, including the column separator */
1454 fputs(ansi_underline(), f);
1455
1456 if (j > 0)
1457 fputc(' ', f); /* column separator */
1458
1459 if (d->color && colors_enabled()) {
1460 if (row == t->data) /* first undo header underliner */
1461 fputs(ANSI_NORMAL, f);
1462
1463 fputs(d->color, f);
1464 }
1465
1466 fputs(field, f);
1467
1468 if (colors_enabled() && (d->color || row == t->data))
1469 fputs(ANSI_NORMAL, f);
1470 }
1471
1472 fputc('\n', f);
1473 }
1474
1475 return fflush_and_check(f);
1476 }
1477
1478 int table_format(Table *t, char **ret) {
1479 _cleanup_fclose_ FILE *f = NULL;
1480 char *buf = NULL;
1481 size_t sz = 0;
1482 int r;
1483
1484 f = open_memstream_unlocked(&buf, &sz);
1485 if (!f)
1486 return -ENOMEM;
1487
1488 r = table_print(t, f);
1489 if (r < 0)
1490 return r;
1491
1492 f = safe_fclose(f);
1493
1494 *ret = buf;
1495
1496 return 0;
1497 }
1498
1499 size_t table_get_rows(Table *t) {
1500 if (!t)
1501 return 0;
1502
1503 assert(t->n_columns > 0);
1504 return t->n_cells / t->n_columns;
1505 }
1506
1507 size_t table_get_columns(Table *t) {
1508 if (!t)
1509 return 0;
1510
1511 assert(t->n_columns > 0);
1512 return t->n_columns;
1513 }
1514
1515 int table_set_reverse(Table *t, size_t column, bool b) {
1516 assert(t);
1517 assert(column < t->n_columns);
1518
1519 if (!t->reverse_map) {
1520 if (!b)
1521 return 0;
1522
1523 t->reverse_map = new0(bool, t->n_columns);
1524 if (!t->reverse_map)
1525 return -ENOMEM;
1526 }
1527
1528 t->reverse_map[column] = b;
1529 return 0;
1530 }
1531
1532 TableCell *table_get_cell(Table *t, size_t row, size_t column) {
1533 size_t i;
1534
1535 assert(t);
1536
1537 if (column >= t->n_columns)
1538 return NULL;
1539
1540 i = row * t->n_columns + column;
1541 if (i >= t->n_cells)
1542 return NULL;
1543
1544 return TABLE_INDEX_TO_CELL(i);
1545 }
1546
1547 const void *table_get(Table *t, TableCell *cell) {
1548 TableData *d;
1549
1550 assert(t);
1551
1552 d = table_get_data(t, cell);
1553 if (!d)
1554 return NULL;
1555
1556 return d->data;
1557 }
1558
1559 const void* table_get_at(Table *t, size_t row, size_t column) {
1560 TableCell *cell;
1561
1562 cell = table_get_cell(t, row, column);
1563 if (!cell)
1564 return NULL;
1565
1566 return table_get(t, cell);
1567 }
1568
1569 static int table_data_to_json(TableData *d, JsonVariant **ret) {
1570
1571 switch (d->type) {
1572
1573 case TABLE_EMPTY:
1574 return json_variant_new_null(ret);
1575
1576 case TABLE_STRING:
1577 return json_variant_new_string(ret, d->string);
1578
1579 case TABLE_BOOLEAN:
1580 return json_variant_new_boolean(ret, d->boolean);
1581
1582 case TABLE_TIMESTAMP:
1583 if (d->timestamp == USEC_INFINITY)
1584 return json_variant_new_null(ret);
1585
1586 return json_variant_new_unsigned(ret, d->timestamp);
1587
1588 case TABLE_TIMESPAN:
1589 if (d->timespan == USEC_INFINITY)
1590 return json_variant_new_null(ret);
1591
1592 return json_variant_new_unsigned(ret, d->timespan);
1593
1594 case TABLE_SIZE:
1595 if (d->size == (size_t) -1)
1596 return json_variant_new_null(ret);
1597
1598 return json_variant_new_unsigned(ret, d->size);
1599
1600 case TABLE_INT:
1601 return json_variant_new_integer(ret, d->int_val);
1602
1603 case TABLE_INT32:
1604 return json_variant_new_integer(ret, d->int32);
1605
1606 case TABLE_INT64:
1607 return json_variant_new_integer(ret, d->int64);
1608
1609 case TABLE_UINT:
1610 return json_variant_new_unsigned(ret, d->uint_val);
1611
1612 case TABLE_UINT32:
1613 return json_variant_new_unsigned(ret, d->uint32);
1614
1615 case TABLE_UINT64:
1616 return json_variant_new_unsigned(ret, d->uint64);
1617
1618 case TABLE_PERCENT:
1619 return json_variant_new_integer(ret, d->percent);
1620
1621 default:
1622 return -EINVAL;
1623 }
1624 }
1625
1626 int table_to_json(Table *t, JsonVariant **ret) {
1627 JsonVariant **rows = NULL, **elements = NULL;
1628 _cleanup_free_ size_t *sorted = NULL;
1629 size_t n_rows, i, j, display_columns;
1630 int r;
1631
1632 assert(t);
1633
1634 /* Ensure we have no incomplete rows */
1635 assert(t->n_cells % t->n_columns == 0);
1636
1637 n_rows = t->n_cells / t->n_columns;
1638 assert(n_rows > 0); /* at least the header row must be complete */
1639
1640 if (t->sort_map) {
1641 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1642
1643 sorted = new(size_t, n_rows);
1644 if (!sorted) {
1645 r = -ENOMEM;
1646 goto finish;
1647 }
1648
1649 for (i = 0; i < n_rows; i++)
1650 sorted[i] = i * t->n_columns;
1651
1652 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1653 }
1654
1655 if (t->display_map)
1656 display_columns = t->n_display_map;
1657 else
1658 display_columns = t->n_columns;
1659 assert(display_columns > 0);
1660
1661 elements = new0(JsonVariant*, display_columns * 2);
1662 if (!elements) {
1663 r = -ENOMEM;
1664 goto finish;
1665 }
1666
1667 for (j = 0; j < display_columns; j++) {
1668 TableData *d;
1669
1670 assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
1671
1672 r = table_data_to_json(d, elements + j*2);
1673 if (r < 0)
1674 goto finish;
1675 }
1676
1677 rows = new0(JsonVariant*, n_rows-1);
1678 if (!rows) {
1679 r = -ENOMEM;
1680 goto finish;
1681 }
1682
1683 for (i = 1; i < n_rows; i++) {
1684 TableData **row;
1685
1686 if (sorted)
1687 row = t->data + sorted[i];
1688 else
1689 row = t->data + i * t->n_columns;
1690
1691 for (j = 0; j < display_columns; j++) {
1692 TableData *d;
1693 size_t k;
1694
1695 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1696
1697 k = j*2+1;
1698 elements[k] = json_variant_unref(elements[k]);
1699
1700 r = table_data_to_json(d, elements + k);
1701 if (r < 0)
1702 goto finish;
1703 }
1704
1705 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1706 if (r < 0)
1707 goto finish;
1708 }
1709
1710 r = json_variant_new_array(ret, rows, n_rows - 1);
1711
1712 finish:
1713 if (rows) {
1714 json_variant_unref_many(rows, n_rows-1);
1715 free(rows);
1716 }
1717
1718 if (elements) {
1719 json_variant_unref_many(elements, display_columns*2);
1720 free(elements);
1721 }
1722
1723 return r;
1724 }
1725
1726 int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
1727 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1728 int r;
1729
1730 assert(t);
1731
1732 if (!f)
1733 f = stdout;
1734
1735 r = table_to_json(t, &v);
1736 if (r < 0)
1737 return r;
1738
1739 json_variant_dump(v, flags, f, NULL);
1740
1741 return fflush_and_check(f);
1742 }