]>
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"
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_dup_cell ( Table
* t
, TableCell
* cell
) {
388 /* Add the data of the specified cell a second time as a new cell to the end. */
390 i
= TABLE_CELL_TO_INDEX ( cell
);
394 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
397 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
401 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
402 _cleanup_free_
char * curl
= NULL
;
408 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
409 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
411 i
= TABLE_CELL_TO_INDEX ( cell
);
415 assert_se ( od
= t
-> data
[ i
]);
419 assert ( od
-> n_ref
> 1 );
422 curl
= strdup ( od
-> url
);
434 od
-> ellipsize_percent
);
438 nd
-> color
= od
-> color
;
439 nd
-> url
= TAKE_PTR ( curl
);
440 nd
-> uppercase
= od
-> uppercase
;
442 table_data_unref ( od
);
445 assert ( nd
-> n_ref
== 1 );
450 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
456 /* Get the data object of the specified cell, or NULL if it doesn't exist */
458 i
= TABLE_CELL_TO_INDEX ( cell
);
463 assert ( t
-> data
[ i
]-> n_ref
> 0 );
468 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
474 if ( minimum_width
== ( size_t ) - 1 )
477 r
= table_dedup_cell ( t
, cell
);
481 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
485 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
491 r
= table_dedup_cell ( t
, cell
);
495 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
499 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
505 if ( weight
== ( unsigned ) - 1 )
506 weight
= DEFAULT_WEIGHT
;
508 r
= table_dedup_cell ( t
, cell
);
512 table_get_data ( t
, cell
)-> weight
= weight
;
516 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
522 if ( percent
== ( unsigned ) - 1 )
525 assert ( percent
<= 100 );
527 r
= table_dedup_cell ( t
, cell
);
531 table_get_data ( t
, cell
)-> align_percent
= percent
;
535 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
541 if ( percent
== ( unsigned ) - 1 )
544 assert ( percent
<= 100 );
546 r
= table_dedup_cell ( t
, cell
);
550 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
554 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
560 r
= table_dedup_cell ( t
, cell
);
564 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
568 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
569 _cleanup_free_
char * copy
= NULL
;
581 r
= table_dedup_cell ( t
, cell
);
585 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
588 int table_set_uppercase ( Table
* t
, TableCell
* cell
, bool b
) {
595 r
= table_dedup_cell ( t
, cell
);
599 assert_se ( d
= table_get_data ( t
, cell
));
601 if ( d
-> uppercase
== b
)
604 d
-> formatted
= mfree ( d
-> formatted
);
609 int table_update ( Table
* t
, TableCell
* cell
, TableDataType type
, const void * data
) {
610 _cleanup_free_
char * curl
= NULL
;
617 i
= TABLE_CELL_TO_INDEX ( cell
);
621 assert_se ( od
= t
-> data
[ i
]);
624 curl
= strdup ( od
-> url
);
636 od
-> ellipsize_percent
);
640 nd
-> color
= od
-> color
;
641 nd
-> url
= TAKE_PTR ( curl
);
642 nd
-> uppercase
= od
-> uppercase
;
644 table_data_unref ( od
);
650 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
656 assert ( first_type
>= 0 );
657 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
661 va_start ( ap
, first_type
);
680 data
= va_arg ( ap
, const char *);
684 buffer
. b
= va_arg ( ap
, int );
688 case TABLE_TIMESTAMP
:
690 buffer
. usec
= va_arg ( ap
, usec_t
);
695 buffer
. size
= va_arg ( ap
, uint64_t );
700 buffer
. uint32
= va_arg ( ap
, uint32_t );
701 data
= & buffer
. uint32
;
705 buffer
. uint64
= va_arg ( ap
, uint64_t );
706 data
= & buffer
. uint64
;
710 buffer
. percent
= va_arg ( ap
, int );
711 data
= & buffer
. percent
;
714 case _TABLE_DATA_TYPE_MAX
:
715 /* Used as end marker */
720 assert_not_reached ( "Uh? Unexpected data type." );
723 r
= table_add_cell ( t
, NULL
, type
, data
);
729 type
= va_arg ( ap
, TableDataType
);
733 void table_set_header ( Table
* t
, bool b
) {
739 void table_set_width ( Table
* t
, size_t width
) {
745 int table_set_display ( Table
* t
, size_t first_column
, ...) {
746 size_t allocated
, column
;
751 allocated
= t
-> n_display_map
;
752 column
= first_column
;
754 va_start ( ap
, first_column
);
756 assert ( column
< t
-> n_columns
);
758 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
763 t
-> display_map
[ t
-> n_display_map
++] = column
;
765 column
= va_arg ( ap
, size_t );
766 if ( column
== ( size_t ) - 1 )
775 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
776 size_t allocated
, column
;
781 allocated
= t
-> n_sort_map
;
782 column
= first_column
;
784 va_start ( ap
, first_column
);
786 assert ( column
< t
-> n_columns
);
788 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
793 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
795 column
= va_arg ( ap
, size_t );
796 if ( column
== ( size_t ) - 1 )
804 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
808 if ( a
-> type
== b
-> type
) {
810 /* We only define ordering for cells of the same data type. If cells with different data types are
811 * compared we follow the order the cells were originally added in */
816 return strcmp ( a
-> string
, b
-> string
);
819 if (! a
-> boolean
&& b
-> boolean
)
821 if ( a
-> boolean
&& ! b
-> boolean
)
825 case TABLE_TIMESTAMP
:
826 return CMP ( a
-> timestamp
, b
-> timestamp
);
829 return CMP ( a
-> timespan
, b
-> timespan
);
832 return CMP ( a
-> size
, b
-> size
);
835 return CMP ( a
-> uint32
, b
-> uint32
);
838 return CMP ( a
-> uint64
, b
-> uint64
);
841 return CMP ( a
-> percent
, b
-> percent
);
848 /* Generic fallback using the orginal order in which the cells where added. */
849 return CMP ( index_a
, index_b
);
852 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
859 /* Make sure the header stays at the beginning */
860 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
862 if (* a
< t
-> n_columns
)
864 if (* b
< t
-> n_columns
)
867 /* Order other lines by the sorting map */
868 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
871 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
872 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
874 r
= cell_data_compare ( d
, * a
, dd
, * b
);
876 return t
-> reverse_map
&& t
-> reverse_map
[ t
-> sort_map
[ i
]] ? - r
: r
;
879 /* Order identical lines by the order there were originally added in */
883 static const char * table_data_format ( TableData
* d
) {
897 d
-> formatted
= new ( char , strlen ( d
-> string
) + 1 );
901 for ( p
= d
-> string
, q
= d
-> formatted
; * p
; p
++, q
++)
902 * q
= ( char ) toupper (( unsigned char ) * p
);
911 return yes_no ( d
-> boolean
);
913 case TABLE_TIMESTAMP
: {
914 _cleanup_free_
char * p
;
916 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
920 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
923 d
-> formatted
= TAKE_PTR ( p
);
927 case TABLE_TIMESPAN
: {
928 _cleanup_free_
char * p
;
930 p
= new ( char , FORMAT_TIMESPAN_MAX
);
934 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timespan
, 0 ))
937 d
-> formatted
= TAKE_PTR ( p
);
942 _cleanup_free_
char * p
;
944 p
= new ( char , FORMAT_BYTES_MAX
);
948 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
951 d
-> formatted
= TAKE_PTR ( p
);
956 _cleanup_free_
char * p
;
958 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
962 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
963 d
-> formatted
= TAKE_PTR ( p
);
968 _cleanup_free_
char * p
;
970 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
974 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
975 d
-> formatted
= TAKE_PTR ( p
);
979 case TABLE_PERCENT
: {
980 _cleanup_free_
char * p
;
982 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
986 sprintf ( p
, "%i%%" , d
-> percent
);
987 d
-> formatted
= TAKE_PTR ( p
);
992 assert_not_reached ( "Unexpected type?" );
998 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
1002 t
= table_data_format ( d
);
1006 l
= utf8_console_width ( t
);
1007 if ( l
== ( size_t ) - 1 )
1010 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
1011 l
= d
-> maximum_width
;
1013 if ( l
< d
-> minimum_width
)
1014 l
= d
-> minimum_width
;
1020 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
1021 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
1022 _cleanup_free_
char * clickable
= NULL
;
1028 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1031 assert ( percent
<= 100 );
1033 old_length
= strlen ( str
);
1036 r
= terminal_urlify ( url
, str
, & clickable
);
1040 clickable_length
= strlen ( clickable
);
1042 clickable_length
= old_length
;
1044 /* Determine current width on screen */
1046 while ( p
< str
+ old_length
) {
1049 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
1050 p
++, w
++; /* count invalid chars as 1 */
1054 p
= utf8_next_char ( p
);
1055 w
+= unichar_iswide ( c
) ? 2 : 1 ;
1058 /* Already wider than the target, if so, don't do anything */
1059 if ( w
>= new_length
)
1060 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
1062 /* How much spaces shall we add? An how much on the left side? */
1063 space
= new_length
- w
;
1064 lspace
= space
* percent
/ 100U ;
1066 ret
= new ( char , space
+ clickable_length
+ 1 );
1070 for ( i
= 0 ; i
< lspace
; i
++)
1072 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
1073 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1076 ret
[ space
+ clickable_length
] = 0 ;
1080 int table_print ( Table
* t
, FILE * f
) {
1081 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
1082 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1084 _cleanup_free_
size_t * sorted
= NULL
;
1085 uint64_t * column_weight
, weight_sum
;
1093 /* Ensure we have no incomplete rows */
1094 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1096 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1097 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1100 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1102 sorted
= new ( size_t , n_rows
);
1106 for ( i
= 0 ; i
< n_rows
; i
++)
1107 sorted
[ i
] = i
* t
-> n_columns
;
1109 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1113 display_columns
= t
-> n_display_map
;
1115 display_columns
= t
-> n_columns
;
1117 assert ( display_columns
> 0 );
1119 minimum_width
= newa ( size_t , display_columns
);
1120 maximum_width
= newa ( size_t , display_columns
);
1121 requested_width
= newa ( size_t , display_columns
);
1122 width
= newa ( size_t , display_columns
);
1123 column_weight
= newa0 ( uint64_t , display_columns
);
1125 for ( j
= 0 ; j
< display_columns
; j
++) {
1126 minimum_width
[ j
] = 1 ;
1127 maximum_width
[ j
] = ( size_t ) - 1 ;
1128 requested_width
[ j
] = ( size_t ) - 1 ;
1131 /* First pass: determine column sizes */
1132 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1135 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1136 * hence we don't care for sorted[] during the first pass. */
1137 row
= t
-> data
+ i
* t
-> n_columns
;
1139 for ( j
= 0 ; j
< display_columns
; j
++) {
1143 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1145 r
= table_data_requested_width ( d
, & req
);
1149 /* Determine the biggest width that any cell in this column would like to have */
1150 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1151 requested_width
[ j
] < req
)
1152 requested_width
[ j
] = req
;
1154 /* Determine the minimum width any cell in this column needs */
1155 if ( minimum_width
[ j
] < d
-> minimum_width
)
1156 minimum_width
[ j
] = d
-> minimum_width
;
1158 /* Determine the maximum width any cell in this column needs */
1159 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1160 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1161 maximum_width
[ j
] > d
-> maximum_width
))
1162 maximum_width
[ j
] = d
-> maximum_width
;
1164 /* Determine the full columns weight */
1165 column_weight
[ j
] += d
-> weight
;
1169 /* One space between each column */
1170 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1172 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1174 for ( j
= 0 ; j
< display_columns
; j
++) {
1175 weight_sum
+= column_weight
[ j
];
1177 table_minimum_width
+= minimum_width
[ j
];
1179 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1180 table_maximum_width
= ( size_t ) - 1 ;
1182 table_maximum_width
+= maximum_width
[ j
];
1184 table_requested_width
+= requested_width
[ j
];
1187 /* Calculate effective table width */
1188 if ( t
-> width
== ( size_t ) - 1 )
1189 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1191 table_effective_width
= t
-> width
;
1193 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1194 table_effective_width
= table_maximum_width
;
1196 if ( table_effective_width
< table_minimum_width
)
1197 table_effective_width
= table_minimum_width
;
1199 if ( table_effective_width
>= table_requested_width
) {
1202 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1203 * each column with what it asked for and the distribute the rest. */
1205 extra
= table_effective_width
- table_requested_width
;
1207 for ( j
= 0 ; j
< display_columns
; j
++) {
1210 if ( weight_sum
== 0 )
1211 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1213 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1215 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1216 width
[ j
] = maximum_width
[ j
];
1218 if ( width
[ j
] < minimum_width
[ j
])
1219 width
[ j
] = minimum_width
[ j
];
1221 assert ( width
[ j
] >= requested_width
[ j
]);
1222 delta
= width
[ j
] - requested_width
[ j
];
1224 /* Subtract what we just added from the rest */
1230 assert ( weight_sum
>= column_weight
[ j
]);
1231 weight_sum
-= column_weight
[ j
];
1235 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1236 * with the minimum they need, and then distribute anything left. */
1237 bool finalize
= false ;
1240 extra
= table_effective_width
- table_minimum_width
;
1242 for ( j
= 0 ; j
< display_columns
; j
++)
1243 width
[ j
] = ( size_t ) - 1 ;
1246 bool restart
= false ;
1248 for ( j
= 0 ; j
< display_columns
; j
++) {
1251 /* Did this column already get something assigned? If so, let's skip to the next */
1252 if ( width
[ j
] != ( size_t ) - 1 )
1255 if ( weight_sum
== 0 )
1256 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1258 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1260 if ( w
>= requested_width
[ j
]) {
1261 /* Never give more than requested. If we hit a column like this, there's more
1262 * space to allocate to other columns which means we need to restart the
1263 * iteration. However, if we hit a column like this, let's assign it the space
1264 * it wanted for good early.*/
1266 w
= requested_width
[ j
];
1269 } else if (! finalize
)
1274 assert ( w
>= minimum_width
[ j
]);
1275 delta
= w
- minimum_width
[ j
];
1277 assert ( delta
<= extra
);
1280 assert ( weight_sum
>= column_weight
[ j
]);
1281 weight_sum
-= column_weight
[ j
];
1283 if ( restart
&& ! finalize
)
1295 /* Second pass: show output */
1296 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1300 row
= t
-> data
+ sorted
[ i
];
1302 row
= t
-> data
+ i
* t
-> n_columns
;
1304 for ( j
= 0 ; j
< display_columns
; j
++) {
1305 _cleanup_free_
char * buffer
= NULL
;
1310 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1312 field
= table_data_format ( d
);
1316 l
= utf8_console_width ( field
);
1318 /* Field is wider than allocated space. Let's ellipsize */
1320 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1326 } else if ( l
< width
[ j
]) {
1327 /* Field is shorter than allocated space. Let's align with spaces */
1329 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1336 if ( l
>= width
[ j
] && d
-> url
) {
1337 _cleanup_free_
char * clickable
= NULL
;
1339 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1343 free_and_replace ( buffer
, clickable
);
1347 if ( row
== t
-> data
) /* underline header line fully, including the column separator */
1348 fputs ( ansi_underline (), f
);
1351 fputc ( ' ' , f
); /* column separator */
1353 if ( d
-> color
&& colors_enabled ()) {
1354 if ( row
== t
-> data
) /* first undo header underliner */
1355 fputs ( ANSI_NORMAL
, f
);
1362 if ( colors_enabled () && ( d
-> color
|| row
== t
-> data
))
1363 fputs ( ANSI_NORMAL
, f
);
1369 return fflush_and_check ( f
);
1372 int table_format ( Table
* t
, char ** ret
) {
1373 _cleanup_fclose_
FILE * f
= NULL
;
1378 f
= open_memstream (& buf
, & sz
);
1382 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1384 r
= table_print ( t
, f
);
1395 size_t table_get_rows ( Table
* t
) {
1399 assert ( t
-> n_columns
> 0 );
1400 return t
-> n_cells
/ t
-> n_columns
;
1403 size_t table_get_columns ( Table
* t
) {
1407 assert ( t
-> n_columns
> 0 );
1408 return t
-> n_columns
;
1411 int table_set_reverse ( Table
* t
, size_t column
, bool b
) {
1413 assert ( column
< t
-> n_columns
);
1415 if (! t
-> reverse_map
) {
1419 t
-> reverse_map
= new0 ( bool , t
-> n_columns
);
1420 if (! t
-> reverse_map
)
1424 t
-> reverse_map
[ column
] = b
;
1428 TableCell
* table_get_cell ( Table
* t
, size_t row
, size_t column
) {
1433 if ( column
>= t
-> n_columns
)
1436 i
= row
* t
-> n_columns
+ column
;
1437 if ( i
>= t
-> n_cells
)
1440 return TABLE_INDEX_TO_CELL ( i
);
1443 const void * table_get ( Table
* t
, TableCell
* cell
) {
1448 d
= table_get_data ( t
, cell
);
1455 const void * table_get_at ( Table
* t
, size_t row
, size_t column
) {
1458 cell
= table_get_cell ( t
, row
, column
);
1462 return table_get ( t
, cell
);
1465 static int table_data_to_json ( TableData
* d
, JsonVariant
** ret
) {
1470 return json_variant_new_null ( ret
);
1473 return json_variant_new_string ( ret
, d
-> string
);
1476 return json_variant_new_boolean ( ret
, d
-> boolean
);
1478 case TABLE_TIMESTAMP
:
1479 if ( d
-> timestamp
== USEC_INFINITY
)
1480 return json_variant_new_null ( ret
);
1482 return json_variant_new_unsigned ( ret
, d
-> timestamp
);
1484 case TABLE_TIMESPAN
:
1485 if ( d
-> timespan
== USEC_INFINITY
)
1486 return json_variant_new_null ( ret
);
1488 return json_variant_new_unsigned ( ret
, d
-> timespan
);
1491 if ( d
-> size
== ( size_t ) - 1 )
1492 return json_variant_new_null ( ret
);
1494 return json_variant_new_unsigned ( ret
, d
-> size
);
1497 return json_variant_new_unsigned ( ret
, d
-> uint32
);
1500 return json_variant_new_unsigned ( ret
, d
-> uint64
);
1503 return json_variant_new_integer ( ret
, d
-> percent
);
1510 int table_to_json ( Table
* t
, JsonVariant
** ret
) {
1511 JsonVariant
** rows
= NULL
, ** elements
= NULL
;
1512 _cleanup_free_
size_t * sorted
= NULL
;
1513 size_t n_rows
, i
, j
, display_columns
;
1518 /* Ensure we have no incomplete rows */
1519 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1521 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1522 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1525 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1527 sorted
= new ( size_t , n_rows
);
1533 for ( i
= 0 ; i
< n_rows
; i
++)
1534 sorted
[ i
] = i
* t
-> n_columns
;
1536 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1540 display_columns
= t
-> n_display_map
;
1542 display_columns
= t
-> n_columns
;
1543 assert ( display_columns
> 0 );
1545 elements
= new0 ( JsonVariant
*, display_columns
* 2 );
1551 for ( j
= 0 ; j
< display_columns
; j
++) {
1554 assert_se ( d
= t
-> data
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1556 r
= table_data_to_json ( d
, elements
+ j
* 2 );
1561 rows
= new0 ( JsonVariant
*, n_rows
- 1 );
1567 for ( i
= 1 ; i
< n_rows
; i
++) {
1571 row
= t
-> data
+ sorted
[ i
];
1573 row
= t
-> data
+ i
* t
-> n_columns
;
1575 for ( j
= 0 ; j
< display_columns
; j
++) {
1579 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1582 elements
[ k
] = json_variant_unref ( elements
[ k
]);
1584 r
= table_data_to_json ( d
, elements
+ k
);
1589 r
= json_variant_new_object ( rows
+ i
- 1 , elements
, display_columns
* 2 );
1594 r
= json_variant_new_array ( ret
, rows
, n_rows
- 1 );
1598 json_variant_unref_many ( rows
, n_rows
- 1 );
1603 json_variant_unref_many ( elements
, display_columns
* 2 );
1610 int table_print_json ( Table
* t
, FILE * f
, JsonFormatFlags flags
) {
1611 _cleanup_ ( json_variant_unrefp
) JsonVariant
* v
= NULL
;
1619 r
= table_to_json ( t
, & v
);
1623 json_variant_dump ( v
, flags
, f
, NULL
);
1625 return fflush_and_check ( f
);