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