]>
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"
12 #include "parse-util.h"
13 #include "pretty-print.h"
14 #include "string-util.h"
15 #include "terminal-util.h"
16 #include "time-util.h"
20 #define DEFAULT_WEIGHT 100
23 A few notes on implementation details:
25 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
26 table. It can be easily converted to an index number and back.
28 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
29 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
30 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
31 outside only sees Table and TableCell.
33 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
36 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
37 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
38 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
39 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
41 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
42 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
43 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
44 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
47 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
48 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
49 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
52 typedef struct TableData
{
56 size_t minimum_width
; /* minimum width for the column */
57 size_t maximum_width
; /* maximum width for the column */
58 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
59 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
60 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
62 bool uppercase
; /* Uppercase string on display */
64 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 */
65 char * url
; /* A URL to use for a clickable hyperlink */
66 char * formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
69 uint8_t data
[ 0 ]; /* data is generic array */
77 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
78 /* … add more here as we start supporting more cell data types … */
82 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
87 i
= PTR_TO_SIZE ( cell
);
93 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
94 assert ( index
!= ( size_t ) - 1 );
95 return SIZE_TO_PTR ( index
+ 1 );
102 bool header
; /* Whether to show the header row? */
103 size_t width
; /* If != (size_t) -1 the width to format this table in */
108 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 */
109 size_t n_display_map
;
111 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
117 Table
* table_new_raw ( size_t n_columns
) {
118 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
120 assert ( n_columns
> 0 );
126 * t
= ( struct Table
) {
127 . n_columns
= n_columns
,
129 . width
= ( size_t ) - 1 ,
135 Table
* table_new_internal ( const char * first_header
, ...) {
136 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
137 size_t n_columns
= 1 ;
142 assert ( first_header
);
144 va_start ( ap
, first_header
);
146 h
= va_arg ( ap
, const char *);
154 t
= table_new_raw ( n_columns
);
158 va_start ( ap
, first_header
);
159 for ( h
= first_header
; h
; h
= va_arg ( ap
, const char *)) {
162 r
= table_add_cell ( t
, & cell
, TABLE_STRING
, h
);
168 /* Make the table header uppercase */
169 r
= table_set_uppercase ( t
, cell
, true );
177 assert ( t
-> n_columns
== t
-> n_cells
);
181 static TableData
* table_data_free ( TableData
* d
) {
190 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
191 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
193 Table
* table_unref ( Table
* t
) {
199 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
200 table_data_unref ( t
-> data
[ i
]);
203 free ( t
-> display_map
);
205 free ( t
-> reverse_map
);
210 static size_t table_data_size ( TableDataType type
, const void * data
) {
218 return strlen ( data
) + 1 ;
223 case TABLE_TIMESTAMP
:
225 return sizeof ( usec_t
);
229 return sizeof ( uint64_t );
232 return sizeof ( uint32_t );
238 assert_not_reached ( "Uh? Unexpected cell type" );
242 static bool table_data_matches (
246 size_t minimum_width
,
247 size_t maximum_width
,
249 unsigned align_percent
,
250 unsigned ellipsize_percent
) {
258 if ( d
-> minimum_width
!= minimum_width
)
261 if ( d
-> maximum_width
!= maximum_width
)
264 if ( d
-> weight
!= weight
)
267 if ( d
-> align_percent
!= align_percent
)
270 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
273 /* If a color/url/uppercase flag is set, refuse to merge */
281 k
= table_data_size ( type
, data
);
282 l
= table_data_size ( d
-> type
, d
-> data
);
287 return memcmp_safe ( data
, d
-> data
, l
) == 0 ;
290 static TableData
* table_data_new (
293 size_t minimum_width
,
294 size_t maximum_width
,
296 unsigned align_percent
,
297 unsigned ellipsize_percent
) {
302 data_size
= table_data_size ( type
, data
);
304 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
310 d
-> minimum_width
= minimum_width
;
311 d
-> maximum_width
= maximum_width
;
313 d
-> align_percent
= align_percent
;
314 d
-> ellipsize_percent
= ellipsize_percent
;
315 memcpy_safe ( d
-> data
, data
, data_size
);
320 int table_add_cell_full (
322 TableCell
** ret_cell
,
325 size_t minimum_width
,
326 size_t maximum_width
,
328 unsigned align_percent
,
329 unsigned ellipsize_percent
) {
331 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
336 assert ( type
< _TABLE_DATA_TYPE_MAX
);
338 /* Determine the cell adjacent to the current one, but one row up */
339 if ( t
-> n_cells
>= t
-> n_columns
)
340 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
344 /* If formatting parameters are left unspecified, copy from the previous row */
345 if ( minimum_width
== ( size_t ) - 1 )
346 minimum_width
= p
? p
-> minimum_width
: 1 ;
348 if ( weight
== ( unsigned ) - 1 )
349 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
351 if ( align_percent
== ( unsigned ) - 1 )
352 align_percent
= p
? p
-> align_percent
: 0 ;
354 if ( ellipsize_percent
== ( unsigned ) - 1 )
355 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
357 assert ( align_percent
<= 100 );
358 assert ( ellipsize_percent
<= 100 );
360 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
361 * formatting. Let's see if we can reuse the cell data and ref it once more. */
363 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
364 d
= table_data_ref ( p
);
366 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
371 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
375 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
377 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
382 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
387 /* Add the data of the specified cell a second time as a new cell to the end. */
389 i
= TABLE_CELL_TO_INDEX ( cell
);
393 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
396 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
400 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
401 _cleanup_free_
char * curl
= NULL
;
407 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
408 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
410 i
= TABLE_CELL_TO_INDEX ( cell
);
414 assert_se ( od
= t
-> data
[ i
]);
418 assert ( od
-> n_ref
> 1 );
421 curl
= strdup ( od
-> url
);
433 od
-> ellipsize_percent
);
437 nd
-> color
= od
-> color
;
438 nd
-> url
= TAKE_PTR ( curl
);
439 nd
-> uppercase
= od
-> uppercase
;
441 table_data_unref ( od
);
444 assert ( nd
-> n_ref
== 1 );
449 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
455 /* Get the data object of the specified cell, or NULL if it doesn't exist */
457 i
= TABLE_CELL_TO_INDEX ( cell
);
462 assert ( t
-> data
[ i
]-> n_ref
> 0 );
467 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
473 if ( minimum_width
== ( size_t ) - 1 )
476 r
= table_dedup_cell ( t
, cell
);
480 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
484 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
490 r
= table_dedup_cell ( t
, cell
);
494 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
498 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
504 if ( weight
== ( unsigned ) - 1 )
505 weight
= DEFAULT_WEIGHT
;
507 r
= table_dedup_cell ( t
, cell
);
511 table_get_data ( t
, cell
)-> weight
= weight
;
515 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
521 if ( percent
== ( unsigned ) - 1 )
524 assert ( percent
<= 100 );
526 r
= table_dedup_cell ( t
, cell
);
530 table_get_data ( t
, cell
)-> align_percent
= percent
;
534 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
540 if ( percent
== ( unsigned ) - 1 )
543 assert ( percent
<= 100 );
545 r
= table_dedup_cell ( t
, cell
);
549 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
553 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
559 r
= table_dedup_cell ( t
, cell
);
563 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
567 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
568 _cleanup_free_
char * copy
= NULL
;
580 r
= table_dedup_cell ( t
, cell
);
584 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
587 int table_set_uppercase ( Table
* t
, TableCell
* cell
, bool b
) {
594 r
= table_dedup_cell ( t
, cell
);
598 assert_se ( d
= table_get_data ( t
, cell
));
600 if ( d
-> uppercase
== b
)
603 d
-> formatted
= mfree ( d
-> formatted
);
608 int table_update ( Table
* t
, TableCell
* cell
, TableDataType type
, const void * data
) {
609 _cleanup_free_
char * curl
= NULL
;
616 i
= TABLE_CELL_TO_INDEX ( cell
);
620 assert_se ( od
= t
-> data
[ i
]);
623 curl
= strdup ( od
-> url
);
635 od
-> ellipsize_percent
);
639 nd
-> color
= od
-> color
;
640 nd
-> url
= TAKE_PTR ( curl
);
641 nd
-> uppercase
= od
-> uppercase
;
643 table_data_unref ( od
);
649 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
655 assert ( first_type
>= 0 );
656 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
660 va_start ( ap
, first_type
);
679 data
= va_arg ( ap
, const char *);
683 buffer
. b
= va_arg ( ap
, int );
687 case TABLE_TIMESTAMP
:
689 buffer
. usec
= va_arg ( ap
, usec_t
);
694 buffer
. size
= va_arg ( ap
, uint64_t );
699 buffer
. uint32
= va_arg ( ap
, uint32_t );
700 data
= & buffer
. uint32
;
704 buffer
. uint64
= va_arg ( ap
, uint64_t );
705 data
= & buffer
. uint64
;
709 buffer
. percent
= va_arg ( ap
, int );
710 data
= & buffer
. percent
;
713 case _TABLE_DATA_TYPE_MAX
:
714 /* Used as end marker */
719 assert_not_reached ( "Uh? Unexpected data type." );
722 r
= table_add_cell ( t
, NULL
, type
, data
);
728 type
= va_arg ( ap
, TableDataType
);
732 void table_set_header ( Table
* t
, bool b
) {
738 void table_set_width ( Table
* t
, size_t width
) {
744 int table_set_display ( Table
* t
, size_t first_column
, ...) {
745 size_t allocated
, column
;
750 allocated
= t
-> n_display_map
;
751 column
= first_column
;
753 va_start ( ap
, first_column
);
755 assert ( column
< t
-> n_columns
);
757 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
762 t
-> display_map
[ t
-> n_display_map
++] = column
;
764 column
= va_arg ( ap
, size_t );
765 if ( column
== ( size_t ) - 1 )
774 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
775 size_t allocated
, column
;
780 allocated
= t
-> n_sort_map
;
781 column
= first_column
;
783 va_start ( ap
, first_column
);
785 assert ( column
< t
-> n_columns
);
787 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
792 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
794 column
= va_arg ( ap
, size_t );
795 if ( column
== ( size_t ) - 1 )
803 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
807 if ( a
-> type
== b
-> type
) {
809 /* We only define ordering for cells of the same data type. If cells with different data types are
810 * compared we follow the order the cells were originally added in */
815 return strcmp ( a
-> string
, b
-> string
);
818 if (! a
-> boolean
&& b
-> boolean
)
820 if ( a
-> boolean
&& ! b
-> boolean
)
824 case TABLE_TIMESTAMP
:
825 return CMP ( a
-> timestamp
, b
-> timestamp
);
828 return CMP ( a
-> timespan
, b
-> timespan
);
831 return CMP ( a
-> size
, b
-> size
);
834 return CMP ( a
-> uint32
, b
-> uint32
);
837 return CMP ( a
-> uint64
, b
-> uint64
);
840 return CMP ( a
-> percent
, b
-> percent
);
847 /* Generic fallback using the orginal order in which the cells where added. */
848 return CMP ( index_a
, index_b
);
851 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
858 /* Make sure the header stays at the beginning */
859 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
861 if (* a
< t
-> n_columns
)
863 if (* b
< t
-> n_columns
)
866 /* Order other lines by the sorting map */
867 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
870 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
871 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
873 r
= cell_data_compare ( d
, * a
, dd
, * b
);
875 return t
-> reverse_map
&& t
-> reverse_map
[ t
-> sort_map
[ i
]] ? - r
: r
;
878 /* Order identical lines by the order there were originally added in */
882 static const char * table_data_format ( TableData
* d
) {
896 d
-> formatted
= new ( char , strlen ( d
-> string
) + 1 );
900 for ( p
= d
-> string
, q
= d
-> formatted
; * p
; p
++, q
++)
901 * q
= ( char ) toupper (( unsigned char ) * p
);
910 return yes_no ( d
-> boolean
);
912 case TABLE_TIMESTAMP
: {
913 _cleanup_free_
char * p
;
915 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
919 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
922 d
-> formatted
= TAKE_PTR ( p
);
926 case TABLE_TIMESPAN
: {
927 _cleanup_free_
char * p
;
929 p
= new ( char , FORMAT_TIMESPAN_MAX
);
933 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timespan
, 0 ))
936 d
-> formatted
= TAKE_PTR ( p
);
941 _cleanup_free_
char * p
;
943 p
= new ( char , FORMAT_BYTES_MAX
);
947 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
950 d
-> formatted
= TAKE_PTR ( p
);
955 _cleanup_free_
char * p
;
957 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
961 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
962 d
-> formatted
= TAKE_PTR ( p
);
967 _cleanup_free_
char * p
;
969 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
973 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
974 d
-> formatted
= TAKE_PTR ( p
);
978 case TABLE_PERCENT
: {
979 _cleanup_free_
char * p
;
981 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
985 sprintf ( p
, "%i%%" , d
-> percent
);
986 d
-> formatted
= TAKE_PTR ( p
);
991 assert_not_reached ( "Unexpected type?" );
997 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
1001 t
= table_data_format ( d
);
1005 l
= utf8_console_width ( t
);
1006 if ( l
== ( size_t ) - 1 )
1009 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
1010 l
= d
-> maximum_width
;
1012 if ( l
< d
-> minimum_width
)
1013 l
= d
-> minimum_width
;
1019 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
1020 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
1021 _cleanup_free_
char * clickable
= NULL
;
1027 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1030 assert ( percent
<= 100 );
1032 old_length
= strlen ( str
);
1035 r
= terminal_urlify ( url
, str
, & clickable
);
1039 clickable_length
= strlen ( clickable
);
1041 clickable_length
= old_length
;
1043 /* Determine current width on screen */
1045 while ( p
< str
+ old_length
) {
1048 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
1049 p
++, w
++; /* count invalid chars as 1 */
1053 p
= utf8_next_char ( p
);
1054 w
+= unichar_iswide ( c
) ? 2 : 1 ;
1057 /* Already wider than the target, if so, don't do anything */
1058 if ( w
>= new_length
)
1059 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
1061 /* How much spaces shall we add? An how much on the left side? */
1062 space
= new_length
- w
;
1063 lspace
= space
* percent
/ 100U ;
1065 ret
= new ( char , space
+ clickable_length
+ 1 );
1069 for ( i
= 0 ; i
< lspace
; i
++)
1071 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
1072 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1075 ret
[ space
+ clickable_length
] = 0 ;
1079 int table_print ( Table
* t
, FILE * f
) {
1080 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
1081 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1083 _cleanup_free_
size_t * sorted
= NULL
;
1084 uint64_t * column_weight
, weight_sum
;
1092 /* Ensure we have no incomplete rows */
1093 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1095 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1096 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1099 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1101 sorted
= new ( size_t , n_rows
);
1105 for ( i
= 0 ; i
< n_rows
; i
++)
1106 sorted
[ i
] = i
* t
-> n_columns
;
1108 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1112 display_columns
= t
-> n_display_map
;
1114 display_columns
= t
-> n_columns
;
1116 assert ( display_columns
> 0 );
1118 minimum_width
= newa ( size_t , display_columns
);
1119 maximum_width
= newa ( size_t , display_columns
);
1120 requested_width
= newa ( size_t , display_columns
);
1121 width
= newa ( size_t , display_columns
);
1122 column_weight
= newa0 ( uint64_t , display_columns
);
1124 for ( j
= 0 ; j
< display_columns
; j
++) {
1125 minimum_width
[ j
] = 1 ;
1126 maximum_width
[ j
] = ( size_t ) - 1 ;
1127 requested_width
[ j
] = ( size_t ) - 1 ;
1130 /* First pass: determine column sizes */
1131 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1134 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1135 * hence we don't care for sorted[] during the first pass. */
1136 row
= t
-> data
+ i
* t
-> n_columns
;
1138 for ( j
= 0 ; j
< display_columns
; j
++) {
1142 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1144 r
= table_data_requested_width ( d
, & req
);
1148 /* Determine the biggest width that any cell in this column would like to have */
1149 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1150 requested_width
[ j
] < req
)
1151 requested_width
[ j
] = req
;
1153 /* Determine the minimum width any cell in this column needs */
1154 if ( minimum_width
[ j
] < d
-> minimum_width
)
1155 minimum_width
[ j
] = d
-> minimum_width
;
1157 /* Determine the maximum width any cell in this column needs */
1158 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1159 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1160 maximum_width
[ j
] > d
-> maximum_width
))
1161 maximum_width
[ j
] = d
-> maximum_width
;
1163 /* Determine the full columns weight */
1164 column_weight
[ j
] += d
-> weight
;
1168 /* One space between each column */
1169 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1171 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1173 for ( j
= 0 ; j
< display_columns
; j
++) {
1174 weight_sum
+= column_weight
[ j
];
1176 table_minimum_width
+= minimum_width
[ j
];
1178 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1179 table_maximum_width
= ( size_t ) - 1 ;
1181 table_maximum_width
+= maximum_width
[ j
];
1183 table_requested_width
+= requested_width
[ j
];
1186 /* Calculate effective table width */
1187 if ( t
-> width
== ( size_t ) - 1 )
1188 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1190 table_effective_width
= t
-> width
;
1192 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1193 table_effective_width
= table_maximum_width
;
1195 if ( table_effective_width
< table_minimum_width
)
1196 table_effective_width
= table_minimum_width
;
1198 if ( table_effective_width
>= table_requested_width
) {
1201 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1202 * each column with what it asked for and the distribute the rest. */
1204 extra
= table_effective_width
- table_requested_width
;
1206 for ( j
= 0 ; j
< display_columns
; j
++) {
1209 if ( weight_sum
== 0 )
1210 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1212 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1214 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1215 width
[ j
] = maximum_width
[ j
];
1217 if ( width
[ j
] < minimum_width
[ j
])
1218 width
[ j
] = minimum_width
[ j
];
1220 assert ( width
[ j
] >= requested_width
[ j
]);
1221 delta
= width
[ j
] - requested_width
[ j
];
1223 /* Subtract what we just added from the rest */
1229 assert ( weight_sum
>= column_weight
[ j
]);
1230 weight_sum
-= column_weight
[ j
];
1234 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1235 * with the minimum they need, and then distribute anything left. */
1236 bool finalize
= false ;
1239 extra
= table_effective_width
- table_minimum_width
;
1241 for ( j
= 0 ; j
< display_columns
; j
++)
1242 width
[ j
] = ( size_t ) - 1 ;
1245 bool restart
= false ;
1247 for ( j
= 0 ; j
< display_columns
; j
++) {
1250 /* Did this column already get something assigned? If so, let's skip to the next */
1251 if ( width
[ j
] != ( size_t ) - 1 )
1254 if ( weight_sum
== 0 )
1255 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1257 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1259 if ( w
>= requested_width
[ j
]) {
1260 /* Never give more than requested. If we hit a column like this, there's more
1261 * space to allocate to other columns which means we need to restart the
1262 * iteration. However, if we hit a column like this, let's assign it the space
1263 * it wanted for good early.*/
1265 w
= requested_width
[ j
];
1268 } else if (! finalize
)
1273 assert ( w
>= minimum_width
[ j
]);
1274 delta
= w
- minimum_width
[ j
];
1276 assert ( delta
<= extra
);
1279 assert ( weight_sum
>= column_weight
[ j
]);
1280 weight_sum
-= column_weight
[ j
];
1282 if ( restart
&& ! finalize
)
1294 /* Second pass: show output */
1295 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1299 row
= t
-> data
+ sorted
[ i
];
1301 row
= t
-> data
+ i
* t
-> n_columns
;
1303 for ( j
= 0 ; j
< display_columns
; j
++) {
1304 _cleanup_free_
char * buffer
= NULL
;
1309 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1311 field
= table_data_format ( d
);
1315 l
= utf8_console_width ( field
);
1317 /* Field is wider than allocated space. Let's ellipsize */
1319 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1325 } else if ( l
< width
[ j
]) {
1326 /* Field is shorter than allocated space. Let's align with spaces */
1328 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1335 if ( l
>= width
[ j
] && d
-> url
) {
1336 _cleanup_free_
char * clickable
= NULL
;
1338 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1342 free_and_replace ( buffer
, clickable
);
1346 if ( row
== t
-> data
) /* underline header line fully, including the column separator */
1347 fputs ( ansi_underline (), f
);
1350 fputc ( ' ' , f
); /* column separator */
1352 if ( d
-> color
&& colors_enabled ()) {
1353 if ( row
== t
-> data
) /* first undo header underliner */
1354 fputs ( ANSI_NORMAL
, f
);
1361 if ( colors_enabled () && ( d
-> color
|| row
== t
-> data
))
1362 fputs ( ANSI_NORMAL
, f
);
1368 return fflush_and_check ( f
);
1371 int table_format ( Table
* t
, char ** ret
) {
1372 _cleanup_fclose_
FILE * f
= NULL
;
1377 f
= open_memstream (& buf
, & sz
);
1381 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1383 r
= table_print ( t
, f
);
1394 size_t table_get_rows ( Table
* t
) {
1398 assert ( t
-> n_columns
> 0 );
1399 return t
-> n_cells
/ t
-> n_columns
;
1402 size_t table_get_columns ( Table
* t
) {
1406 assert ( t
-> n_columns
> 0 );
1407 return t
-> n_columns
;
1410 int table_set_reverse ( Table
* t
, size_t column
, bool b
) {
1412 assert ( column
< t
-> n_columns
);
1414 if (! t
-> reverse_map
) {
1418 t
-> reverse_map
= new0 ( bool , t
-> n_columns
);
1419 if (! t
-> reverse_map
)
1423 t
-> reverse_map
[ column
] = b
;
1427 TableCell
* table_get_cell ( Table
* t
, size_t row
, size_t column
) {
1432 if ( column
>= t
-> n_columns
)
1435 i
= row
* t
-> n_columns
+ column
;
1436 if ( i
>= t
-> n_cells
)
1439 return TABLE_INDEX_TO_CELL ( i
);
1442 const void * table_get ( Table
* t
, TableCell
* cell
) {
1447 d
= table_get_data ( t
, cell
);
1454 const void * table_get_at ( Table
* t
, size_t row
, size_t column
) {
1457 cell
= table_get_cell ( t
, row
, column
);
1461 return table_get ( t
, cell
);
1464 static int table_data_to_json ( TableData
* d
, JsonVariant
** ret
) {
1469 return json_variant_new_null ( ret
);
1472 return json_variant_new_string ( ret
, d
-> string
);
1475 return json_variant_new_boolean ( ret
, d
-> boolean
);
1477 case TABLE_TIMESTAMP
:
1478 if ( d
-> timestamp
== USEC_INFINITY
)
1479 return json_variant_new_null ( ret
);
1481 return json_variant_new_unsigned ( ret
, d
-> timestamp
);
1483 case TABLE_TIMESPAN
:
1484 if ( d
-> timespan
== USEC_INFINITY
)
1485 return json_variant_new_null ( ret
);
1487 return json_variant_new_unsigned ( ret
, d
-> timespan
);
1490 if ( d
-> size
== ( size_t ) - 1 )
1491 return json_variant_new_null ( ret
);
1493 return json_variant_new_unsigned ( ret
, d
-> size
);
1496 return json_variant_new_unsigned ( ret
, d
-> uint32
);
1499 return json_variant_new_unsigned ( ret
, d
-> uint64
);
1502 return json_variant_new_integer ( ret
, d
-> percent
);
1509 int table_to_json ( Table
* t
, JsonVariant
** ret
) {
1510 JsonVariant
** rows
= NULL
, ** elements
= NULL
;
1511 _cleanup_free_
size_t * sorted
= NULL
;
1512 size_t n_rows
, i
, j
, display_columns
;
1517 /* Ensure we have no incomplete rows */
1518 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1520 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1521 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1524 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1526 sorted
= new ( size_t , n_rows
);
1532 for ( i
= 0 ; i
< n_rows
; i
++)
1533 sorted
[ i
] = i
* t
-> n_columns
;
1535 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1539 display_columns
= t
-> n_display_map
;
1541 display_columns
= t
-> n_columns
;
1542 assert ( display_columns
> 0 );
1544 elements
= new0 ( JsonVariant
*, display_columns
* 2 );
1550 for ( j
= 0 ; j
< display_columns
; j
++) {
1553 assert_se ( d
= t
-> data
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1555 r
= table_data_to_json ( d
, elements
+ j
* 2 );
1560 rows
= new0 ( JsonVariant
*, n_rows
- 1 );
1566 for ( i
= 1 ; i
< n_rows
; i
++) {
1570 row
= t
-> data
+ sorted
[ i
];
1572 row
= t
-> data
+ i
* t
-> n_columns
;
1574 for ( j
= 0 ; j
< display_columns
; j
++) {
1578 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1581 elements
[ k
] = json_variant_unref ( elements
[ k
]);
1583 r
= table_data_to_json ( d
, elements
+ k
);
1588 r
= json_variant_new_object ( rows
+ i
- 1 , elements
, display_columns
* 2 );
1593 r
= json_variant_new_array ( ret
, rows
, n_rows
- 1 );
1597 json_variant_unref_many ( rows
, n_rows
- 1 );
1602 json_variant_unref_many ( elements
, display_columns
* 2 );
1609 int table_print_json ( Table
* t
, FILE * f
, JsonFormatFlags flags
) {
1610 _cleanup_ ( json_variant_unrefp
) JsonVariant
* v
= NULL
;
1618 r
= table_to_json ( t
, & v
);
1622 json_variant_dump ( v
, flags
, f
, NULL
);
1624 return fflush_and_check ( f
);