]>
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"
11 #include "parse-util.h"
12 #include "pretty-print.h"
13 #include "string-util.h"
14 #include "terminal-util.h"
15 #include "time-util.h"
19 #define DEFAULT_WEIGHT 100
22 A few notes on implementation details:
24 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
25 table. It can be easily converted to an index number and back.
27 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
28 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
29 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
30 outside only sees Table and TableCell.
32 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
35 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
36 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
37 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
38 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
40 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
41 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
42 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
43 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
46 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
47 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
48 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
51 typedef struct TableData
{
55 size_t minimum_width
; /* minimum width for the column */
56 size_t maximum_width
; /* maximum width for the column */
57 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
58 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
59 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
61 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 */
62 char * url
; /* A URL to use for a clickable hyperlink */
63 char * formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
66 uint8_t data
[ 0 ]; /* data is generic array */
74 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
75 /* … add more here as we start supporting more cell data types … */
79 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
84 i
= PTR_TO_SIZE ( cell
);
90 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
91 assert ( index
!= ( size_t ) - 1 );
92 return SIZE_TO_PTR ( index
+ 1 );
99 bool header
; /* Whether to show the header row? */
100 size_t width
; /* If != (size_t) -1 the width to format this table in */
105 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 */
106 size_t n_display_map
;
108 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
112 Table
* table_new_raw ( size_t n_columns
) {
113 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
115 assert ( n_columns
> 0 );
121 * t
= ( struct Table
) {
122 . n_columns
= n_columns
,
124 . width
= ( size_t ) - 1 ,
130 Table
* table_new_internal ( const char * first_header
, ...) {
131 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
132 size_t n_columns
= 1 ;
136 assert ( first_header
);
138 va_start ( ap
, first_header
);
142 h
= va_arg ( ap
, const char *);
150 t
= table_new_raw ( n_columns
);
154 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, first_header
);
158 va_start ( ap
, first_header
);
162 h
= va_arg ( ap
, const char *);
166 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, h
);
174 assert ( t
-> n_columns
== t
-> n_cells
);
178 static TableData
* table_data_free ( TableData
* d
) {
187 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
188 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
190 Table
* table_unref ( Table
* t
) {
196 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
197 table_data_unref ( t
-> data
[ i
]);
200 free ( t
-> display_map
);
206 static size_t table_data_size ( TableDataType type
, const void * data
) {
214 return strlen ( data
) + 1 ;
219 case TABLE_TIMESTAMP
:
221 return sizeof ( usec_t
);
225 return sizeof ( uint64_t );
228 return sizeof ( uint32_t );
234 assert_not_reached ( "Uh? Unexpected cell type" );
238 static bool table_data_matches (
242 size_t minimum_width
,
243 size_t maximum_width
,
245 unsigned align_percent
,
246 unsigned ellipsize_percent
) {
254 if ( d
-> minimum_width
!= minimum_width
)
257 if ( d
-> maximum_width
!= maximum_width
)
260 if ( d
-> weight
!= weight
)
263 if ( d
-> align_percent
!= align_percent
)
266 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
269 k
= table_data_size ( type
, data
);
270 l
= table_data_size ( d
-> type
, d
-> data
);
275 return memcmp ( data
, d
-> data
, l
) == 0 ;
278 static TableData
* table_data_new (
281 size_t minimum_width
,
282 size_t maximum_width
,
284 unsigned align_percent
,
285 unsigned ellipsize_percent
) {
290 data_size
= table_data_size ( type
, data
);
292 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
298 d
-> minimum_width
= minimum_width
;
299 d
-> maximum_width
= maximum_width
;
301 d
-> align_percent
= align_percent
;
302 d
-> ellipsize_percent
= ellipsize_percent
;
303 memcpy_safe ( d
-> data
, data
, data_size
);
308 int table_add_cell_full (
310 TableCell
** ret_cell
,
313 size_t minimum_width
,
314 size_t maximum_width
,
316 unsigned align_percent
,
317 unsigned ellipsize_percent
) {
319 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
324 assert ( type
< _TABLE_DATA_TYPE_MAX
);
326 /* Determine the cell adjacent to the current one, but one row up */
327 if ( t
-> n_cells
>= t
-> n_columns
)
328 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
332 /* If formatting parameters are left unspecified, copy from the previous row */
333 if ( minimum_width
== ( size_t ) - 1 )
334 minimum_width
= p
? p
-> minimum_width
: 1 ;
336 if ( weight
== ( unsigned ) - 1 )
337 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
339 if ( align_percent
== ( unsigned ) - 1 )
340 align_percent
= p
? p
-> align_percent
: 0 ;
342 if ( ellipsize_percent
== ( unsigned ) - 1 )
343 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
345 assert ( align_percent
<= 100 );
346 assert ( ellipsize_percent
<= 100 );
348 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
349 * formatting. Let's see if we can reuse the cell data and ref it once more. */
351 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
352 d
= table_data_ref ( p
);
354 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
359 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
363 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
365 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
370 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
375 /* Add the data of the specified cell a second time as a new cell to the end. */
377 i
= TABLE_CELL_TO_INDEX ( cell
);
381 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
384 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
388 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
389 _cleanup_free_
char * curl
= NULL
;
395 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
396 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
398 i
= TABLE_CELL_TO_INDEX ( cell
);
402 assert_se ( od
= t
-> data
[ i
]);
406 assert ( od
-> n_ref
> 1 );
409 curl
= strdup ( od
-> url
);
421 od
-> ellipsize_percent
);
425 nd
-> color
= od
-> color
;
426 nd
-> url
= TAKE_PTR ( curl
);
428 table_data_unref ( od
);
431 assert ( nd
-> n_ref
== 1 );
436 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
442 /* Get the data object of the specified cell, or NULL if it doesn't exist */
444 i
= TABLE_CELL_TO_INDEX ( cell
);
449 assert ( t
-> data
[ i
]-> n_ref
> 0 );
454 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
460 if ( minimum_width
== ( size_t ) - 1 )
463 r
= table_dedup_cell ( t
, cell
);
467 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
471 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
477 r
= table_dedup_cell ( t
, cell
);
481 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
485 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
491 if ( weight
== ( unsigned ) - 1 )
492 weight
= DEFAULT_WEIGHT
;
494 r
= table_dedup_cell ( t
, cell
);
498 table_get_data ( t
, cell
)-> weight
= weight
;
502 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
508 if ( percent
== ( unsigned ) - 1 )
511 assert ( percent
<= 100 );
513 r
= table_dedup_cell ( t
, cell
);
517 table_get_data ( t
, cell
)-> align_percent
= percent
;
521 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
527 if ( percent
== ( unsigned ) - 1 )
530 assert ( percent
<= 100 );
532 r
= table_dedup_cell ( t
, cell
);
536 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
540 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
546 r
= table_dedup_cell ( t
, cell
);
550 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
554 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
555 _cleanup_free_
char * copy
= NULL
;
567 r
= table_dedup_cell ( t
, cell
);
571 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
574 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
580 assert ( first_type
>= 0 );
581 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
585 va_start ( ap
, first_type
);
604 data
= va_arg ( ap
, const char *);
608 buffer
. b
= va_arg ( ap
, int );
612 case TABLE_TIMESTAMP
:
614 buffer
. usec
= va_arg ( ap
, usec_t
);
619 buffer
. size
= va_arg ( ap
, uint64_t );
624 buffer
. uint32
= va_arg ( ap
, uint32_t );
625 data
= & buffer
. uint32
;
629 buffer
. uint64
= va_arg ( ap
, uint64_t );
630 data
= & buffer
. uint64
;
634 buffer
. percent
= va_arg ( ap
, int );
635 data
= & buffer
. percent
;
638 case _TABLE_DATA_TYPE_MAX
:
639 /* Used as end marker */
644 assert_not_reached ( "Uh? Unexpected data type." );
647 r
= table_add_cell ( t
, NULL
, type
, data
);
653 type
= va_arg ( ap
, TableDataType
);
657 void table_set_header ( Table
* t
, bool b
) {
663 void table_set_width ( Table
* t
, size_t width
) {
669 int table_set_display ( Table
* t
, size_t first_column
, ...) {
670 size_t allocated
, column
;
675 allocated
= t
-> n_display_map
;
676 column
= first_column
;
678 va_start ( ap
, first_column
);
680 assert ( column
< t
-> n_columns
);
682 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
687 t
-> display_map
[ t
-> n_display_map
++] = column
;
689 column
= va_arg ( ap
, size_t );
690 if ( column
== ( size_t ) - 1 )
699 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
700 size_t allocated
, column
;
705 allocated
= t
-> n_sort_map
;
706 column
= first_column
;
708 va_start ( ap
, first_column
);
710 assert ( column
< t
-> n_columns
);
712 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
717 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
719 column
= va_arg ( ap
, size_t );
720 if ( column
== ( size_t ) - 1 )
728 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
732 if ( a
-> type
== b
-> type
) {
734 /* We only define ordering for cells of the same data type. If cells with different data types are
735 * compared we follow the order the cells were originally added in */
740 return strcmp ( a
-> string
, b
-> string
);
743 if (! a
-> boolean
&& b
-> boolean
)
745 if ( a
-> boolean
&& ! b
-> boolean
)
749 case TABLE_TIMESTAMP
:
750 return CMP ( a
-> timestamp
, b
-> timestamp
);
753 return CMP ( a
-> timespan
, b
-> timespan
);
756 return CMP ( a
-> size
, b
-> size
);
759 return CMP ( a
-> uint32
, b
-> uint32
);
762 return CMP ( a
-> uint64
, b
-> uint64
);
765 return CMP ( a
-> percent
, b
-> percent
);
772 /* Generic fallback using the orginal order in which the cells where added. */
773 return CMP ( index_a
, index_b
);
776 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
783 /* Make sure the header stays at the beginning */
784 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
786 if (* a
< t
-> n_columns
)
788 if (* b
< t
-> n_columns
)
791 /* Order other lines by the sorting map */
792 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
795 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
796 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
798 r
= cell_data_compare ( d
, * a
, dd
, * b
);
803 /* Order identical lines by the order there were originally added in */
807 static const char * table_data_format ( TableData
* d
) {
821 return yes_no ( d
-> boolean
);
823 case TABLE_TIMESTAMP
: {
824 _cleanup_free_
char * p
;
826 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
830 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
833 d
-> formatted
= TAKE_PTR ( p
);
837 case TABLE_TIMESPAN
: {
838 _cleanup_free_
char * p
;
840 p
= new ( char , FORMAT_TIMESPAN_MAX
);
844 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timestamp
, 0 ))
847 d
-> formatted
= TAKE_PTR ( p
);
852 _cleanup_free_
char * p
;
854 p
= new ( char , FORMAT_BYTES_MAX
);
858 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
861 d
-> formatted
= TAKE_PTR ( p
);
866 _cleanup_free_
char * p
;
868 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
872 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
873 d
-> formatted
= TAKE_PTR ( p
);
878 _cleanup_free_
char * p
;
880 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
884 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
885 d
-> formatted
= TAKE_PTR ( p
);
889 case TABLE_PERCENT
: {
890 _cleanup_free_
char * p
;
892 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
896 sprintf ( p
, "%i%%" , d
-> percent
);
897 d
-> formatted
= TAKE_PTR ( p
);
902 assert_not_reached ( "Unexpected type?" );
908 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
912 t
= table_data_format ( d
);
916 l
= utf8_console_width ( t
);
917 if ( l
== ( size_t ) - 1 )
920 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
921 l
= d
-> maximum_width
;
923 if ( l
< d
-> minimum_width
)
924 l
= d
-> minimum_width
;
930 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
931 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
932 _cleanup_free_
char * clickable
= NULL
;
938 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
941 assert ( percent
<= 100 );
943 old_length
= strlen ( str
);
946 r
= terminal_urlify ( url
, str
, & clickable
);
950 clickable_length
= strlen ( clickable
);
952 clickable_length
= old_length
;
954 /* Determine current width on screen */
956 while ( p
< str
+ old_length
) {
959 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
960 p
++, w
++; /* count invalid chars as 1 */
964 p
= utf8_next_char ( p
);
965 w
+= unichar_iswide ( c
) ? 2 : 1 ;
968 /* Already wider than the target, if so, don't do anything */
970 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
972 /* How much spaces shall we add? An how much on the left side? */
973 space
= new_length
- w
;
974 lspace
= space
* percent
/ 100U ;
976 ret
= new ( char , space
+ clickable_length
+ 1 );
980 for ( i
= 0 ; i
< lspace
; i
++)
982 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
983 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
986 ret
[ space
+ clickable_length
] = 0 ;
990 int table_print ( Table
* t
, FILE * f
) {
991 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
992 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
994 _cleanup_free_
size_t * sorted
= NULL
;
995 uint64_t * column_weight
, weight_sum
;
1003 /* Ensure we have no incomplete rows */
1004 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1006 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1007 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1010 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1012 sorted
= new ( size_t , n_rows
);
1016 for ( i
= 0 ; i
< n_rows
; i
++)
1017 sorted
[ i
] = i
* t
-> n_columns
;
1019 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1023 display_columns
= t
-> n_display_map
;
1025 display_columns
= t
-> n_columns
;
1027 assert ( display_columns
> 0 );
1029 minimum_width
= newa ( size_t , display_columns
);
1030 maximum_width
= newa ( size_t , display_columns
);
1031 requested_width
= newa ( size_t , display_columns
);
1032 width
= newa ( size_t , display_columns
);
1033 column_weight
= newa0 ( uint64_t , display_columns
);
1035 for ( j
= 0 ; j
< display_columns
; j
++) {
1036 minimum_width
[ j
] = 1 ;
1037 maximum_width
[ j
] = ( size_t ) - 1 ;
1038 requested_width
[ j
] = ( size_t ) - 1 ;
1041 /* First pass: determine column sizes */
1042 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1045 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1046 * hence we don't care for sorted[] during the first pass. */
1047 row
= t
-> data
+ i
* t
-> n_columns
;
1049 for ( j
= 0 ; j
< display_columns
; j
++) {
1053 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1055 r
= table_data_requested_width ( d
, & req
);
1059 /* Determine the biggest width that any cell in this column would like to have */
1060 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1061 requested_width
[ j
] < req
)
1062 requested_width
[ j
] = req
;
1064 /* Determine the minimum width any cell in this column needs */
1065 if ( minimum_width
[ j
] < d
-> minimum_width
)
1066 minimum_width
[ j
] = d
-> minimum_width
;
1068 /* Determine the maximum width any cell in this column needs */
1069 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1070 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1071 maximum_width
[ j
] > d
-> maximum_width
))
1072 maximum_width
[ j
] = d
-> maximum_width
;
1074 /* Determine the full columns weight */
1075 column_weight
[ j
] += d
-> weight
;
1079 /* One space between each column */
1080 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1082 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1084 for ( j
= 0 ; j
< display_columns
; j
++) {
1085 weight_sum
+= column_weight
[ j
];
1087 table_minimum_width
+= minimum_width
[ j
];
1089 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1090 table_maximum_width
= ( size_t ) - 1 ;
1092 table_maximum_width
+= maximum_width
[ j
];
1094 table_requested_width
+= requested_width
[ j
];
1097 /* Calculate effective table width */
1098 if ( t
-> width
== ( size_t ) - 1 )
1099 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1101 table_effective_width
= t
-> width
;
1103 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1104 table_effective_width
= table_maximum_width
;
1106 if ( table_effective_width
< table_minimum_width
)
1107 table_effective_width
= table_minimum_width
;
1109 if ( table_effective_width
>= table_requested_width
) {
1112 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1113 * each column with what it asked for and the distribute the rest. */
1115 extra
= table_effective_width
- table_requested_width
;
1117 for ( j
= 0 ; j
< display_columns
; j
++) {
1120 if ( weight_sum
== 0 )
1121 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1123 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1125 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1126 width
[ j
] = maximum_width
[ j
];
1128 if ( width
[ j
] < minimum_width
[ j
])
1129 width
[ j
] = minimum_width
[ j
];
1131 assert ( width
[ j
] >= requested_width
[ j
]);
1132 delta
= width
[ j
] - requested_width
[ j
];
1134 /* Subtract what we just added from the rest */
1140 assert ( weight_sum
>= column_weight
[ j
]);
1141 weight_sum
-= column_weight
[ j
];
1145 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1146 * with the minimum they need, and then distribute anything left. */
1147 bool finalize
= false ;
1150 extra
= table_effective_width
- table_minimum_width
;
1152 for ( j
= 0 ; j
< display_columns
; j
++)
1153 width
[ j
] = ( size_t ) - 1 ;
1156 bool restart
= false ;
1158 for ( j
= 0 ; j
< display_columns
; j
++) {
1161 /* Did this column already get something assigned? If so, let's skip to the next */
1162 if ( width
[ j
] != ( size_t ) - 1 )
1165 if ( weight_sum
== 0 )
1166 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1168 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1170 if ( w
>= requested_width
[ j
]) {
1171 /* Never give more than requested. If we hit a column like this, there's more
1172 * space to allocate to other columns which means we need to restart the
1173 * iteration. However, if we hit a column like this, let's assign it the space
1174 * it wanted for good early.*/
1176 w
= requested_width
[ j
];
1179 } else if (! finalize
)
1184 assert ( w
>= minimum_width
[ j
]);
1185 delta
= w
- minimum_width
[ j
];
1187 assert ( delta
<= extra
);
1190 assert ( weight_sum
>= column_weight
[ j
]);
1191 weight_sum
-= column_weight
[ j
];
1193 if ( restart
&& ! finalize
)
1205 /* Second pass: show output */
1206 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1210 row
= t
-> data
+ sorted
[ i
];
1212 row
= t
-> data
+ i
* t
-> n_columns
;
1214 for ( j
= 0 ; j
< display_columns
; j
++) {
1215 _cleanup_free_
char * buffer
= NULL
;
1220 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1222 field
= table_data_format ( d
);
1226 l
= utf8_console_width ( field
);
1228 /* Field is wider than allocated space. Let's ellipsize */
1230 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1236 } else if ( l
< width
[ j
]) {
1237 /* Field is shorter than allocated space. Let's align with spaces */
1239 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1246 if ( l
>= width
[ j
] && d
-> url
) {
1247 _cleanup_free_
char * clickable
= NULL
;
1249 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1253 free_and_replace ( buffer
, clickable
);
1258 fputc ( ' ' , f
); /* column separator */
1260 if ( d
-> color
&& colors_enabled ())
1265 if ( d
-> color
&& colors_enabled ())
1266 fputs ( ANSI_NORMAL
, f
);
1272 return fflush_and_check ( f
);
1275 int table_format ( Table
* t
, char ** ret
) {
1276 _cleanup_fclose_
FILE * f
= NULL
;
1281 f
= open_memstream (& buf
, & sz
);
1285 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1287 r
= table_print ( t
, f
);
1298 size_t table_get_rows ( Table
* t
) {
1302 assert ( t
-> n_columns
> 0 );
1303 return t
-> n_cells
/ t
-> n_columns
;
1306 size_t table_get_columns ( Table
* t
) {
1310 assert ( t
-> n_columns
> 0 );
1311 return t
-> n_columns
;