]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
de72bf02f32c7caf987ee15ec8e7af5fdbb684f6
1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "alloc-util.h"
8 #include "format-table.h"
10 #include "memory-util.h"
12 #include "parse-util.h"
13 #include "pretty-print.h"
14 #include "sort-util.h"
15 #include "string-util.h"
16 #include "terminal-util.h"
17 #include "time-util.h"
21 #define DEFAULT_WEIGHT 100
24 A few notes on implementation details:
26 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
27 table. It can be easily converted to an index number and back.
29 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
30 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
31 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
32 outside only sees Table and TableCell.
34 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
37 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
38 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
39 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
40 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
42 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
43 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
44 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
45 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
48 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
49 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
50 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
53 typedef struct TableData
{
57 size_t minimum_width
; /* minimum width for the column */
58 size_t maximum_width
; /* maximum width for the column */
59 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
60 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
61 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
63 bool uppercase
; /* Uppercase string on display */
65 const char * color
; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
66 char * url
; /* A URL to use for a clickable hyperlink */
67 char * formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
70 uint8_t data
[ 0 ]; /* data is generic array */
78 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
79 /* … add more here as we start supporting more cell data types … */
83 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
88 i
= PTR_TO_SIZE ( cell
);
94 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
95 assert ( index
!= ( size_t ) - 1 );
96 return SIZE_TO_PTR ( index
+ 1 );
103 bool header
; /* Whether to show the header row? */
104 size_t width
; /* If != (size_t) -1 the width to format this table in */
109 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 */
110 size_t n_display_map
;
112 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
118 Table
* table_new_raw ( size_t n_columns
) {
119 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
121 assert ( n_columns
> 0 );
127 * t
= ( struct Table
) {
128 . n_columns
= n_columns
,
130 . width
= ( size_t ) - 1 ,
136 Table
* table_new_internal ( const char * first_header
, ...) {
137 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
138 size_t n_columns
= 1 ;
143 assert ( first_header
);
145 va_start ( ap
, first_header
);
147 h
= va_arg ( ap
, const char *);
155 t
= table_new_raw ( n_columns
);
159 va_start ( ap
, first_header
);
160 for ( h
= first_header
; h
; h
= va_arg ( ap
, const char *)) {
163 r
= table_add_cell ( t
, & cell
, TABLE_STRING
, h
);
169 /* Make the table header uppercase */
170 r
= table_set_uppercase ( t
, cell
, true );
178 assert ( t
-> n_columns
== t
-> n_cells
);
182 static TableData
* table_data_free ( TableData
* d
) {
191 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
192 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
194 Table
* table_unref ( Table
* t
) {
200 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
201 table_data_unref ( t
-> data
[ i
]);
204 free ( t
-> display_map
);
206 free ( t
-> reverse_map
);
211 static size_t table_data_size ( TableDataType type
, const void * data
) {
219 return strlen ( data
) + 1 ;
224 case TABLE_TIMESTAMP
:
226 return sizeof ( usec_t
);
230 return sizeof ( uint64_t );
233 return sizeof ( uint32_t );
239 assert_not_reached ( "Uh? Unexpected cell type" );
243 static bool table_data_matches (
247 size_t minimum_width
,
248 size_t maximum_width
,
250 unsigned align_percent
,
251 unsigned ellipsize_percent
) {
259 if ( d
-> minimum_width
!= minimum_width
)
262 if ( d
-> maximum_width
!= maximum_width
)
265 if ( d
-> weight
!= weight
)
268 if ( d
-> align_percent
!= align_percent
)
271 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
274 /* If a color/url/uppercase flag is set, refuse to merge */
282 k
= table_data_size ( type
, data
);
283 l
= table_data_size ( d
-> type
, d
-> data
);
288 return memcmp_safe ( data
, d
-> data
, l
) == 0 ;
291 static TableData
* table_data_new (
294 size_t minimum_width
,
295 size_t maximum_width
,
297 unsigned align_percent
,
298 unsigned ellipsize_percent
) {
303 data_size
= table_data_size ( type
, data
);
305 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
311 d
-> minimum_width
= minimum_width
;
312 d
-> maximum_width
= maximum_width
;
314 d
-> align_percent
= align_percent
;
315 d
-> ellipsize_percent
= ellipsize_percent
;
316 memcpy_safe ( d
-> data
, data
, data_size
);
321 int table_add_cell_full (
323 TableCell
** ret_cell
,
326 size_t minimum_width
,
327 size_t maximum_width
,
329 unsigned align_percent
,
330 unsigned ellipsize_percent
) {
332 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
337 assert ( type
< _TABLE_DATA_TYPE_MAX
);
339 /* Determine the cell adjacent to the current one, but one row up */
340 if ( t
-> n_cells
>= t
-> n_columns
)
341 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
345 /* If formatting parameters are left unspecified, copy from the previous row */
346 if ( minimum_width
== ( size_t ) - 1 )
347 minimum_width
= p
? p
-> minimum_width
: 1 ;
349 if ( weight
== ( unsigned ) - 1 )
350 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
352 if ( align_percent
== ( unsigned ) - 1 )
353 align_percent
= p
? p
-> align_percent
: 0 ;
355 if ( ellipsize_percent
== ( unsigned ) - 1 )
356 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
358 assert ( align_percent
<= 100 );
359 assert ( ellipsize_percent
<= 100 );
361 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
362 * formatting. Let's see if we can reuse the cell data and ref it once more. */
364 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
365 d
= table_data_ref ( p
);
367 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
372 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
376 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
378 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
383 int table_add_cell_stringf ( Table
* t
, TableCell
** ret_cell
, const char * format
, ...) {
384 _cleanup_free_
char * buffer
= NULL
;
388 va_start ( ap
, format
);
389 r
= vasprintf (& buffer
, format
, ap
);
394 return table_add_cell ( t
, ret_cell
, TABLE_STRING
, buffer
);
397 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
402 /* Add the data of the specified cell a second time as a new cell to the end. */
404 i
= TABLE_CELL_TO_INDEX ( cell
);
408 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
411 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
415 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
416 _cleanup_free_
char * curl
= NULL
;
422 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
423 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
425 i
= TABLE_CELL_TO_INDEX ( cell
);
429 assert_se ( od
= t
-> data
[ i
]);
433 assert ( od
-> n_ref
> 1 );
436 curl
= strdup ( od
-> url
);
448 od
-> ellipsize_percent
);
452 nd
-> color
= od
-> color
;
453 nd
-> url
= TAKE_PTR ( curl
);
454 nd
-> uppercase
= od
-> uppercase
;
456 table_data_unref ( od
);
459 assert ( nd
-> n_ref
== 1 );
464 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
470 /* Get the data object of the specified cell, or NULL if it doesn't exist */
472 i
= TABLE_CELL_TO_INDEX ( cell
);
477 assert ( t
-> data
[ i
]-> n_ref
> 0 );
482 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
488 if ( minimum_width
== ( size_t ) - 1 )
491 r
= table_dedup_cell ( t
, cell
);
495 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
499 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
505 r
= table_dedup_cell ( t
, cell
);
509 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
513 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
519 if ( weight
== ( unsigned ) - 1 )
520 weight
= DEFAULT_WEIGHT
;
522 r
= table_dedup_cell ( t
, cell
);
526 table_get_data ( t
, cell
)-> weight
= weight
;
530 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
536 if ( percent
== ( unsigned ) - 1 )
539 assert ( percent
<= 100 );
541 r
= table_dedup_cell ( t
, cell
);
545 table_get_data ( t
, cell
)-> align_percent
= percent
;
549 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
555 if ( percent
== ( unsigned ) - 1 )
558 assert ( percent
<= 100 );
560 r
= table_dedup_cell ( t
, cell
);
564 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
568 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
574 r
= table_dedup_cell ( t
, cell
);
578 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
582 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
583 _cleanup_free_
char * copy
= NULL
;
595 r
= table_dedup_cell ( t
, cell
);
599 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
602 int table_set_uppercase ( Table
* t
, TableCell
* cell
, bool b
) {
609 r
= table_dedup_cell ( t
, cell
);
613 assert_se ( d
= table_get_data ( t
, cell
));
615 if ( d
-> uppercase
== b
)
618 d
-> formatted
= mfree ( d
-> formatted
);
623 int table_update ( Table
* t
, TableCell
* cell
, TableDataType type
, const void * data
) {
624 _cleanup_free_
char * curl
= NULL
;
631 i
= TABLE_CELL_TO_INDEX ( cell
);
635 assert_se ( od
= t
-> data
[ i
]);
638 curl
= strdup ( od
-> url
);
650 od
-> ellipsize_percent
);
654 nd
-> color
= od
-> color
;
655 nd
-> url
= TAKE_PTR ( curl
);
656 nd
-> uppercase
= od
-> uppercase
;
658 table_data_unref ( od
);
664 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
670 assert ( first_type
>= 0 );
671 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
675 va_start ( ap
, first_type
);
694 data
= va_arg ( ap
, const char *);
698 buffer
. b
= va_arg ( ap
, int );
702 case TABLE_TIMESTAMP
:
704 buffer
. usec
= va_arg ( ap
, usec_t
);
709 buffer
. size
= va_arg ( ap
, uint64_t );
714 buffer
. uint32
= va_arg ( ap
, uint32_t );
715 data
= & buffer
. uint32
;
719 buffer
. uint64
= va_arg ( ap
, uint64_t );
720 data
= & buffer
. uint64
;
724 buffer
. percent
= va_arg ( ap
, int );
725 data
= & buffer
. percent
;
728 case _TABLE_DATA_TYPE_MAX
:
729 /* Used as end marker */
734 assert_not_reached ( "Uh? Unexpected data type." );
737 r
= table_add_cell ( t
, NULL
, type
, data
);
743 type
= va_arg ( ap
, TableDataType
);
747 void table_set_header ( Table
* t
, bool b
) {
753 void table_set_width ( Table
* t
, size_t width
) {
759 int table_set_display ( Table
* t
, size_t first_column
, ...) {
760 size_t allocated
, column
;
765 allocated
= t
-> n_display_map
;
766 column
= first_column
;
768 va_start ( ap
, first_column
);
770 assert ( column
< t
-> n_columns
);
772 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
777 t
-> display_map
[ t
-> n_display_map
++] = column
;
779 column
= va_arg ( ap
, size_t );
780 if ( column
== ( size_t ) - 1 )
789 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
790 size_t allocated
, column
;
795 allocated
= t
-> n_sort_map
;
796 column
= first_column
;
798 va_start ( ap
, first_column
);
800 assert ( column
< t
-> n_columns
);
802 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
807 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
809 column
= va_arg ( ap
, size_t );
810 if ( column
== ( size_t ) - 1 )
818 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
822 if ( a
-> type
== b
-> type
) {
824 /* We only define ordering for cells of the same data type. If cells with different data types are
825 * compared we follow the order the cells were originally added in */
830 return strcmp ( a
-> string
, b
-> string
);
833 if (! a
-> boolean
&& b
-> boolean
)
835 if ( a
-> boolean
&& ! b
-> boolean
)
839 case TABLE_TIMESTAMP
:
840 return CMP ( a
-> timestamp
, b
-> timestamp
);
843 return CMP ( a
-> timespan
, b
-> timespan
);
846 return CMP ( a
-> size
, b
-> size
);
849 return CMP ( a
-> uint32
, b
-> uint32
);
852 return CMP ( a
-> uint64
, b
-> uint64
);
855 return CMP ( a
-> percent
, b
-> percent
);
862 /* Generic fallback using the original order in which the cells where added. */
863 return CMP ( index_a
, index_b
);
866 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
873 /* Make sure the header stays at the beginning */
874 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
876 if (* a
< t
-> n_columns
)
878 if (* b
< t
-> n_columns
)
881 /* Order other lines by the sorting map */
882 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
885 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
886 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
888 r
= cell_data_compare ( d
, * a
, dd
, * b
);
890 return t
-> reverse_map
&& t
-> reverse_map
[ t
-> sort_map
[ i
]] ? - r
: r
;
893 /* Order identical lines by the order there were originally added in */
897 static const char * table_data_format ( TableData
* d
) {
911 d
-> formatted
= new ( char , strlen ( d
-> string
) + 1 );
915 for ( p
= d
-> string
, q
= d
-> formatted
; * p
; p
++, q
++)
916 * q
= ( char ) toupper (( unsigned char ) * p
);
925 return yes_no ( d
-> boolean
);
927 case TABLE_TIMESTAMP
: {
928 _cleanup_free_
char * p
;
930 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
934 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
937 d
-> formatted
= TAKE_PTR ( p
);
941 case TABLE_TIMESPAN
: {
942 _cleanup_free_
char * p
;
944 p
= new ( char , FORMAT_TIMESPAN_MAX
);
948 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timespan
, 0 ))
951 d
-> formatted
= TAKE_PTR ( p
);
956 _cleanup_free_
char * p
;
958 p
= new ( char , FORMAT_BYTES_MAX
);
962 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
965 d
-> formatted
= TAKE_PTR ( p
);
970 _cleanup_free_
char * p
;
972 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
976 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
977 d
-> formatted
= TAKE_PTR ( p
);
982 _cleanup_free_
char * p
;
984 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
988 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
989 d
-> formatted
= TAKE_PTR ( p
);
993 case TABLE_PERCENT
: {
994 _cleanup_free_
char * p
;
996 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
1000 sprintf ( p
, "%i%%" , d
-> percent
);
1001 d
-> formatted
= TAKE_PTR ( p
);
1006 assert_not_reached ( "Unexpected type?" );
1009 return d
-> formatted
;
1012 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
1016 t
= table_data_format ( d
);
1020 l
= utf8_console_width ( t
);
1021 if ( l
== ( size_t ) - 1 )
1024 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
1025 l
= d
-> maximum_width
;
1027 if ( l
< d
-> minimum_width
)
1028 l
= d
-> minimum_width
;
1034 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
1035 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
1036 _cleanup_free_
char * clickable
= NULL
;
1042 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1045 assert ( percent
<= 100 );
1047 old_length
= strlen ( str
);
1050 r
= terminal_urlify ( url
, str
, & clickable
);
1054 clickable_length
= strlen ( clickable
);
1056 clickable_length
= old_length
;
1058 /* Determine current width on screen */
1060 while ( p
< str
+ old_length
) {
1063 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
1064 p
++, w
++; /* count invalid chars as 1 */
1068 p
= utf8_next_char ( p
);
1069 w
+= unichar_iswide ( c
) ? 2 : 1 ;
1072 /* Already wider than the target, if so, don't do anything */
1073 if ( w
>= new_length
)
1074 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
1076 /* How much spaces shall we add? An how much on the left side? */
1077 space
= new_length
- w
;
1078 lspace
= space
* percent
/ 100U ;
1080 ret
= new ( char , space
+ clickable_length
+ 1 );
1084 for ( i
= 0 ; i
< lspace
; i
++)
1086 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
1087 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1090 ret
[ space
+ clickable_length
] = 0 ;
1094 int table_print ( Table
* t
, FILE * f
) {
1095 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
1096 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1098 _cleanup_free_
size_t * sorted
= NULL
;
1099 uint64_t * column_weight
, weight_sum
;
1107 /* Ensure we have no incomplete rows */
1108 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1110 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1111 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1114 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1116 sorted
= new ( size_t , n_rows
);
1120 for ( i
= 0 ; i
< n_rows
; i
++)
1121 sorted
[ i
] = i
* t
-> n_columns
;
1123 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1127 display_columns
= t
-> n_display_map
;
1129 display_columns
= t
-> n_columns
;
1131 assert ( display_columns
> 0 );
1133 minimum_width
= newa ( size_t , display_columns
);
1134 maximum_width
= newa ( size_t , display_columns
);
1135 requested_width
= newa ( size_t , display_columns
);
1136 width
= newa ( size_t , display_columns
);
1137 column_weight
= newa0 ( uint64_t , display_columns
);
1139 for ( j
= 0 ; j
< display_columns
; j
++) {
1140 minimum_width
[ j
] = 1 ;
1141 maximum_width
[ j
] = ( size_t ) - 1 ;
1142 requested_width
[ j
] = ( size_t ) - 1 ;
1145 /* First pass: determine column sizes */
1146 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1149 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1150 * hence we don't care for sorted[] during the first pass. */
1151 row
= t
-> data
+ i
* t
-> n_columns
;
1153 for ( j
= 0 ; j
< display_columns
; j
++) {
1157 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1159 r
= table_data_requested_width ( d
, & req
);
1163 /* Determine the biggest width that any cell in this column would like to have */
1164 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1165 requested_width
[ j
] < req
)
1166 requested_width
[ j
] = req
;
1168 /* Determine the minimum width any cell in this column needs */
1169 if ( minimum_width
[ j
] < d
-> minimum_width
)
1170 minimum_width
[ j
] = d
-> minimum_width
;
1172 /* Determine the maximum width any cell in this column needs */
1173 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1174 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1175 maximum_width
[ j
] > d
-> maximum_width
))
1176 maximum_width
[ j
] = d
-> maximum_width
;
1178 /* Determine the full columns weight */
1179 column_weight
[ j
] += d
-> weight
;
1183 /* One space between each column */
1184 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1186 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1188 for ( j
= 0 ; j
< display_columns
; j
++) {
1189 weight_sum
+= column_weight
[ j
];
1191 table_minimum_width
+= minimum_width
[ j
];
1193 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1194 table_maximum_width
= ( size_t ) - 1 ;
1196 table_maximum_width
+= maximum_width
[ j
];
1198 table_requested_width
+= requested_width
[ j
];
1201 /* Calculate effective table width */
1202 if ( t
-> width
== ( size_t ) - 1 )
1203 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1205 table_effective_width
= t
-> width
;
1207 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1208 table_effective_width
= table_maximum_width
;
1210 if ( table_effective_width
< table_minimum_width
)
1211 table_effective_width
= table_minimum_width
;
1213 if ( table_effective_width
>= table_requested_width
) {
1216 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1217 * each column with what it asked for and the distribute the rest. */
1219 extra
= table_effective_width
- table_requested_width
;
1221 for ( j
= 0 ; j
< display_columns
; j
++) {
1224 if ( weight_sum
== 0 )
1225 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1227 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1229 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1230 width
[ j
] = maximum_width
[ j
];
1232 if ( width
[ j
] < minimum_width
[ j
])
1233 width
[ j
] = minimum_width
[ j
];
1235 assert ( width
[ j
] >= requested_width
[ j
]);
1236 delta
= width
[ j
] - requested_width
[ j
];
1238 /* Subtract what we just added from the rest */
1244 assert ( weight_sum
>= column_weight
[ j
]);
1245 weight_sum
-= column_weight
[ j
];
1249 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1250 * with the minimum they need, and then distribute anything left. */
1251 bool finalize
= false ;
1254 extra
= table_effective_width
- table_minimum_width
;
1256 for ( j
= 0 ; j
< display_columns
; j
++)
1257 width
[ j
] = ( size_t ) - 1 ;
1260 bool restart
= false ;
1262 for ( j
= 0 ; j
< display_columns
; j
++) {
1265 /* Did this column already get something assigned? If so, let's skip to the next */
1266 if ( width
[ j
] != ( size_t ) - 1 )
1269 if ( weight_sum
== 0 )
1270 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1272 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1274 if ( w
>= requested_width
[ j
]) {
1275 /* Never give more than requested. If we hit a column like this, there's more
1276 * space to allocate to other columns which means we need to restart the
1277 * iteration. However, if we hit a column like this, let's assign it the space
1278 * it wanted for good early.*/
1280 w
= requested_width
[ j
];
1283 } else if (! finalize
)
1288 assert ( w
>= minimum_width
[ j
]);
1289 delta
= w
- minimum_width
[ j
];
1291 assert ( delta
<= extra
);
1294 assert ( weight_sum
>= column_weight
[ j
]);
1295 weight_sum
-= column_weight
[ j
];
1297 if ( restart
&& ! finalize
)
1309 /* Second pass: show output */
1310 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1314 row
= t
-> data
+ sorted
[ i
];
1316 row
= t
-> data
+ i
* t
-> n_columns
;
1318 for ( j
= 0 ; j
< display_columns
; j
++) {
1319 _cleanup_free_
char * buffer
= NULL
;
1324 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1326 field
= table_data_format ( d
);
1330 l
= utf8_console_width ( field
);
1332 /* Field is wider than allocated space. Let's ellipsize */
1334 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1340 } else if ( l
< width
[ j
]) {
1341 /* Field is shorter than allocated space. Let's align with spaces */
1343 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1350 if ( l
>= width
[ j
] && d
-> url
) {
1351 _cleanup_free_
char * clickable
= NULL
;
1353 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1357 free_and_replace ( buffer
, clickable
);
1361 if ( row
== t
-> data
) /* underline header line fully, including the column separator */
1362 fputs ( ansi_underline (), f
);
1365 fputc ( ' ' , f
); /* column separator */
1367 if ( d
-> color
&& colors_enabled ()) {
1368 if ( row
== t
-> data
) /* first undo header underliner */
1369 fputs ( ANSI_NORMAL
, f
);
1376 if ( colors_enabled () && ( d
-> color
|| row
== t
-> data
))
1377 fputs ( ANSI_NORMAL
, f
);
1383 return fflush_and_check ( f
);
1386 int table_format ( Table
* t
, char ** ret
) {
1387 _cleanup_fclose_
FILE * f
= NULL
;
1392 f
= open_memstream_unlocked (& buf
, & sz
);
1396 r
= table_print ( t
, f
);
1407 size_t table_get_rows ( Table
* t
) {
1411 assert ( t
-> n_columns
> 0 );
1412 return t
-> n_cells
/ t
-> n_columns
;
1415 size_t table_get_columns ( Table
* t
) {
1419 assert ( t
-> n_columns
> 0 );
1420 return t
-> n_columns
;
1423 int table_set_reverse ( Table
* t
, size_t column
, bool b
) {
1425 assert ( column
< t
-> n_columns
);
1427 if (! t
-> reverse_map
) {
1431 t
-> reverse_map
= new0 ( bool , t
-> n_columns
);
1432 if (! t
-> reverse_map
)
1436 t
-> reverse_map
[ column
] = b
;
1440 TableCell
* table_get_cell ( Table
* t
, size_t row
, size_t column
) {
1445 if ( column
>= t
-> n_columns
)
1448 i
= row
* t
-> n_columns
+ column
;
1449 if ( i
>= t
-> n_cells
)
1452 return TABLE_INDEX_TO_CELL ( i
);
1455 const void * table_get ( Table
* t
, TableCell
* cell
) {
1460 d
= table_get_data ( t
, cell
);
1467 const void * table_get_at ( Table
* t
, size_t row
, size_t column
) {
1470 cell
= table_get_cell ( t
, row
, column
);
1474 return table_get ( t
, cell
);
1477 static int table_data_to_json ( TableData
* d
, JsonVariant
** ret
) {
1482 return json_variant_new_null ( ret
);
1485 return json_variant_new_string ( ret
, d
-> string
);
1488 return json_variant_new_boolean ( ret
, d
-> boolean
);
1490 case TABLE_TIMESTAMP
:
1491 if ( d
-> timestamp
== USEC_INFINITY
)
1492 return json_variant_new_null ( ret
);
1494 return json_variant_new_unsigned ( ret
, d
-> timestamp
);
1496 case TABLE_TIMESPAN
:
1497 if ( d
-> timespan
== USEC_INFINITY
)
1498 return json_variant_new_null ( ret
);
1500 return json_variant_new_unsigned ( ret
, d
-> timespan
);
1503 if ( d
-> size
== ( size_t ) - 1 )
1504 return json_variant_new_null ( ret
);
1506 return json_variant_new_unsigned ( ret
, d
-> size
);
1509 return json_variant_new_unsigned ( ret
, d
-> uint32
);
1512 return json_variant_new_unsigned ( ret
, d
-> uint64
);
1515 return json_variant_new_integer ( ret
, d
-> percent
);
1522 int table_to_json ( Table
* t
, JsonVariant
** ret
) {
1523 JsonVariant
** rows
= NULL
, ** elements
= NULL
;
1524 _cleanup_free_
size_t * sorted
= NULL
;
1525 size_t n_rows
, i
, j
, display_columns
;
1530 /* Ensure we have no incomplete rows */
1531 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1533 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1534 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1537 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1539 sorted
= new ( size_t , n_rows
);
1545 for ( i
= 0 ; i
< n_rows
; i
++)
1546 sorted
[ i
] = i
* t
-> n_columns
;
1548 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1552 display_columns
= t
-> n_display_map
;
1554 display_columns
= t
-> n_columns
;
1555 assert ( display_columns
> 0 );
1557 elements
= new0 ( JsonVariant
*, display_columns
* 2 );
1563 for ( j
= 0 ; j
< display_columns
; j
++) {
1566 assert_se ( d
= t
-> data
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1568 r
= table_data_to_json ( d
, elements
+ j
* 2 );
1573 rows
= new0 ( JsonVariant
*, n_rows
- 1 );
1579 for ( i
= 1 ; i
< n_rows
; i
++) {
1583 row
= t
-> data
+ sorted
[ i
];
1585 row
= t
-> data
+ i
* t
-> n_columns
;
1587 for ( j
= 0 ; j
< display_columns
; j
++) {
1591 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1594 elements
[ k
] = json_variant_unref ( elements
[ k
]);
1596 r
= table_data_to_json ( d
, elements
+ k
);
1601 r
= json_variant_new_object ( rows
+ i
- 1 , elements
, display_columns
* 2 );
1606 r
= json_variant_new_array ( ret
, rows
, n_rows
- 1 );
1610 json_variant_unref_many ( rows
, n_rows
- 1 );
1615 json_variant_unref_many ( elements
, display_columns
* 2 );
1622 int table_print_json ( Table
* t
, FILE * f
, JsonFormatFlags flags
) {
1623 _cleanup_ ( json_variant_unrefp
) JsonVariant
* v
= NULL
;
1631 r
= table_to_json ( t
, & v
);
1635 json_variant_dump ( v
, flags
, f
, NULL
);
1637 return fflush_and_check ( f
);