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