]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/format-table.c
Set theme jekyll-theme-primer
[thirdparty/systemd.git] / src / basic / format-table.c
CommitLineData
1960e736
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <stdio_ext.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 "pager.h"
11#include "parse-util.h"
12#include "string-util.h"
13#include "terminal-util.h"
14#include "time-util.h"
15#include "utf8.h"
16#include "util.h"
17
18#define DEFAULT_WEIGHT 100
19
20/*
21 A few notes on implementation details:
22
23 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
24 table. It can be easily converted to an index number and back.
25
26 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
27 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
28 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
29 outside only sees Table and TableCell.
30
31 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
32 previous one.
33
34 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
35 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
36 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
37 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
38
475d8599 39 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
1960e736
LP
40 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
41 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
42 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
43 instead.
44
45 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
46 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
47 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
48*/
49
50typedef struct TableData {
51 unsigned n_ref;
52 TableDataType type;
53
54 size_t minimum_width; /* minimum width for the column */
55 size_t maximum_width; /* maximum width for the column */
56 unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
57 unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
58 unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
59
60 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 */
61 char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
62
63 union {
64 uint8_t data[0]; /* data is generic array */
65 bool boolean;
66 usec_t timestamp;
67 usec_t timespan;
68 uint64_t size;
69 char string[0];
70 uint32_t uint32;
71 /* … add more here as we start supporting more cell data types … */
72 };
73} TableData;
74
75static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
ee7b9f1d 76 size_t i;
1960e736
LP
77
78 assert(cell);
79
ee7b9f1d 80 i = PTR_TO_SIZE(cell);
1960e736
LP
81 assert(i > 0);
82
83 return i-1;
84}
85
86static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
87 assert(index != (size_t) -1);
ee7b9f1d 88 return SIZE_TO_PTR(index + 1);
1960e736
LP
89}
90
91struct Table {
92 size_t n_columns;
93 size_t n_cells;
94
95 bool header; /* Whether to show the header row? */
96 size_t width; /* If != (size_t) -1 the width to format this table in */
97
98 TableData **data;
99 size_t n_allocated;
100
101 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 */
102 size_t n_display_map;
103
104 size_t *sort_map; /* The columns to order rows by, in order of preference. */
105 size_t n_sort_map;
106};
107
108Table *table_new_raw(size_t n_columns) {
109 _cleanup_(table_unrefp) Table *t = NULL;
110
111 assert(n_columns > 0);
112
113 t = new(Table, 1);
114 if (!t)
115 return NULL;
116
117 *t = (struct Table) {
118 .n_columns = n_columns,
119 .header = true,
120 .width = (size_t) -1,
121 };
122
123 return TAKE_PTR(t);
124}
125
126Table *table_new_internal(const char *first_header, ...) {
127 _cleanup_(table_unrefp) Table *t = NULL;
128 size_t n_columns = 1;
129 va_list ap;
130 int r;
131
132 assert(first_header);
133
134 va_start(ap, first_header);
135 for (;;) {
136 const char *h;
137
138 h = va_arg(ap, const char*);
139 if (!h)
140 break;
141
142 n_columns++;
143 }
144 va_end(ap);
145
146 t = table_new_raw(n_columns);
147 if (!t)
148 return NULL;
149
150 r = table_add_cell(t, NULL, TABLE_STRING, first_header);
151 if (r < 0)
152 return NULL;
153
154 va_start(ap, first_header);
155 for (;;) {
156 const char *h;
157
158 h = va_arg(ap, const char*);
159 if (!h)
160 break;
161
162 r = table_add_cell(t, NULL, TABLE_STRING, h);
163 if (r < 0) {
164 va_end(ap);
165 return NULL;
166 }
167 }
168 va_end(ap);
169
170 assert(t->n_columns == t->n_cells);
171 return TAKE_PTR(t);
172}
173
8301aa0b
YW
174static TableData *table_data_free(TableData *d) {
175 assert(d);
1960e736
LP
176
177 free(d->formatted);
178 return mfree(d);
179}
180
8301aa0b 181DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
1960e736
LP
182DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
183
1960e736
LP
184Table *table_unref(Table *t) {
185 size_t i;
186
187 if (!t)
188 return NULL;
189
190 for (i = 0; i < t->n_cells; i++)
191 table_data_unref(t->data[i]);
192
193 free(t->data);
194 free(t->display_map);
195 free(t->sort_map);
196
197 return mfree(t);
198}
199
200static size_t table_data_size(TableDataType type, const void *data) {
201
202 switch (type) {
203
204 case TABLE_EMPTY:
205 return 0;
206
207 case TABLE_STRING:
208 return strlen(data) + 1;
209
210 case TABLE_BOOLEAN:
211 return sizeof(bool);
212
213 case TABLE_TIMESTAMP:
214 case TABLE_TIMESPAN:
215 return sizeof(usec_t);
216
217 case TABLE_SIZE:
218 return sizeof(uint64_t);
219
220 case TABLE_UINT32:
221 return sizeof(uint32_t);
222
223 default:
224 assert_not_reached("Uh? Unexpected cell type");
225 }
226}
227
228static bool table_data_matches(
229 TableData *d,
230 TableDataType type,
231 const void *data,
232 size_t minimum_width,
233 size_t maximum_width,
234 unsigned weight,
235 unsigned align_percent,
236 unsigned ellipsize_percent) {
237
238 size_t k, l;
239 assert(d);
240
241 if (d->type != type)
242 return false;
243
244 if (d->minimum_width != minimum_width)
245 return false;
246
247 if (d->maximum_width != maximum_width)
248 return false;
249
250 if (d->weight != weight)
251 return false;
252
253 if (d->align_percent != align_percent)
254 return false;
255
256 if (d->ellipsize_percent != ellipsize_percent)
257 return false;
258
259 k = table_data_size(type, data);
260 l = table_data_size(d->type, d->data);
261
262 if (k != l)
263 return false;
264
265 return memcmp(data, d->data, l) == 0;
266}
267
268static TableData *table_data_new(
269 TableDataType type,
270 const void *data,
271 size_t minimum_width,
272 size_t maximum_width,
273 unsigned weight,
274 unsigned align_percent,
275 unsigned ellipsize_percent) {
276
277 size_t data_size;
278 TableData *d;
279
280 data_size = table_data_size(type, data);
281
282 d = malloc0(offsetof(TableData, data) + data_size);
283 if (!d)
284 return NULL;
285
286 d->n_ref = 1;
287 d->type = type;
288 d->minimum_width = minimum_width;
289 d->maximum_width = maximum_width;
290 d->weight = weight;
291 d->align_percent = align_percent;
292 d->ellipsize_percent = ellipsize_percent;
293 memcpy_safe(d->data, data, data_size);
294
295 return d;
296}
297
298int table_add_cell_full(
299 Table *t,
300 TableCell **ret_cell,
301 TableDataType type,
302 const void *data,
303 size_t minimum_width,
304 size_t maximum_width,
305 unsigned weight,
306 unsigned align_percent,
307 unsigned ellipsize_percent) {
308
309 _cleanup_(table_data_unrefp) TableData *d = NULL;
310 TableData *p;
311
312 assert(t);
313 assert(type >= 0);
314 assert(type < _TABLE_DATA_TYPE_MAX);
315
316 /* Determine the cell adjacent to the current one, but one row up */
317 if (t->n_cells >= t->n_columns)
318 assert_se(p = t->data[t->n_cells - t->n_columns]);
319 else
320 p = NULL;
321
322 /* If formatting parameters are left unspecified, copy from the previous row */
323 if (minimum_width == (size_t) -1)
324 minimum_width = p ? p->minimum_width : 1;
325
326 if (weight == (unsigned) -1)
327 weight = p ? p->weight : DEFAULT_WEIGHT;
328
329 if (align_percent == (unsigned) -1)
330 align_percent = p ? p->align_percent : 0;
331
332 if (ellipsize_percent == (unsigned) -1)
333 ellipsize_percent = p ? p->ellipsize_percent : 100;
334
335 assert(align_percent <= 100);
336 assert(ellipsize_percent <= 100);
337
338 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
339 * formatting. Let's see if we can reuse the cell data and ref it once more. */
340
341 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
342 d = table_data_ref(p);
343 else {
344 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
345 if (!d)
346 return -ENOMEM;
347 }
348
349 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
350 return -ENOMEM;
351
352 if (ret_cell)
353 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
354
355 t->data[t->n_cells++] = TAKE_PTR(d);
356
357 return 0;
358}
359
360int table_dup_cell(Table *t, TableCell *cell) {
361 size_t i;
362
363 assert(t);
364
365 /* Add the data of the specified cell a second time as a new cell to the end. */
366
367 i = TABLE_CELL_TO_INDEX(cell);
368 if (i >= t->n_cells)
369 return -ENXIO;
370
371 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
372 return -ENOMEM;
373
374 t->data[t->n_cells++] = table_data_ref(t->data[i]);
375 return 0;
376}
377
378static int table_dedup_cell(Table *t, TableCell *cell) {
379 TableData *nd, *od;
380 size_t i;
381
382 assert(t);
383
384 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
385 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
386
387 i = TABLE_CELL_TO_INDEX(cell);
388 if (i >= t->n_cells)
389 return -ENXIO;
390
391 assert_se(od = t->data[i]);
392 if (od->n_ref == 1)
393 return 0;
394
395 assert(od->n_ref > 1);
396
397 nd = table_data_new(od->type, od->data, od->minimum_width, od->maximum_width, od->weight, od->align_percent, od->ellipsize_percent);
398 if (!nd)
399 return -ENOMEM;
400
401 table_data_unref(od);
402 t->data[i] = nd;
403
404 assert(nd->n_ref == 1);
405
406 return 1;
407}
408
409static TableData *table_get_data(Table *t, TableCell *cell) {
410 size_t i;
411
412 assert(t);
413 assert(cell);
414
415 /* Get the data object of the specified cell, or NULL if it doesn't exist */
416
417 i = TABLE_CELL_TO_INDEX(cell);
418 if (i >= t->n_cells)
419 return NULL;
420
421 assert(t->data[i]);
422 assert(t->data[i]->n_ref > 0);
423
424 return t->data[i];
425}
426
427int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
428 int r;
429
430 assert(t);
431 assert(cell);
432
433 if (minimum_width == (size_t) -1)
434 minimum_width = 1;
435
436 r = table_dedup_cell(t, cell);
437 if (r < 0)
438 return r;
439
440 table_get_data(t, cell)->minimum_width = minimum_width;
441 return 0;
442}
443
444int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
445 int r;
446
447 assert(t);
448 assert(cell);
449
450 r = table_dedup_cell(t, cell);
451 if (r < 0)
452 return r;
453
454 table_get_data(t, cell)->maximum_width = maximum_width;
455 return 0;
456}
457
458int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
459 int r;
460
461 assert(t);
462 assert(cell);
463
464 if (weight == (unsigned) -1)
465 weight = DEFAULT_WEIGHT;
466
467 r = table_dedup_cell(t, cell);
468 if (r < 0)
469 return r;
470
471 table_get_data(t, cell)->weight = weight;
472 return 0;
473}
474
475int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
476 int r;
477
478 assert(t);
479 assert(cell);
480
481 if (percent == (unsigned) -1)
482 percent = 0;
483
484 assert(percent <= 100);
485
486 r = table_dedup_cell(t, cell);
487 if (r < 0)
488 return r;
489
490 table_get_data(t, cell)->align_percent = percent;
491 return 0;
492}
493
494int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
495 int r;
496
497 assert(t);
498 assert(cell);
499
500 if (percent == (unsigned) -1)
501 percent = 100;
502
503 assert(percent <= 100);
504
505 r = table_dedup_cell(t, cell);
506 if (r < 0)
507 return r;
508
509 table_get_data(t, cell)->ellipsize_percent = percent;
510 return 0;
511}
512
513int table_set_color(Table *t, TableCell *cell, const char *color) {
514 int r;
515
516 assert(t);
517 assert(cell);
518
519 r = table_dedup_cell(t, cell);
520 if (r < 0)
521 return r;
522
523 table_get_data(t, cell)->color = empty_to_null(color);
524 return 0;
525}
526
527int table_add_many_internal(Table *t, TableDataType first_type, ...) {
528 TableDataType type;
529 va_list ap;
530 int r;
531
532 assert(t);
533 assert(first_type >= 0);
534 assert(first_type < _TABLE_DATA_TYPE_MAX);
535
536 type = first_type;
537
538 va_start(ap, first_type);
539 for (;;) {
540 const void *data;
541 union {
542 uint64_t size;
543 usec_t usec;
544 uint32_t uint32;
545 bool b;
546 } buffer;
547
548 switch (type) {
549
550 case TABLE_EMPTY:
551 data = NULL;
552 break;
553
554 case TABLE_STRING:
555 data = va_arg(ap, const char *);
556 break;
557
558 case TABLE_BOOLEAN:
5d904a6a 559 buffer.b = va_arg(ap, int);
1960e736
LP
560 data = &buffer.b;
561 break;
562
563 case TABLE_TIMESTAMP:
564 case TABLE_TIMESPAN:
565 buffer.usec = va_arg(ap, usec_t);
566 data = &buffer.usec;
567 break;
568
569 case TABLE_SIZE:
570 buffer.size = va_arg(ap, uint64_t);
571 data = &buffer.size;
572 break;
573
574 case TABLE_UINT32:
575 buffer.uint32 = va_arg(ap, uint32_t);
576 data = &buffer.uint32;
577 break;
578
579 case _TABLE_DATA_TYPE_MAX:
580 /* Used as end marker */
581 va_end(ap);
582 return 0;
583
584 default:
585 assert_not_reached("Uh? Unexpected data type.");
586 }
587
588 r = table_add_cell(t, NULL, type, data);
589 if (r < 0) {
590 va_end(ap);
591 return r;
592 }
593
594 type = va_arg(ap, TableDataType);
595 }
596}
597
598void table_set_header(Table *t, bool b) {
599 assert(t);
600
601 t->header = b;
602}
603
604void table_set_width(Table *t, size_t width) {
605 assert(t);
606
607 t->width = width;
608}
609
610int table_set_display(Table *t, size_t first_column, ...) {
611 size_t allocated, column;
612 va_list ap;
613
614 assert(t);
615
616 allocated = t->n_display_map;
617 column = first_column;
618
619 va_start(ap, first_column);
620 for (;;) {
621 assert(column < t->n_columns);
622
623 if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
624 va_end(ap);
625 return -ENOMEM;
626 }
627
628 t->display_map[t->n_display_map++] = column;
629
630 column = va_arg(ap, size_t);
631 if (column == (size_t) -1)
632 break;
633
634 }
f20f4a77 635 va_end(ap);
1960e736
LP
636
637 return 0;
638}
639
640int table_set_sort(Table *t, size_t first_column, ...) {
641 size_t allocated, column;
642 va_list ap;
643
644 assert(t);
645
646 allocated = t->n_sort_map;
647 column = first_column;
648
649 va_start(ap, first_column);
650 for (;;) {
651 assert(column < t->n_columns);
652
653 if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
654 va_end(ap);
655 return -ENOMEM;
656 }
657
658 t->sort_map[t->n_sort_map++] = column;
659
660 column = va_arg(ap, size_t);
661 if (column == (size_t) -1)
662 break;
663 }
f20f4a77 664 va_end(ap);
1960e736
LP
665
666 return 0;
667}
668
669static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
670 assert(a);
671 assert(b);
672
673 if (a->type == b->type) {
674
675 /* We only define ordering for cells of the same data type. If cells with different data types are
676 * compared we follow the order the cells were originally added in */
677
678 switch (a->type) {
679
680 case TABLE_STRING:
681 return strcmp(a->string, b->string);
682
683 case TABLE_BOOLEAN:
684 if (!a->boolean && b->boolean)
685 return -1;
686 if (a->boolean && !b->boolean)
687 return 1;
688 return 0;
689
690 case TABLE_TIMESTAMP:
691 if (a->timestamp < b->timestamp)
692 return -1;
693 if (a->timestamp > b->timestamp)
694 return 1;
695 return 0;
696
697 case TABLE_TIMESPAN:
698 if (a->timespan < b->timespan)
699 return -1;
700 if (a->timespan > b->timespan)
701 return 1;
702 return 0;
703
704 case TABLE_SIZE:
705 if (a->size < b->size)
706 return -1;
707 if (a->size > b->size)
708 return 1;
709 return 0;
710
711 case TABLE_UINT32:
712 if (a->uint32 < b->uint32)
713 return -1;
714 if (a->uint32 > b->uint32)
715 return 1;
716 return 0;
717
718 default:
719 ;
720 }
721 }
722
723 /* Generic fallback using the orginal order in which the cells where added. */
724 if (index_a < index_b)
725 return -1;
726 if (index_a > index_b)
727 return 1;
728
729 return 0;
730}
731
ba0a7bfb 732static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
1960e736
LP
733 size_t i;
734 int r;
735
736 assert(t);
737 assert(t->sort_map);
738
739 /* Make sure the header stays at the beginning */
740 if (*a < t->n_columns && *b < t->n_columns)
741 return 0;
742 if (*a < t->n_columns)
743 return -1;
744 if (*b < t->n_columns)
745 return 1;
746
747 /* Order other lines by the sorting map */
748 for (i = 0; i < t->n_sort_map; i++) {
749 TableData *d, *dd;
750
751 d = t->data[*a + t->sort_map[i]];
752 dd = t->data[*b + t->sort_map[i]];
753
754 r = cell_data_compare(d, *a, dd, *b);
755 if (r != 0)
756 return r;
757 }
758
759 /* Order identical lines by the order there were originally added in */
ba0a7bfb 760 return CMP(*a, *b);
1960e736
LP
761}
762
763static const char *table_data_format(TableData *d) {
764 assert(d);
765
766 if (d->formatted)
767 return d->formatted;
768
769 switch (d->type) {
770 case TABLE_EMPTY:
771 return "";
772
773 case TABLE_STRING:
774 return d->string;
775
776 case TABLE_BOOLEAN:
777 return yes_no(d->boolean);
778
779 case TABLE_TIMESTAMP: {
780 _cleanup_free_ char *p;
781
782 p = new(char, FORMAT_TIMESTAMP_MAX);
783 if (!p)
784 return NULL;
785
786 if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
787 return "n/a";
788
789 d->formatted = TAKE_PTR(p);
790 break;
791 }
792
793 case TABLE_TIMESPAN: {
794 _cleanup_free_ char *p;
795
796 p = new(char, FORMAT_TIMESPAN_MAX);
797 if (!p)
798 return NULL;
799
800 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
801 return "n/a";
802
803 d->formatted = TAKE_PTR(p);
804 break;
805 }
806
807 case TABLE_SIZE: {
808 _cleanup_free_ char *p;
809
810 p = new(char, FORMAT_BYTES_MAX);
811 if (!p)
812 return NULL;
813
814 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
815 return "n/a";
816
817 d->formatted = TAKE_PTR(p);
818 break;
819 }
820
821 case TABLE_UINT32: {
822 _cleanup_free_ char *p;
823
824 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
825 if (!p)
826 return NULL;
827
828 sprintf(p, "%" PRIu32, d->uint32);
829 d->formatted = TAKE_PTR(p);
830 break;
831 }
832
833 default:
834 assert_not_reached("Unexpected type?");
835 }
836
1960e736
LP
837 return d->formatted;
838}
839
840static int table_data_requested_width(TableData *d, size_t *ret) {
841 const char *t;
842 size_t l;
843
844 t = table_data_format(d);
845 if (!t)
846 return -ENOMEM;
847
848 l = utf8_console_width(t);
849 if (l == (size_t) -1)
850 return -EINVAL;
851
852 if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
853 l = d->maximum_width;
854
855 if (l < d->minimum_width)
856 l = d->minimum_width;
857
858 *ret = l;
859 return 0;
860}
861
cfc01c1e
ZJS
862static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
863 size_t w = 0, space, lspace, old_length;
1960e736
LP
864 const char *p;
865 char *ret;
866 size_t i;
867
868 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
869
870 assert(str);
871 assert(percent <= 100);
872
cfc01c1e 873 old_length = strlen(str);
1960e736
LP
874
875 /* Determine current width on screen */
876 p = str;
877 while (p < str + old_length) {
878 char32_t c;
879
880 if (utf8_encoded_to_unichar(p, &c) < 0) {
881 p++, w++; /* count invalid chars as 1 */
882 continue;
883 }
884
885 p = utf8_next_char(p);
886 w += unichar_iswide(c) ? 2 : 1;
887 }
888
889 /* Already wider than the target, if so, don't do anything */
890 if (w >= new_length)
891 return strndup(str, old_length);
892
893 /* How much spaces shall we add? An how much on the left side? */
894 space = new_length - w;
895 lspace = space * percent / 100U;
896
897 ret = new(char, space + old_length + 1);
898 if (!ret)
899 return NULL;
900
901 for (i = 0; i < lspace; i++)
902 ret[i] = ' ';
903 memcpy(ret + lspace, str, old_length);
904 for (i = lspace + old_length; i < space + old_length; i++)
905 ret[i] = ' ';
906
907 ret[space + old_length] = 0;
908 return ret;
909}
910
911int table_print(Table *t, FILE *f) {
912 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
913 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
914 *width;
915 _cleanup_free_ size_t *sorted = NULL;
916 uint64_t *column_weight, weight_sum;
917 int r;
918
919 assert(t);
920
921 if (!f)
922 f = stdout;
923
924 /* Ensure we have no incomplete rows */
925 assert(t->n_cells % t->n_columns == 0);
926
927 n_rows = t->n_cells / t->n_columns;
928 assert(n_rows > 0); /* at least the header row must be complete */
929
930 if (t->sort_map) {
931 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
932
933 sorted = new(size_t, n_rows);
934 if (!sorted)
935 return -ENOMEM;
936
937 for (i = 0; i < n_rows; i++)
938 sorted[i] = i * t->n_columns;
939
ba0a7bfb 940 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1960e736
LP
941 }
942
943 if (t->display_map)
944 display_columns = t->n_display_map;
945 else
946 display_columns = t->n_columns;
947
948 assert(display_columns > 0);
949
950 minimum_width = newa(size_t, display_columns);
951 maximum_width = newa(size_t, display_columns);
952 requested_width = newa(size_t, display_columns);
953 width = newa(size_t, display_columns);
954 column_weight = newa0(uint64_t, display_columns);
955
956 for (j = 0; j < display_columns; j++) {
957 minimum_width[j] = 1;
958 maximum_width[j] = (size_t) -1;
959 requested_width[j] = (size_t) -1;
960 }
961
962 /* First pass: determine column sizes */
963 for (i = t->header ? 0 : 1; i < n_rows; i++) {
964 TableData **row;
965
966 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
967 * hence we don't care for sorted[] during the first pass. */
968 row = t->data + i * t->n_columns;
969
970 for (j = 0; j < display_columns; j++) {
971 TableData *d;
972 size_t req;
973
974 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
975
976 r = table_data_requested_width(d, &req);
977 if (r < 0)
978 return r;
979
980 /* Determine the biggest width that any cell in this column would like to have */
981 if (requested_width[j] == (size_t) -1 ||
982 requested_width[j] < req)
983 requested_width[j] = req;
984
985 /* Determine the minimum width any cell in this column needs */
986 if (minimum_width[j] < d->minimum_width)
987 minimum_width[j] = d->minimum_width;
988
989 /* Determine the maximum width any cell in this column needs */
990 if (d->maximum_width != (size_t) -1 &&
991 (maximum_width[j] == (size_t) -1 ||
992 maximum_width[j] > d->maximum_width))
993 maximum_width[j] = d->maximum_width;
994
995 /* Determine the full columns weight */
996 column_weight[j] += d->weight;
997 }
998 }
999
1000 /* One space between each column */
1001 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1002
1003 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1004 weight_sum = 0;
1005 for (j = 0; j < display_columns; j++) {
1006 weight_sum += column_weight[j];
1007
1008 table_minimum_width += minimum_width[j];
1009
1010 if (maximum_width[j] == (size_t) -1)
1011 table_maximum_width = (size_t) -1;
1012 else
1013 table_maximum_width += maximum_width[j];
1014
1015 table_requested_width += requested_width[j];
1016 }
1017
1018 /* Calculate effective table width */
1019 if (t->width == (size_t) -1)
1020 table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1021 else
1022 table_effective_width = t->width;
1023
1024 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1025 table_effective_width = table_maximum_width;
1026
1027 if (table_effective_width < table_minimum_width)
1028 table_effective_width = table_minimum_width;
1029
1030 if (table_effective_width >= table_requested_width) {
1031 size_t extra;
1032
1033 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1034 * each column with what it asked for and the distribute the rest. */
1035
1036 extra = table_effective_width - table_requested_width;
1037
1038 for (j = 0; j < display_columns; j++) {
1039 size_t delta;
1040
1041 if (weight_sum == 0)
1042 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1043 else
1044 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1045
1046 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1047 width[j] = maximum_width[j];
1048
1049 if (width[j] < minimum_width[j])
1050 width[j] = minimum_width[j];
1051
1052 assert(width[j] >= requested_width[j]);
1053 delta = width[j] - requested_width[j];
1054
1055 /* Subtract what we just added from the rest */
1056 if (extra > delta)
1057 extra -= delta;
1058 else
1059 extra = 0;
1060
1061 assert(weight_sum >= column_weight[j]);
1062 weight_sum -= column_weight[j];
1063 }
1064
1065 } else {
1066 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1067 * with the minimum they need, and then distribute anything left. */
1068 bool finalize = false;
1069 size_t extra;
1070
1071 extra = table_effective_width - table_minimum_width;
1072
1073 for (j = 0; j < display_columns; j++)
1074 width[j] = (size_t) -1;
1075
1076 for (;;) {
1077 bool restart = false;
1078
1079 for (j = 0; j < display_columns; j++) {
1080 size_t delta, w;
1081
1082 /* Did this column already get something assigned? If so, let's skip to the next */
1083 if (width[j] != (size_t) -1)
1084 continue;
1085
1086 if (weight_sum == 0)
1087 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1088 else
1089 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1090
1091 if (w >= requested_width[j]) {
1092 /* Never give more than requested. If we hit a column like this, there's more
1093 * space to allocate to other columns which means we need to restart the
1094 * iteration. However, if we hit a column like this, let's assign it the space
1095 * it wanted for good early.*/
1096
1097 w = requested_width[j];
1098 restart = true;
1099
1100 } else if (!finalize)
1101 continue;
1102
1103 width[j] = w;
1104
1105 assert(w >= minimum_width[j]);
1106 delta = w - minimum_width[j];
1107
1108 assert(delta <= extra);
1109 extra -= delta;
1110
1111 assert(weight_sum >= column_weight[j]);
1112 weight_sum -= column_weight[j];
1113
a26db0bc 1114 if (restart && !finalize)
1960e736
LP
1115 break;
1116 }
1117
a26db0bc 1118 if (finalize)
1960e736 1119 break;
1960e736
LP
1120
1121 if (!restart)
1122 finalize = true;
1123 }
1124 }
1125
1126 /* Second pass: show output */
1127 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1128 TableData **row;
1129
1130 if (sorted)
1131 row = t->data + sorted[i];
1132 else
1133 row = t->data + i * t->n_columns;
1134
1135 for (j = 0; j < display_columns; j++) {
1136 _cleanup_free_ char *buffer = NULL;
1137 const char *field;
1138 TableData *d;
1139 size_t l;
1140
1141 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1142
1143 field = table_data_format(d);
1144 if (!field)
1145 return -ENOMEM;
1146
1147 l = utf8_console_width(field);
1148 if (l > width[j]) {
1149 /* Field is wider than allocated space. Let's ellipsize */
1150
21e4e3e0 1151 buffer = ellipsize(field, width[j], d->ellipsize_percent);
1960e736
LP
1152 if (!buffer)
1153 return -ENOMEM;
1154
1155 field = buffer;
1156
1157 } else if (l < width[j]) {
1158 /* Field is shorter than allocated space. Let's align with spaces */
1159
cfc01c1e 1160 buffer = align_string_mem(field, width[j], d->align_percent);
1960e736
LP
1161 if (!buffer)
1162 return -ENOMEM;
1163
1164 field = buffer;
1165 }
1166
1167 if (j > 0)
1168 fputc(' ', f); /* column separator */
1169
1170 if (d->color)
1171 fputs(d->color, f);
1172
1173 fputs(field, f);
1174
1175 if (d->color)
1176 fputs(ansi_normal(), f);
1177 }
1178
1179 fputc('\n', f);
1180 }
1181
1182 return fflush_and_check(f);
1183}
1184
1185int table_format(Table *t, char **ret) {
1186 _cleanup_fclose_ FILE *f = NULL;
1187 char *buf = NULL;
1188 size_t sz = 0;
1189 int r;
1190
1191 f = open_memstream(&buf, &sz);
1192 if (!f)
1193 return -ENOMEM;
1194
1195 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
1196
1197 r = table_print(t, f);
1198 if (r < 0)
1199 return r;
1200
1201 f = safe_fclose(f);
1202
1203 *ret = buf;
1204
1205 return 0;
1206}
1207
1208size_t table_get_rows(Table *t) {
1209 if (!t)
1210 return 0;
1211
1212 assert(t->n_columns > 0);
1213 return t->n_cells / t->n_columns;
1214}
1215
1216size_t table_get_columns(Table *t) {
1217 if (!t)
1218 return 0;
1219
1220 assert(t->n_columns > 0);
1221 return t->n_columns;
1222}