]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "alloc-util.h"
9 #include "format-table.h"
11 #include "memory-util.h"
13 #include "parse-util.h"
14 #include "pretty-print.h"
15 #include "sort-util.h"
16 #include "string-util.h"
17 #include "terminal-util.h"
18 #include "time-util.h"
22 #define DEFAULT_WEIGHT 100
25 A few notes on implementation details:
27 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
28 table. It can be easily converted to an index number and back.
30 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
31 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
32 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
33 outside only sees Table and TableCell.
35 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
38 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
39 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
40 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
41 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
43 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
44 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
45 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
46 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
49 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
50 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
51 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
54 typedef struct TableData
{
58 size_t minimum_width
; /* minimum width for the column */
59 size_t maximum_width
; /* maximum width for the column */
60 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
61 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
62 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
64 bool uppercase
; /* Uppercase string on display */
66 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 */
67 char * url
; /* A URL to use for a clickable hyperlink */
68 char * formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
71 uint8_t data
[ 0 ]; /* data is generic array */
79 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
80 /* … add more here as we start supporting more cell data types … */
84 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
89 i
= PTR_TO_SIZE ( cell
);
95 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
96 assert ( index
!= ( size_t ) - 1 );
97 return SIZE_TO_PTR ( index
+ 1 );
104 bool header
; /* Whether to show the header row? */
105 size_t width
; /* If != (size_t) -1 the width to format this table in */
110 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 */
111 size_t n_display_map
;
113 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
119 Table
* table_new_raw ( size_t n_columns
) {
120 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
122 assert ( n_columns
> 0 );
128 * t
= ( struct Table
) {
129 . n_columns
= n_columns
,
131 . width
= ( size_t ) - 1 ,
137 Table
* table_new_internal ( const char * first_header
, ...) {
138 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
139 size_t n_columns
= 1 ;
144 assert ( first_header
);
146 va_start ( ap
, first_header
);
148 h
= va_arg ( ap
, const char *);
156 t
= table_new_raw ( n_columns
);
160 va_start ( ap
, first_header
);
161 for ( h
= first_header
; h
; h
= va_arg ( ap
, const char *)) {
164 r
= table_add_cell ( t
, & cell
, TABLE_STRING
, h
);
170 /* Make the table header uppercase */
171 r
= table_set_uppercase ( t
, cell
, true );
179 assert ( t
-> n_columns
== t
-> n_cells
);
183 static TableData
* table_data_free ( TableData
* d
) {
192 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
193 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
195 Table
* table_unref ( Table
* t
) {
201 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
202 table_data_unref ( t
-> data
[ i
]);
205 free ( t
-> display_map
);
207 free ( t
-> reverse_map
);
212 static size_t table_data_size ( TableDataType type
, const void * data
) {
220 return strlen ( data
) + 1 ;
225 case TABLE_TIMESTAMP
:
227 return sizeof ( usec_t
);
231 return sizeof ( uint64_t );
234 return sizeof ( uint32_t );
240 assert_not_reached ( "Uh? Unexpected cell type" );
244 static bool table_data_matches (
248 size_t minimum_width
,
249 size_t maximum_width
,
251 unsigned align_percent
,
252 unsigned ellipsize_percent
) {
260 if ( d
-> minimum_width
!= minimum_width
)
263 if ( d
-> maximum_width
!= maximum_width
)
266 if ( d
-> weight
!= weight
)
269 if ( d
-> align_percent
!= align_percent
)
272 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
275 /* If a color/url/uppercase flag is set, refuse to merge */
283 k
= table_data_size ( type
, data
);
284 l
= table_data_size ( d
-> type
, d
-> data
);
289 return memcmp_safe ( data
, d
-> data
, l
) == 0 ;
292 static TableData
* table_data_new (
295 size_t minimum_width
,
296 size_t maximum_width
,
298 unsigned align_percent
,
299 unsigned ellipsize_percent
) {
304 data_size
= table_data_size ( type
, data
);
306 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
312 d
-> minimum_width
= minimum_width
;
313 d
-> maximum_width
= maximum_width
;
315 d
-> align_percent
= align_percent
;
316 d
-> ellipsize_percent
= ellipsize_percent
;
317 memcpy_safe ( d
-> data
, data
, data_size
);
322 int table_add_cell_full (
324 TableCell
** ret_cell
,
327 size_t minimum_width
,
328 size_t maximum_width
,
330 unsigned align_percent
,
331 unsigned ellipsize_percent
) {
333 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
338 assert ( type
< _TABLE_DATA_TYPE_MAX
);
340 /* Determine the cell adjacent to the current one, but one row up */
341 if ( t
-> n_cells
>= t
-> n_columns
)
342 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
346 /* If formatting parameters are left unspecified, copy from the previous row */
347 if ( minimum_width
== ( size_t ) - 1 )
348 minimum_width
= p
? p
-> minimum_width
: 1 ;
350 if ( weight
== ( unsigned ) - 1 )
351 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
353 if ( align_percent
== ( unsigned ) - 1 )
354 align_percent
= p
? p
-> align_percent
: 0 ;
356 if ( ellipsize_percent
== ( unsigned ) - 1 )
357 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
359 assert ( align_percent
<= 100 );
360 assert ( ellipsize_percent
<= 100 );
362 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
363 * formatting. Let's see if we can reuse the cell data and ref it once more. */
365 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
366 d
= table_data_ref ( p
);
368 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
373 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
377 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
379 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
384 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
389 /* Add the data of the specified cell a second time as a new cell to the end. */
391 i
= TABLE_CELL_TO_INDEX ( cell
);
395 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
398 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
402 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
403 _cleanup_free_
char * curl
= NULL
;
409 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
410 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
412 i
= TABLE_CELL_TO_INDEX ( cell
);
416 assert_se ( od
= t
-> data
[ i
]);
420 assert ( od
-> n_ref
> 1 );
423 curl
= strdup ( od
-> url
);
435 od
-> ellipsize_percent
);
439 nd
-> color
= od
-> color
;
440 nd
-> url
= TAKE_PTR ( curl
);
441 nd
-> uppercase
= od
-> uppercase
;
443 table_data_unref ( od
);
446 assert ( nd
-> n_ref
== 1 );
451 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
457 /* Get the data object of the specified cell, or NULL if it doesn't exist */
459 i
= TABLE_CELL_TO_INDEX ( cell
);
464 assert ( t
-> data
[ i
]-> n_ref
> 0 );
469 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
475 if ( minimum_width
== ( size_t ) - 1 )
478 r
= table_dedup_cell ( t
, cell
);
482 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
486 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
492 r
= table_dedup_cell ( t
, cell
);
496 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
500 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
506 if ( weight
== ( unsigned ) - 1 )
507 weight
= DEFAULT_WEIGHT
;
509 r
= table_dedup_cell ( t
, cell
);
513 table_get_data ( t
, cell
)-> weight
= weight
;
517 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
523 if ( percent
== ( unsigned ) - 1 )
526 assert ( percent
<= 100 );
528 r
= table_dedup_cell ( t
, cell
);
532 table_get_data ( t
, cell
)-> align_percent
= percent
;
536 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
542 if ( percent
== ( unsigned ) - 1 )
545 assert ( percent
<= 100 );
547 r
= table_dedup_cell ( t
, cell
);
551 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
555 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
561 r
= table_dedup_cell ( t
, cell
);
565 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
569 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
570 _cleanup_free_
char * copy
= NULL
;
582 r
= table_dedup_cell ( t
, cell
);
586 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
589 int table_set_uppercase ( Table
* t
, TableCell
* cell
, bool b
) {
596 r
= table_dedup_cell ( t
, cell
);
600 assert_se ( d
= table_get_data ( t
, cell
));
602 if ( d
-> uppercase
== b
)
605 d
-> formatted
= mfree ( d
-> formatted
);
610 int table_update ( Table
* t
, TableCell
* cell
, TableDataType type
, const void * data
) {
611 _cleanup_free_
char * curl
= NULL
;
618 i
= TABLE_CELL_TO_INDEX ( cell
);
622 assert_se ( od
= t
-> data
[ i
]);
625 curl
= strdup ( od
-> url
);
637 od
-> ellipsize_percent
);
641 nd
-> color
= od
-> color
;
642 nd
-> url
= TAKE_PTR ( curl
);
643 nd
-> uppercase
= od
-> uppercase
;
645 table_data_unref ( od
);
651 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
657 assert ( first_type
>= 0 );
658 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
662 va_start ( ap
, first_type
);
681 data
= va_arg ( ap
, const char *);
685 buffer
. b
= va_arg ( ap
, int );
689 case TABLE_TIMESTAMP
:
691 buffer
. usec
= va_arg ( ap
, usec_t
);
696 buffer
. size
= va_arg ( ap
, uint64_t );
701 buffer
. uint32
= va_arg ( ap
, uint32_t );
702 data
= & buffer
. uint32
;
706 buffer
. uint64
= va_arg ( ap
, uint64_t );
707 data
= & buffer
. uint64
;
711 buffer
. percent
= va_arg ( ap
, int );
712 data
= & buffer
. percent
;
715 case _TABLE_DATA_TYPE_MAX
:
716 /* Used as end marker */
721 assert_not_reached ( "Uh? Unexpected data type." );
724 r
= table_add_cell ( t
, NULL
, type
, data
);
730 type
= va_arg ( ap
, TableDataType
);
734 void table_set_header ( Table
* t
, bool b
) {
740 void table_set_width ( Table
* t
, size_t width
) {
746 int table_set_display ( Table
* t
, size_t first_column
, ...) {
747 size_t allocated
, column
;
752 allocated
= t
-> n_display_map
;
753 column
= first_column
;
755 va_start ( ap
, first_column
);
757 assert ( column
< t
-> n_columns
);
759 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
764 t
-> display_map
[ t
-> n_display_map
++] = column
;
766 column
= va_arg ( ap
, size_t );
767 if ( column
== ( size_t ) - 1 )
776 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
777 size_t allocated
, column
;
782 allocated
= t
-> n_sort_map
;
783 column
= first_column
;
785 va_start ( ap
, first_column
);
787 assert ( column
< t
-> n_columns
);
789 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
794 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
796 column
= va_arg ( ap
, size_t );
797 if ( column
== ( size_t ) - 1 )
805 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
809 if ( a
-> type
== b
-> type
) {
811 /* We only define ordering for cells of the same data type. If cells with different data types are
812 * compared we follow the order the cells were originally added in */
817 return strcmp ( a
-> string
, b
-> string
);
820 if (! a
-> boolean
&& b
-> boolean
)
822 if ( a
-> boolean
&& ! b
-> boolean
)
826 case TABLE_TIMESTAMP
:
827 return CMP ( a
-> timestamp
, b
-> timestamp
);
830 return CMP ( a
-> timespan
, b
-> timespan
);
833 return CMP ( a
-> size
, b
-> size
);
836 return CMP ( a
-> uint32
, b
-> uint32
);
839 return CMP ( a
-> uint64
, b
-> uint64
);
842 return CMP ( a
-> percent
, b
-> percent
);
849 /* Generic fallback using the orginal order in which the cells where added. */
850 return CMP ( index_a
, index_b
);
853 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
860 /* Make sure the header stays at the beginning */
861 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
863 if (* a
< t
-> n_columns
)
865 if (* b
< t
-> n_columns
)
868 /* Order other lines by the sorting map */
869 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
872 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
873 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
875 r
= cell_data_compare ( d
, * a
, dd
, * b
);
877 return t
-> reverse_map
&& t
-> reverse_map
[ t
-> sort_map
[ i
]] ? - r
: r
;
880 /* Order identical lines by the order there were originally added in */
884 static const char * table_data_format ( TableData
* d
) {
898 d
-> formatted
= new ( char , strlen ( d
-> string
) + 1 );
902 for ( p
= d
-> string
, q
= d
-> formatted
; * p
; p
++, q
++)
903 * q
= ( char ) toupper (( unsigned char ) * p
);
912 return yes_no ( d
-> boolean
);
914 case TABLE_TIMESTAMP
: {
915 _cleanup_free_
char * p
;
917 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
921 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
924 d
-> formatted
= TAKE_PTR ( p
);
928 case TABLE_TIMESPAN
: {
929 _cleanup_free_
char * p
;
931 p
= new ( char , FORMAT_TIMESPAN_MAX
);
935 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timespan
, 0 ))
938 d
-> formatted
= TAKE_PTR ( p
);
943 _cleanup_free_
char * p
;
945 p
= new ( char , FORMAT_BYTES_MAX
);
949 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
952 d
-> formatted
= TAKE_PTR ( p
);
957 _cleanup_free_
char * p
;
959 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
963 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
964 d
-> formatted
= TAKE_PTR ( p
);
969 _cleanup_free_
char * p
;
971 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
975 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
976 d
-> formatted
= TAKE_PTR ( p
);
980 case TABLE_PERCENT
: {
981 _cleanup_free_
char * p
;
983 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
987 sprintf ( p
, "%i%%" , d
-> percent
);
988 d
-> formatted
= TAKE_PTR ( p
);
993 assert_not_reached ( "Unexpected type?" );
999 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
1003 t
= table_data_format ( d
);
1007 l
= utf8_console_width ( t
);
1008 if ( l
== ( size_t ) - 1 )
1011 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
1012 l
= d
-> maximum_width
;
1014 if ( l
< d
-> minimum_width
)
1015 l
= d
-> minimum_width
;
1021 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
1022 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
1023 _cleanup_free_
char * clickable
= NULL
;
1029 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1032 assert ( percent
<= 100 );
1034 old_length
= strlen ( str
);
1037 r
= terminal_urlify ( url
, str
, & clickable
);
1041 clickable_length
= strlen ( clickable
);
1043 clickable_length
= old_length
;
1045 /* Determine current width on screen */
1047 while ( p
< str
+ old_length
) {
1050 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
1051 p
++, w
++; /* count invalid chars as 1 */
1055 p
= utf8_next_char ( p
);
1056 w
+= unichar_iswide ( c
) ? 2 : 1 ;
1059 /* Already wider than the target, if so, don't do anything */
1060 if ( w
>= new_length
)
1061 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
1063 /* How much spaces shall we add? An how much on the left side? */
1064 space
= new_length
- w
;
1065 lspace
= space
* percent
/ 100U ;
1067 ret
= new ( char , space
+ clickable_length
+ 1 );
1071 for ( i
= 0 ; i
< lspace
; i
++)
1073 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
1074 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1077 ret
[ space
+ clickable_length
] = 0 ;
1081 int table_print ( Table
* t
, FILE * f
) {
1082 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
1083 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1085 _cleanup_free_
size_t * sorted
= NULL
;
1086 uint64_t * column_weight
, weight_sum
;
1094 /* Ensure we have no incomplete rows */
1095 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1097 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1098 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1101 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1103 sorted
= new ( size_t , n_rows
);
1107 for ( i
= 0 ; i
< n_rows
; i
++)
1108 sorted
[ i
] = i
* t
-> n_columns
;
1110 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1114 display_columns
= t
-> n_display_map
;
1116 display_columns
= t
-> n_columns
;
1118 assert ( display_columns
> 0 );
1120 minimum_width
= newa ( size_t , display_columns
);
1121 maximum_width
= newa ( size_t , display_columns
);
1122 requested_width
= newa ( size_t , display_columns
);
1123 width
= newa ( size_t , display_columns
);
1124 column_weight
= newa0 ( uint64_t , display_columns
);
1126 for ( j
= 0 ; j
< display_columns
; j
++) {
1127 minimum_width
[ j
] = 1 ;
1128 maximum_width
[ j
] = ( size_t ) - 1 ;
1129 requested_width
[ j
] = ( size_t ) - 1 ;
1132 /* First pass: determine column sizes */
1133 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1136 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1137 * hence we don't care for sorted[] during the first pass. */
1138 row
= t
-> data
+ i
* t
-> n_columns
;
1140 for ( j
= 0 ; j
< display_columns
; j
++) {
1144 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1146 r
= table_data_requested_width ( d
, & req
);
1150 /* Determine the biggest width that any cell in this column would like to have */
1151 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1152 requested_width
[ j
] < req
)
1153 requested_width
[ j
] = req
;
1155 /* Determine the minimum width any cell in this column needs */
1156 if ( minimum_width
[ j
] < d
-> minimum_width
)
1157 minimum_width
[ j
] = d
-> minimum_width
;
1159 /* Determine the maximum width any cell in this column needs */
1160 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1161 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1162 maximum_width
[ j
] > d
-> maximum_width
))
1163 maximum_width
[ j
] = d
-> maximum_width
;
1165 /* Determine the full columns weight */
1166 column_weight
[ j
] += d
-> weight
;
1170 /* One space between each column */
1171 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1173 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1175 for ( j
= 0 ; j
< display_columns
; j
++) {
1176 weight_sum
+= column_weight
[ j
];
1178 table_minimum_width
+= minimum_width
[ j
];
1180 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1181 table_maximum_width
= ( size_t ) - 1 ;
1183 table_maximum_width
+= maximum_width
[ j
];
1185 table_requested_width
+= requested_width
[ j
];
1188 /* Calculate effective table width */
1189 if ( t
-> width
== ( size_t ) - 1 )
1190 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1192 table_effective_width
= t
-> width
;
1194 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1195 table_effective_width
= table_maximum_width
;
1197 if ( table_effective_width
< table_minimum_width
)
1198 table_effective_width
= table_minimum_width
;
1200 if ( table_effective_width
>= table_requested_width
) {
1203 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1204 * each column with what it asked for and the distribute the rest. */
1206 extra
= table_effective_width
- table_requested_width
;
1208 for ( j
= 0 ; j
< display_columns
; j
++) {
1211 if ( weight_sum
== 0 )
1212 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1214 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1216 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1217 width
[ j
] = maximum_width
[ j
];
1219 if ( width
[ j
] < minimum_width
[ j
])
1220 width
[ j
] = minimum_width
[ j
];
1222 assert ( width
[ j
] >= requested_width
[ j
]);
1223 delta
= width
[ j
] - requested_width
[ j
];
1225 /* Subtract what we just added from the rest */
1231 assert ( weight_sum
>= column_weight
[ j
]);
1232 weight_sum
-= column_weight
[ j
];
1236 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1237 * with the minimum they need, and then distribute anything left. */
1238 bool finalize
= false ;
1241 extra
= table_effective_width
- table_minimum_width
;
1243 for ( j
= 0 ; j
< display_columns
; j
++)
1244 width
[ j
] = ( size_t ) - 1 ;
1247 bool restart
= false ;
1249 for ( j
= 0 ; j
< display_columns
; j
++) {
1252 /* Did this column already get something assigned? If so, let's skip to the next */
1253 if ( width
[ j
] != ( size_t ) - 1 )
1256 if ( weight_sum
== 0 )
1257 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1259 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1261 if ( w
>= requested_width
[ j
]) {
1262 /* Never give more than requested. If we hit a column like this, there's more
1263 * space to allocate to other columns which means we need to restart the
1264 * iteration. However, if we hit a column like this, let's assign it the space
1265 * it wanted for good early.*/
1267 w
= requested_width
[ j
];
1270 } else if (! finalize
)
1275 assert ( w
>= minimum_width
[ j
]);
1276 delta
= w
- minimum_width
[ j
];
1278 assert ( delta
<= extra
);
1281 assert ( weight_sum
>= column_weight
[ j
]);
1282 weight_sum
-= column_weight
[ j
];
1284 if ( restart
&& ! finalize
)
1296 /* Second pass: show output */
1297 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1301 row
= t
-> data
+ sorted
[ i
];
1303 row
= t
-> data
+ i
* t
-> n_columns
;
1305 for ( j
= 0 ; j
< display_columns
; j
++) {
1306 _cleanup_free_
char * buffer
= NULL
;
1311 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1313 field
= table_data_format ( d
);
1317 l
= utf8_console_width ( field
);
1319 /* Field is wider than allocated space. Let's ellipsize */
1321 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1327 } else if ( l
< width
[ j
]) {
1328 /* Field is shorter than allocated space. Let's align with spaces */
1330 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1337 if ( l
>= width
[ j
] && d
-> url
) {
1338 _cleanup_free_
char * clickable
= NULL
;
1340 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1344 free_and_replace ( buffer
, clickable
);
1348 if ( row
== t
-> data
) /* underline header line fully, including the column separator */
1349 fputs ( ansi_underline (), f
);
1352 fputc ( ' ' , f
); /* column separator */
1354 if ( d
-> color
&& colors_enabled ()) {
1355 if ( row
== t
-> data
) /* first undo header underliner */
1356 fputs ( ANSI_NORMAL
, f
);
1363 if ( colors_enabled () && ( d
-> color
|| row
== t
-> data
))
1364 fputs ( ANSI_NORMAL
, f
);
1370 return fflush_and_check ( f
);
1373 int table_format ( Table
* t
, char ** ret
) {
1374 _cleanup_fclose_
FILE * f
= NULL
;
1379 f
= open_memstream (& buf
, & sz
);
1383 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1385 r
= table_print ( t
, f
);
1396 size_t table_get_rows ( Table
* t
) {
1400 assert ( t
-> n_columns
> 0 );
1401 return t
-> n_cells
/ t
-> n_columns
;
1404 size_t table_get_columns ( Table
* t
) {
1408 assert ( t
-> n_columns
> 0 );
1409 return t
-> n_columns
;
1412 int table_set_reverse ( Table
* t
, size_t column
, bool b
) {
1414 assert ( column
< t
-> n_columns
);
1416 if (! t
-> reverse_map
) {
1420 t
-> reverse_map
= new0 ( bool , t
-> n_columns
);
1421 if (! t
-> reverse_map
)
1425 t
-> reverse_map
[ column
] = b
;
1429 TableCell
* table_get_cell ( Table
* t
, size_t row
, size_t column
) {
1434 if ( column
>= t
-> n_columns
)
1437 i
= row
* t
-> n_columns
+ column
;
1438 if ( i
>= t
-> n_cells
)
1441 return TABLE_INDEX_TO_CELL ( i
);
1444 const void * table_get ( Table
* t
, TableCell
* cell
) {
1449 d
= table_get_data ( t
, cell
);
1456 const void * table_get_at ( Table
* t
, size_t row
, size_t column
) {
1459 cell
= table_get_cell ( t
, row
, column
);
1463 return table_get ( t
, cell
);
1466 static int table_data_to_json ( TableData
* d
, JsonVariant
** ret
) {
1471 return json_variant_new_null ( ret
);
1474 return json_variant_new_string ( ret
, d
-> string
);
1477 return json_variant_new_boolean ( ret
, d
-> boolean
);
1479 case TABLE_TIMESTAMP
:
1480 if ( d
-> timestamp
== USEC_INFINITY
)
1481 return json_variant_new_null ( ret
);
1483 return json_variant_new_unsigned ( ret
, d
-> timestamp
);
1485 case TABLE_TIMESPAN
:
1486 if ( d
-> timespan
== USEC_INFINITY
)
1487 return json_variant_new_null ( ret
);
1489 return json_variant_new_unsigned ( ret
, d
-> timespan
);
1492 if ( d
-> size
== ( size_t ) - 1 )
1493 return json_variant_new_null ( ret
);
1495 return json_variant_new_unsigned ( ret
, d
-> size
);
1498 return json_variant_new_unsigned ( ret
, d
-> uint32
);
1501 return json_variant_new_unsigned ( ret
, d
-> uint64
);
1504 return json_variant_new_integer ( ret
, d
-> percent
);
1511 int table_to_json ( Table
* t
, JsonVariant
** ret
) {
1512 JsonVariant
** rows
= NULL
, ** elements
= NULL
;
1513 _cleanup_free_
size_t * sorted
= NULL
;
1514 size_t n_rows
, i
, j
, display_columns
;
1519 /* Ensure we have no incomplete rows */
1520 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1522 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1523 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1526 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1528 sorted
= new ( size_t , n_rows
);
1534 for ( i
= 0 ; i
< n_rows
; i
++)
1535 sorted
[ i
] = i
* t
-> n_columns
;
1537 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1541 display_columns
= t
-> n_display_map
;
1543 display_columns
= t
-> n_columns
;
1544 assert ( display_columns
> 0 );
1546 elements
= new0 ( JsonVariant
*, display_columns
* 2 );
1552 for ( j
= 0 ; j
< display_columns
; j
++) {
1555 assert_se ( d
= t
-> data
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1557 r
= table_data_to_json ( d
, elements
+ j
* 2 );
1562 rows
= new0 ( JsonVariant
*, n_rows
- 1 );
1568 for ( i
= 1 ; i
< n_rows
; i
++) {
1572 row
= t
-> data
+ sorted
[ i
];
1574 row
= t
-> data
+ i
* t
-> n_columns
;
1576 for ( j
= 0 ; j
< display_columns
; j
++) {
1580 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1583 elements
[ k
] = json_variant_unref ( elements
[ k
]);
1585 r
= table_data_to_json ( d
, elements
+ k
);
1590 r
= json_variant_new_object ( rows
+ i
- 1 , elements
, display_columns
* 2 );
1595 r
= json_variant_new_array ( ret
, rows
, n_rows
- 1 );
1599 json_variant_unref_many ( rows
, n_rows
- 1 );
1604 json_variant_unref_many ( elements
, display_columns
* 2 );
1611 int table_print_json ( Table
* t
, FILE * f
, JsonFormatFlags flags
) {
1612 _cleanup_ ( json_variant_unrefp
) JsonVariant
* v
= NULL
;
1620 r
= table_to_json ( t
, & v
);
1624 json_variant_dump ( v
, flags
, f
, NULL
);
1626 return fflush_and_check ( f
);