]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
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 */
82 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
83 /* … add more here as we start supporting more cell data types … */
87 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
92 i
= PTR_TO_SIZE ( cell
);
98 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
99 assert ( index
!= ( size_t ) - 1 );
100 return SIZE_TO_PTR ( index
+ 1 );
107 bool header
; /* Whether to show the header row? */
108 size_t width
; /* If != (size_t) -1 the width to format this table in */
113 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 */
114 size_t n_display_map
;
116 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
122 Table
* table_new_raw ( size_t n_columns
) {
123 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
125 assert ( n_columns
> 0 );
131 * t
= ( struct Table
) {
132 . n_columns
= n_columns
,
134 . width
= ( size_t ) - 1 ,
140 Table
* table_new_internal ( const char * first_header
, ...) {
141 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
142 size_t n_columns
= 1 ;
147 assert ( first_header
);
149 va_start ( ap
, first_header
);
151 h
= va_arg ( ap
, const char *);
159 t
= table_new_raw ( n_columns
);
163 va_start ( ap
, first_header
);
164 for ( h
= first_header
; h
; h
= va_arg ( ap
, const char *)) {
167 r
= table_add_cell ( t
, & cell
, TABLE_STRING
, h
);
173 /* Make the table header uppercase */
174 r
= table_set_uppercase ( t
, cell
, true );
182 assert ( t
-> n_columns
== t
-> n_cells
);
186 static TableData
* table_data_free ( TableData
* d
) {
195 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
196 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
198 Table
* table_unref ( Table
* t
) {
204 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
205 table_data_unref ( t
-> data
[ i
]);
208 free ( t
-> display_map
);
210 free ( t
-> reverse_map
);
215 static size_t table_data_size ( TableDataType type
, const void * data
) {
223 return strlen ( data
) + 1 ;
228 case TABLE_TIMESTAMP
:
230 return sizeof ( usec_t
);
235 return sizeof ( uint64_t );
239 return sizeof ( uint32_t );
247 assert_not_reached ( "Uh? Unexpected cell type" );
251 static bool table_data_matches (
255 size_t minimum_width
,
256 size_t maximum_width
,
258 unsigned align_percent
,
259 unsigned ellipsize_percent
) {
267 if ( d
-> minimum_width
!= minimum_width
)
270 if ( d
-> maximum_width
!= maximum_width
)
273 if ( d
-> weight
!= weight
)
276 if ( d
-> align_percent
!= align_percent
)
279 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
282 /* If a color/url/uppercase flag is set, refuse to merge */
290 k
= table_data_size ( type
, data
);
291 l
= table_data_size ( d
-> type
, d
-> data
);
296 return memcmp_safe ( data
, d
-> data
, l
) == 0 ;
299 static TableData
* table_data_new (
302 size_t minimum_width
,
303 size_t maximum_width
,
305 unsigned align_percent
,
306 unsigned ellipsize_percent
) {
311 data_size
= table_data_size ( type
, data
);
313 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
319 d
-> minimum_width
= minimum_width
;
320 d
-> maximum_width
= maximum_width
;
322 d
-> align_percent
= align_percent
;
323 d
-> ellipsize_percent
= ellipsize_percent
;
324 memcpy_safe ( d
-> data
, data
, data_size
);
329 int table_add_cell_full (
331 TableCell
** ret_cell
,
334 size_t minimum_width
,
335 size_t maximum_width
,
337 unsigned align_percent
,
338 unsigned ellipsize_percent
) {
340 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
345 assert ( type
< _TABLE_DATA_TYPE_MAX
);
347 /* Determine the cell adjacent to the current one, but one row up */
348 if ( t
-> n_cells
>= t
-> n_columns
)
349 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
353 /* If formatting parameters are left unspecified, copy from the previous row */
354 if ( minimum_width
== ( size_t ) - 1 )
355 minimum_width
= p
? p
-> minimum_width
: 1 ;
357 if ( weight
== ( unsigned ) - 1 )
358 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
360 if ( align_percent
== ( unsigned ) - 1 )
361 align_percent
= p
? p
-> align_percent
: 0 ;
363 if ( ellipsize_percent
== ( unsigned ) - 1 )
364 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
366 assert ( align_percent
<= 100 );
367 assert ( ellipsize_percent
<= 100 );
369 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
370 * formatting. Let's see if we can reuse the cell data and ref it once more. */
372 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
373 d
= table_data_ref ( p
);
375 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
380 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
384 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
386 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
391 int table_add_cell_stringf ( Table
* t
, TableCell
** ret_cell
, const char * format
, ...) {
392 _cleanup_free_
char * buffer
= NULL
;
396 va_start ( ap
, format
);
397 r
= vasprintf (& buffer
, format
, ap
);
402 return table_add_cell ( t
, ret_cell
, TABLE_STRING
, buffer
);
405 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
410 /* Add the data of the specified cell a second time as a new cell to the end. */
412 i
= TABLE_CELL_TO_INDEX ( cell
);
416 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
419 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
423 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
424 _cleanup_free_
char * curl
= NULL
;
430 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
431 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
433 i
= TABLE_CELL_TO_INDEX ( cell
);
437 assert_se ( od
= t
-> data
[ i
]);
441 assert ( od
-> n_ref
> 1 );
444 curl
= strdup ( od
-> url
);
456 od
-> ellipsize_percent
);
460 nd
-> color
= od
-> color
;
461 nd
-> url
= TAKE_PTR ( curl
);
462 nd
-> uppercase
= od
-> uppercase
;
464 table_data_unref ( od
);
467 assert ( nd
-> n_ref
== 1 );
472 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
478 /* Get the data object of the specified cell, or NULL if it doesn't exist */
480 i
= TABLE_CELL_TO_INDEX ( cell
);
485 assert ( t
-> data
[ i
]-> n_ref
> 0 );
490 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
496 if ( minimum_width
== ( size_t ) - 1 )
499 r
= table_dedup_cell ( t
, cell
);
503 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
507 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
513 r
= table_dedup_cell ( t
, cell
);
517 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
521 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
527 if ( weight
== ( unsigned ) - 1 )
528 weight
= DEFAULT_WEIGHT
;
530 r
= table_dedup_cell ( t
, cell
);
534 table_get_data ( t
, cell
)-> weight
= weight
;
538 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
544 if ( percent
== ( unsigned ) - 1 )
547 assert ( percent
<= 100 );
549 r
= table_dedup_cell ( t
, cell
);
553 table_get_data ( t
, cell
)-> align_percent
= percent
;
557 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
563 if ( percent
== ( unsigned ) - 1 )
566 assert ( percent
<= 100 );
568 r
= table_dedup_cell ( t
, cell
);
572 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
576 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
582 r
= table_dedup_cell ( t
, cell
);
586 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
590 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
591 _cleanup_free_
char * copy
= NULL
;
603 r
= table_dedup_cell ( t
, cell
);
607 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
610 int table_set_uppercase ( Table
* t
, TableCell
* cell
, bool b
) {
617 r
= table_dedup_cell ( t
, cell
);
621 assert_se ( d
= table_get_data ( t
, cell
));
623 if ( d
-> uppercase
== b
)
626 d
-> formatted
= mfree ( d
-> formatted
);
631 int table_update ( Table
* t
, TableCell
* cell
, TableDataType type
, const void * data
) {
632 _cleanup_free_
char * curl
= NULL
;
639 i
= TABLE_CELL_TO_INDEX ( cell
);
643 assert_se ( od
= t
-> data
[ i
]);
646 curl
= strdup ( od
-> url
);
658 od
-> ellipsize_percent
);
662 nd
-> color
= od
-> color
;
663 nd
-> url
= TAKE_PTR ( curl
);
664 nd
-> uppercase
= od
-> uppercase
;
666 table_data_unref ( od
);
672 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
678 assert ( first_type
>= 0 );
679 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
683 va_start ( ap
, first_type
);
706 data
= va_arg ( ap
, const char *);
710 buffer
. b
= va_arg ( ap
, int );
714 case TABLE_TIMESTAMP
:
716 buffer
. usec
= va_arg ( ap
, usec_t
);
721 buffer
. size
= va_arg ( ap
, uint64_t );
726 buffer
. int_val
= va_arg ( ap
, int );
727 data
= & buffer
. int_val
;
731 buffer
. int32
= va_arg ( ap
, int32_t );
732 data
= & buffer
. int32
;
736 buffer
. int64
= va_arg ( ap
, int64_t );
737 data
= & buffer
. int64
;
741 buffer
. uint_val
= va_arg ( ap
, unsigned );
742 data
= & buffer
. uint_val
;
746 buffer
. uint32
= va_arg ( ap
, uint32_t );
747 data
= & buffer
. uint32
;
751 buffer
. uint64
= va_arg ( ap
, uint64_t );
752 data
= & buffer
. uint64
;
756 buffer
. percent
= va_arg ( ap
, int );
757 data
= & buffer
. percent
;
760 case _TABLE_DATA_TYPE_MAX
:
761 /* Used as end marker */
766 assert_not_reached ( "Uh? Unexpected data type." );
769 r
= table_add_cell ( t
, NULL
, type
, data
);
775 type
= va_arg ( ap
, TableDataType
);
779 void table_set_header ( Table
* t
, bool b
) {
785 void table_set_width ( Table
* t
, size_t width
) {
791 int table_set_display ( Table
* t
, size_t first_column
, ...) {
792 size_t allocated
, column
;
797 allocated
= t
-> n_display_map
;
798 column
= first_column
;
800 va_start ( ap
, first_column
);
802 assert ( column
< t
-> n_columns
);
804 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
809 t
-> display_map
[ t
-> n_display_map
++] = column
;
811 column
= va_arg ( ap
, size_t );
812 if ( column
== ( size_t ) - 1 )
821 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
822 size_t allocated
, column
;
827 allocated
= t
-> n_sort_map
;
828 column
= first_column
;
830 va_start ( ap
, first_column
);
832 assert ( column
< t
-> n_columns
);
834 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
839 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
841 column
= va_arg ( ap
, size_t );
842 if ( column
== ( size_t ) - 1 )
850 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
854 if ( a
-> type
== b
-> type
) {
856 /* We only define ordering for cells of the same data type. If cells with different data types are
857 * compared we follow the order the cells were originally added in */
862 return strcmp ( a
-> string
, b
-> string
);
865 if (! a
-> boolean
&& b
-> boolean
)
867 if ( a
-> boolean
&& ! b
-> boolean
)
871 case TABLE_TIMESTAMP
:
872 return CMP ( a
-> timestamp
, b
-> timestamp
);
875 return CMP ( a
-> timespan
, b
-> timespan
);
878 return CMP ( a
-> size
, b
-> size
);
881 return CMP ( a
-> int_val
, b
-> int_val
);
884 return CMP ( a
-> int32
, b
-> int32
);
887 return CMP ( a
-> int64
, b
-> int64
);
890 return CMP ( a
-> uint_val
, b
-> uint_val
);
893 return CMP ( a
-> uint32
, b
-> uint32
);
896 return CMP ( a
-> uint64
, b
-> uint64
);
899 return CMP ( a
-> percent
, b
-> percent
);
906 /* Generic fallback using the original order in which the cells where added. */
907 return CMP ( index_a
, index_b
);
910 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
917 /* Make sure the header stays at the beginning */
918 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
920 if (* a
< t
-> n_columns
)
922 if (* b
< t
-> n_columns
)
925 /* Order other lines by the sorting map */
926 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
929 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
930 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
932 r
= cell_data_compare ( d
, * a
, dd
, * b
);
934 return t
-> reverse_map
&& t
-> reverse_map
[ t
-> sort_map
[ i
]] ? - r
: r
;
937 /* Order identical lines by the order there were originally added in */
941 static const char * table_data_format ( TableData
* d
) {
955 d
-> formatted
= new ( char , strlen ( d
-> string
) + 1 );
959 for ( p
= d
-> string
, q
= d
-> formatted
; * p
; p
++, q
++)
960 * q
= ( char ) toupper (( unsigned char ) * p
);
969 return yes_no ( d
-> boolean
);
971 case TABLE_TIMESTAMP
: {
972 _cleanup_free_
char * p
;
974 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
978 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
981 d
-> formatted
= TAKE_PTR ( p
);
985 case TABLE_TIMESPAN
: {
986 _cleanup_free_
char * p
;
988 p
= new ( char , FORMAT_TIMESPAN_MAX
);
992 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timespan
, 0 ))
995 d
-> formatted
= TAKE_PTR ( p
);
1000 _cleanup_free_
char * p
;
1002 p
= new ( char , FORMAT_BYTES_MAX
);
1006 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
1009 d
-> formatted
= TAKE_PTR ( p
);
1014 _cleanup_free_
char * p
;
1016 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> int_val
) + 1 );
1020 sprintf ( p
, "%i" , d
-> int_val
);
1021 d
-> formatted
= TAKE_PTR ( p
);
1026 _cleanup_free_
char * p
;
1028 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> int32
) + 1 );
1032 sprintf ( p
, "%" PRIi32
, d
-> int32
);
1033 d
-> formatted
= TAKE_PTR ( p
);
1038 _cleanup_free_
char * p
;
1040 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> int64
) + 1 );
1044 sprintf ( p
, "%" PRIi64
, d
-> int64
);
1045 d
-> formatted
= TAKE_PTR ( p
);
1050 _cleanup_free_
char * p
;
1052 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint_val
) + 1 );
1056 sprintf ( p
, "%u" , d
-> uint_val
);
1057 d
-> formatted
= TAKE_PTR ( p
);
1061 case TABLE_UINT32
: {
1062 _cleanup_free_
char * p
;
1064 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
1068 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
1069 d
-> formatted
= TAKE_PTR ( p
);
1073 case TABLE_UINT64
: {
1074 _cleanup_free_
char * p
;
1076 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
1080 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
1081 d
-> formatted
= TAKE_PTR ( p
);
1085 case TABLE_PERCENT
: {
1086 _cleanup_free_
char * p
;
1088 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
1092 sprintf ( p
, "%i%%" , d
-> percent
);
1093 d
-> formatted
= TAKE_PTR ( p
);
1098 assert_not_reached ( "Unexpected type?" );
1101 return d
-> formatted
;
1104 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
1108 t
= table_data_format ( d
);
1112 l
= utf8_console_width ( t
);
1113 if ( l
== ( size_t ) - 1 )
1116 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
1117 l
= d
-> maximum_width
;
1119 if ( l
< d
-> minimum_width
)
1120 l
= d
-> minimum_width
;
1126 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
1127 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
1128 _cleanup_free_
char * clickable
= NULL
;
1134 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1137 assert ( percent
<= 100 );
1139 old_length
= strlen ( str
);
1142 r
= terminal_urlify ( url
, str
, & clickable
);
1146 clickable_length
= strlen ( clickable
);
1148 clickable_length
= old_length
;
1150 /* Determine current width on screen */
1152 while ( p
< str
+ old_length
) {
1155 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
1156 p
++, w
++; /* count invalid chars as 1 */
1160 p
= utf8_next_char ( p
);
1161 w
+= unichar_iswide ( c
) ? 2 : 1 ;
1164 /* Already wider than the target, if so, don't do anything */
1165 if ( w
>= new_length
)
1166 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
1168 /* How much spaces shall we add? An how much on the left side? */
1169 space
= new_length
- w
;
1170 lspace
= space
* percent
/ 100U ;
1172 ret
= new ( char , space
+ clickable_length
+ 1 );
1176 for ( i
= 0 ; i
< lspace
; i
++)
1178 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
1179 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1182 ret
[ space
+ clickable_length
] = 0 ;
1186 int table_print ( Table
* t
, FILE * f
) {
1187 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
1188 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1190 _cleanup_free_
size_t * sorted
= NULL
;
1191 uint64_t * column_weight
, weight_sum
;
1199 /* Ensure we have no incomplete rows */
1200 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1202 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1203 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1206 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1208 sorted
= new ( size_t , n_rows
);
1212 for ( i
= 0 ; i
< n_rows
; i
++)
1213 sorted
[ i
] = i
* t
-> n_columns
;
1215 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1219 display_columns
= t
-> n_display_map
;
1221 display_columns
= t
-> n_columns
;
1223 assert ( display_columns
> 0 );
1225 minimum_width
= newa ( size_t , display_columns
);
1226 maximum_width
= newa ( size_t , display_columns
);
1227 requested_width
= newa ( size_t , display_columns
);
1228 width
= newa ( size_t , display_columns
);
1229 column_weight
= newa0 ( uint64_t , display_columns
);
1231 for ( j
= 0 ; j
< display_columns
; j
++) {
1232 minimum_width
[ j
] = 1 ;
1233 maximum_width
[ j
] = ( size_t ) - 1 ;
1234 requested_width
[ j
] = ( size_t ) - 1 ;
1237 /* First pass: determine column sizes */
1238 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1241 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1242 * hence we don't care for sorted[] during the first pass. */
1243 row
= t
-> data
+ i
* t
-> n_columns
;
1245 for ( j
= 0 ; j
< display_columns
; j
++) {
1249 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1251 r
= table_data_requested_width ( d
, & req
);
1255 /* Determine the biggest width that any cell in this column would like to have */
1256 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1257 requested_width
[ j
] < req
)
1258 requested_width
[ j
] = req
;
1260 /* Determine the minimum width any cell in this column needs */
1261 if ( minimum_width
[ j
] < d
-> minimum_width
)
1262 minimum_width
[ j
] = d
-> minimum_width
;
1264 /* Determine the maximum width any cell in this column needs */
1265 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1266 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1267 maximum_width
[ j
] > d
-> maximum_width
))
1268 maximum_width
[ j
] = d
-> maximum_width
;
1270 /* Determine the full columns weight */
1271 column_weight
[ j
] += d
-> weight
;
1275 /* One space between each column */
1276 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1278 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1280 for ( j
= 0 ; j
< display_columns
; j
++) {
1281 weight_sum
+= column_weight
[ j
];
1283 table_minimum_width
+= minimum_width
[ j
];
1285 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1286 table_maximum_width
= ( size_t ) - 1 ;
1288 table_maximum_width
+= maximum_width
[ j
];
1290 table_requested_width
+= requested_width
[ j
];
1293 /* Calculate effective table width */
1294 if ( t
-> width
== ( size_t ) - 1 )
1295 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1297 table_effective_width
= t
-> width
;
1299 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1300 table_effective_width
= table_maximum_width
;
1302 if ( table_effective_width
< table_minimum_width
)
1303 table_effective_width
= table_minimum_width
;
1305 if ( table_effective_width
>= table_requested_width
) {
1308 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1309 * each column with what it asked for and the distribute the rest. */
1311 extra
= table_effective_width
- table_requested_width
;
1313 for ( j
= 0 ; j
< display_columns
; j
++) {
1316 if ( weight_sum
== 0 )
1317 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1319 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1321 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1322 width
[ j
] = maximum_width
[ j
];
1324 if ( width
[ j
] < minimum_width
[ j
])
1325 width
[ j
] = minimum_width
[ j
];
1327 assert ( width
[ j
] >= requested_width
[ j
]);
1328 delta
= width
[ j
] - requested_width
[ j
];
1330 /* Subtract what we just added from the rest */
1336 assert ( weight_sum
>= column_weight
[ j
]);
1337 weight_sum
-= column_weight
[ j
];
1341 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1342 * with the minimum they need, and then distribute anything left. */
1343 bool finalize
= false ;
1346 extra
= table_effective_width
- table_minimum_width
;
1348 for ( j
= 0 ; j
< display_columns
; j
++)
1349 width
[ j
] = ( size_t ) - 1 ;
1352 bool restart
= false ;
1354 for ( j
= 0 ; j
< display_columns
; j
++) {
1357 /* Did this column already get something assigned? If so, let's skip to the next */
1358 if ( width
[ j
] != ( size_t ) - 1 )
1361 if ( weight_sum
== 0 )
1362 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1364 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1366 if ( w
>= requested_width
[ j
]) {
1367 /* Never give more than requested. If we hit a column like this, there's more
1368 * space to allocate to other columns which means we need to restart the
1369 * iteration. However, if we hit a column like this, let's assign it the space
1370 * it wanted for good early.*/
1372 w
= requested_width
[ j
];
1375 } else if (! finalize
)
1380 assert ( w
>= minimum_width
[ j
]);
1381 delta
= w
- minimum_width
[ j
];
1383 assert ( delta
<= extra
);
1386 assert ( weight_sum
>= column_weight
[ j
]);
1387 weight_sum
-= column_weight
[ j
];
1389 if ( restart
&& ! finalize
)
1401 /* Second pass: show output */
1402 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1406 row
= t
-> data
+ sorted
[ i
];
1408 row
= t
-> data
+ i
* t
-> n_columns
;
1410 for ( j
= 0 ; j
< display_columns
; j
++) {
1411 _cleanup_free_
char * buffer
= NULL
;
1416 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1418 field
= table_data_format ( d
);
1422 l
= utf8_console_width ( field
);
1424 /* Field is wider than allocated space. Let's ellipsize */
1426 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1432 } else if ( l
< width
[ j
]) {
1433 /* Field is shorter than allocated space. Let's align with spaces */
1435 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1442 if ( l
>= width
[ j
] && d
-> url
) {
1443 _cleanup_free_
char * clickable
= NULL
;
1445 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1449 free_and_replace ( buffer
, clickable
);
1453 if ( row
== t
-> data
) /* underline header line fully, including the column separator */
1454 fputs ( ansi_underline (), f
);
1457 fputc ( ' ' , f
); /* column separator */
1459 if ( d
-> color
&& colors_enabled ()) {
1460 if ( row
== t
-> data
) /* first undo header underliner */
1461 fputs ( ANSI_NORMAL
, f
);
1468 if ( colors_enabled () && ( d
-> color
|| row
== t
-> data
))
1469 fputs ( ANSI_NORMAL
, f
);
1475 return fflush_and_check ( f
);
1478 int table_format ( Table
* t
, char ** ret
) {
1479 _cleanup_fclose_
FILE * f
= NULL
;
1484 f
= open_memstream_unlocked (& buf
, & sz
);
1488 r
= table_print ( t
, f
);
1499 size_t table_get_rows ( Table
* t
) {
1503 assert ( t
-> n_columns
> 0 );
1504 return t
-> n_cells
/ t
-> n_columns
;
1507 size_t table_get_columns ( Table
* t
) {
1511 assert ( t
-> n_columns
> 0 );
1512 return t
-> n_columns
;
1515 int table_set_reverse ( Table
* t
, size_t column
, bool b
) {
1517 assert ( column
< t
-> n_columns
);
1519 if (! t
-> reverse_map
) {
1523 t
-> reverse_map
= new0 ( bool , t
-> n_columns
);
1524 if (! t
-> reverse_map
)
1528 t
-> reverse_map
[ column
] = b
;
1532 TableCell
* table_get_cell ( Table
* t
, size_t row
, size_t column
) {
1537 if ( column
>= t
-> n_columns
)
1540 i
= row
* t
-> n_columns
+ column
;
1541 if ( i
>= t
-> n_cells
)
1544 return TABLE_INDEX_TO_CELL ( i
);
1547 const void * table_get ( Table
* t
, TableCell
* cell
) {
1552 d
= table_get_data ( t
, cell
);
1559 const void * table_get_at ( Table
* t
, size_t row
, size_t column
) {
1562 cell
= table_get_cell ( t
, row
, column
);
1566 return table_get ( t
, cell
);
1569 static int table_data_to_json ( TableData
* d
, JsonVariant
** ret
) {
1574 return json_variant_new_null ( ret
);
1577 return json_variant_new_string ( ret
, d
-> string
);
1580 return json_variant_new_boolean ( ret
, d
-> boolean
);
1582 case TABLE_TIMESTAMP
:
1583 if ( d
-> timestamp
== USEC_INFINITY
)
1584 return json_variant_new_null ( ret
);
1586 return json_variant_new_unsigned ( ret
, d
-> timestamp
);
1588 case TABLE_TIMESPAN
:
1589 if ( d
-> timespan
== USEC_INFINITY
)
1590 return json_variant_new_null ( ret
);
1592 return json_variant_new_unsigned ( ret
, d
-> timespan
);
1595 if ( d
-> size
== ( size_t ) - 1 )
1596 return json_variant_new_null ( ret
);
1598 return json_variant_new_unsigned ( ret
, d
-> size
);
1601 return json_variant_new_integer ( ret
, d
-> int_val
);
1604 return json_variant_new_integer ( ret
, d
-> int32
);
1607 return json_variant_new_integer ( ret
, d
-> int64
);
1610 return json_variant_new_unsigned ( ret
, d
-> uint_val
);
1613 return json_variant_new_unsigned ( ret
, d
-> uint32
);
1616 return json_variant_new_unsigned ( ret
, d
-> uint64
);
1619 return json_variant_new_integer ( ret
, d
-> percent
);
1626 int table_to_json ( Table
* t
, JsonVariant
** ret
) {
1627 JsonVariant
** rows
= NULL
, ** elements
= NULL
;
1628 _cleanup_free_
size_t * sorted
= NULL
;
1629 size_t n_rows
, i
, j
, display_columns
;
1634 /* Ensure we have no incomplete rows */
1635 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1637 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1638 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1641 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1643 sorted
= new ( size_t , n_rows
);
1649 for ( i
= 0 ; i
< n_rows
; i
++)
1650 sorted
[ i
] = i
* t
-> n_columns
;
1652 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1656 display_columns
= t
-> n_display_map
;
1658 display_columns
= t
-> n_columns
;
1659 assert ( display_columns
> 0 );
1661 elements
= new0 ( JsonVariant
*, display_columns
* 2 );
1667 for ( j
= 0 ; j
< display_columns
; j
++) {
1670 assert_se ( d
= t
-> data
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1672 r
= table_data_to_json ( d
, elements
+ j
* 2 );
1677 rows
= new0 ( JsonVariant
*, n_rows
- 1 );
1683 for ( i
= 1 ; i
< n_rows
; i
++) {
1687 row
= t
-> data
+ sorted
[ i
];
1689 row
= t
-> data
+ i
* t
-> n_columns
;
1691 for ( j
= 0 ; j
< display_columns
; j
++) {
1695 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1698 elements
[ k
] = json_variant_unref ( elements
[ k
]);
1700 r
= table_data_to_json ( d
, elements
+ k
);
1705 r
= json_variant_new_object ( rows
+ i
- 1 , elements
, display_columns
* 2 );
1710 r
= json_variant_new_array ( ret
, rows
, n_rows
- 1 );
1714 json_variant_unref_many ( rows
, n_rows
- 1 );
1719 json_variant_unref_many ( elements
, display_columns
* 2 );
1726 int table_print_json ( Table
* t
, FILE * f
, JsonFormatFlags flags
) {
1727 _cleanup_ ( json_variant_unrefp
) JsonVariant
* v
= NULL
;
1735 r
= table_to_json ( t
, & v
);
1739 json_variant_dump ( v
, flags
, f
, NULL
);
1741 return fflush_and_check ( f
);