]>
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 */
73 /* … add more here as we start supporting more cell data types … */
77 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
82 i
= PTR_TO_SIZE ( cell
);
88 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
89 assert ( index
!= ( size_t ) - 1 );
90 return SIZE_TO_PTR ( index
+ 1 );
97 bool header
; /* Whether to show the header row? */
98 size_t width
; /* If != (size_t) -1 the width to format this table in */
103 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 */
104 size_t n_display_map
;
106 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
110 Table
* table_new_raw ( size_t n_columns
) {
111 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
113 assert ( n_columns
> 0 );
119 * t
= ( struct Table
) {
120 . n_columns
= n_columns
,
122 . width
= ( size_t ) - 1 ,
128 Table
* table_new_internal ( const char * first_header
, ...) {
129 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
130 size_t n_columns
= 1 ;
134 assert ( first_header
);
136 va_start ( ap
, first_header
);
140 h
= va_arg ( ap
, const char *);
148 t
= table_new_raw ( n_columns
);
152 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, first_header
);
156 va_start ( ap
, first_header
);
160 h
= va_arg ( ap
, const char *);
164 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, h
);
172 assert ( t
-> n_columns
== t
-> n_cells
);
176 static TableData
* table_data_free ( TableData
* d
) {
185 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
186 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
188 Table
* table_unref ( Table
* t
) {
194 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
195 table_data_unref ( t
-> data
[ i
]);
198 free ( t
-> display_map
);
204 static size_t table_data_size ( TableDataType type
, const void * data
) {
212 return strlen ( data
) + 1 ;
217 case TABLE_TIMESTAMP
:
219 return sizeof ( usec_t
);
222 return sizeof ( uint64_t );
225 return sizeof ( uint32_t );
228 assert_not_reached ( "Uh? Unexpected cell type" );
232 static bool table_data_matches (
236 size_t minimum_width
,
237 size_t maximum_width
,
239 unsigned align_percent
,
240 unsigned ellipsize_percent
) {
248 if ( d
-> minimum_width
!= minimum_width
)
251 if ( d
-> maximum_width
!= maximum_width
)
254 if ( d
-> weight
!= weight
)
257 if ( d
-> align_percent
!= align_percent
)
260 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
263 k
= table_data_size ( type
, data
);
264 l
= table_data_size ( d
-> type
, d
-> data
);
269 return memcmp ( data
, d
-> data
, l
) == 0 ;
272 static TableData
* table_data_new (
275 size_t minimum_width
,
276 size_t maximum_width
,
278 unsigned align_percent
,
279 unsigned ellipsize_percent
) {
284 data_size
= table_data_size ( type
, data
);
286 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
292 d
-> minimum_width
= minimum_width
;
293 d
-> maximum_width
= maximum_width
;
295 d
-> align_percent
= align_percent
;
296 d
-> ellipsize_percent
= ellipsize_percent
;
297 memcpy_safe ( d
-> data
, data
, data_size
);
302 int table_add_cell_full (
304 TableCell
** ret_cell
,
307 size_t minimum_width
,
308 size_t maximum_width
,
310 unsigned align_percent
,
311 unsigned ellipsize_percent
) {
313 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
318 assert ( type
< _TABLE_DATA_TYPE_MAX
);
320 /* Determine the cell adjacent to the current one, but one row up */
321 if ( t
-> n_cells
>= t
-> n_columns
)
322 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
326 /* If formatting parameters are left unspecified, copy from the previous row */
327 if ( minimum_width
== ( size_t ) - 1 )
328 minimum_width
= p
? p
-> minimum_width
: 1 ;
330 if ( weight
== ( unsigned ) - 1 )
331 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
333 if ( align_percent
== ( unsigned ) - 1 )
334 align_percent
= p
? p
-> align_percent
: 0 ;
336 if ( ellipsize_percent
== ( unsigned ) - 1 )
337 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
339 assert ( align_percent
<= 100 );
340 assert ( ellipsize_percent
<= 100 );
342 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
343 * formatting. Let's see if we can reuse the cell data and ref it once more. */
345 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
346 d
= table_data_ref ( p
);
348 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
353 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
357 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
359 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
364 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
369 /* Add the data of the specified cell a second time as a new cell to the end. */
371 i
= TABLE_CELL_TO_INDEX ( cell
);
375 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
378 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
382 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
383 _cleanup_free_
char * curl
= NULL
;
389 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
390 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
392 i
= TABLE_CELL_TO_INDEX ( cell
);
396 assert_se ( od
= t
-> data
[ i
]);
400 assert ( od
-> n_ref
> 1 );
403 curl
= strdup ( od
-> url
);
415 od
-> ellipsize_percent
);
419 nd
-> color
= od
-> color
;
420 nd
-> url
= TAKE_PTR ( curl
);
422 table_data_unref ( od
);
425 assert ( nd
-> n_ref
== 1 );
430 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
436 /* Get the data object of the specified cell, or NULL if it doesn't exist */
438 i
= TABLE_CELL_TO_INDEX ( cell
);
443 assert ( t
-> data
[ i
]-> n_ref
> 0 );
448 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
454 if ( minimum_width
== ( size_t ) - 1 )
457 r
= table_dedup_cell ( t
, cell
);
461 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
465 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
471 r
= table_dedup_cell ( t
, cell
);
475 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
479 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
485 if ( weight
== ( unsigned ) - 1 )
486 weight
= DEFAULT_WEIGHT
;
488 r
= table_dedup_cell ( t
, cell
);
492 table_get_data ( t
, cell
)-> weight
= weight
;
496 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
502 if ( percent
== ( unsigned ) - 1 )
505 assert ( percent
<= 100 );
507 r
= table_dedup_cell ( t
, cell
);
511 table_get_data ( t
, cell
)-> align_percent
= percent
;
515 int table_set_ellipsize_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
)-> ellipsize_percent
= percent
;
534 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
540 r
= table_dedup_cell ( t
, cell
);
544 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
548 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
549 _cleanup_free_
char * copy
= NULL
;
561 r
= table_dedup_cell ( t
, cell
);
565 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
568 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
574 assert ( first_type
>= 0 );
575 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
579 va_start ( ap
, first_type
);
596 data
= va_arg ( ap
, const char *);
600 buffer
. b
= va_arg ( ap
, int );
604 case TABLE_TIMESTAMP
:
606 buffer
. usec
= va_arg ( ap
, usec_t
);
611 buffer
. size
= va_arg ( ap
, uint64_t );
616 buffer
. uint32
= va_arg ( ap
, uint32_t );
617 data
= & buffer
. uint32
;
620 case _TABLE_DATA_TYPE_MAX
:
621 /* Used as end marker */
626 assert_not_reached ( "Uh? Unexpected data type." );
629 r
= table_add_cell ( t
, NULL
, type
, data
);
635 type
= va_arg ( ap
, TableDataType
);
639 void table_set_header ( Table
* t
, bool b
) {
645 void table_set_width ( Table
* t
, size_t width
) {
651 int table_set_display ( Table
* t
, size_t first_column
, ...) {
652 size_t allocated
, column
;
657 allocated
= t
-> n_display_map
;
658 column
= first_column
;
660 va_start ( ap
, first_column
);
662 assert ( column
< t
-> n_columns
);
664 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
669 t
-> display_map
[ t
-> n_display_map
++] = column
;
671 column
= va_arg ( ap
, size_t );
672 if ( column
== ( size_t ) - 1 )
681 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
682 size_t allocated
, column
;
687 allocated
= t
-> n_sort_map
;
688 column
= first_column
;
690 va_start ( ap
, first_column
);
692 assert ( column
< t
-> n_columns
);
694 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
699 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
701 column
= va_arg ( ap
, size_t );
702 if ( column
== ( size_t ) - 1 )
710 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
714 if ( a
-> type
== b
-> type
) {
716 /* We only define ordering for cells of the same data type. If cells with different data types are
717 * compared we follow the order the cells were originally added in */
722 return strcmp ( a
-> string
, b
-> string
);
725 if (! a
-> boolean
&& b
-> boolean
)
727 if ( a
-> boolean
&& ! b
-> boolean
)
731 case TABLE_TIMESTAMP
:
732 return CMP ( a
-> timestamp
, b
-> timestamp
);
735 return CMP ( a
-> timespan
, b
-> timespan
);
738 return CMP ( a
-> size
, b
-> size
);
741 return CMP ( a
-> uint32
, b
-> uint32
);
748 /* Generic fallback using the orginal order in which the cells where added. */
749 return CMP ( index_a
, index_b
);
752 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
759 /* Make sure the header stays at the beginning */
760 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
762 if (* a
< t
-> n_columns
)
764 if (* b
< t
-> n_columns
)
767 /* Order other lines by the sorting map */
768 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
771 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
772 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
774 r
= cell_data_compare ( d
, * a
, dd
, * b
);
779 /* Order identical lines by the order there were originally added in */
783 static const char * table_data_format ( TableData
* d
) {
797 return yes_no ( d
-> boolean
);
799 case TABLE_TIMESTAMP
: {
800 _cleanup_free_
char * p
;
802 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
806 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
809 d
-> formatted
= TAKE_PTR ( p
);
813 case TABLE_TIMESPAN
: {
814 _cleanup_free_
char * p
;
816 p
= new ( char , FORMAT_TIMESPAN_MAX
);
820 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timestamp
, 0 ))
823 d
-> formatted
= TAKE_PTR ( p
);
828 _cleanup_free_
char * p
;
830 p
= new ( char , FORMAT_BYTES_MAX
);
834 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
837 d
-> formatted
= TAKE_PTR ( p
);
842 _cleanup_free_
char * p
;
844 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
848 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
849 d
-> formatted
= TAKE_PTR ( p
);
854 assert_not_reached ( "Unexpected type?" );
860 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
864 t
= table_data_format ( d
);
868 l
= utf8_console_width ( t
);
869 if ( l
== ( size_t ) - 1 )
872 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
873 l
= d
-> maximum_width
;
875 if ( l
< d
-> minimum_width
)
876 l
= d
-> minimum_width
;
882 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
883 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
884 _cleanup_free_
char * clickable
= NULL
;
890 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
893 assert ( percent
<= 100 );
895 old_length
= strlen ( str
);
898 r
= terminal_urlify ( url
, str
, & clickable
);
902 clickable_length
= strlen ( clickable
);
904 clickable_length
= old_length
;
906 /* Determine current width on screen */
908 while ( p
< str
+ old_length
) {
911 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
912 p
++, w
++; /* count invalid chars as 1 */
916 p
= utf8_next_char ( p
);
917 w
+= unichar_iswide ( c
) ? 2 : 1 ;
920 /* Already wider than the target, if so, don't do anything */
922 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
924 /* How much spaces shall we add? An how much on the left side? */
925 space
= new_length
- w
;
926 lspace
= space
* percent
/ 100U ;
928 ret
= new ( char , space
+ clickable_length
+ 1 );
932 for ( i
= 0 ; i
< lspace
; i
++)
934 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
935 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
938 ret
[ space
+ clickable_length
] = 0 ;
942 int table_print ( Table
* t
, FILE * f
) {
943 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
944 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
946 _cleanup_free_
size_t * sorted
= NULL
;
947 uint64_t * column_weight
, weight_sum
;
955 /* Ensure we have no incomplete rows */
956 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
958 n_rows
= t
-> n_cells
/ t
-> n_columns
;
959 assert ( n_rows
> 0 ); /* at least the header row must be complete */
962 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
964 sorted
= new ( size_t , n_rows
);
968 for ( i
= 0 ; i
< n_rows
; i
++)
969 sorted
[ i
] = i
* t
-> n_columns
;
971 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
975 display_columns
= t
-> n_display_map
;
977 display_columns
= t
-> n_columns
;
979 assert ( display_columns
> 0 );
981 minimum_width
= newa ( size_t , display_columns
);
982 maximum_width
= newa ( size_t , display_columns
);
983 requested_width
= newa ( size_t , display_columns
);
984 width
= newa ( size_t , display_columns
);
985 column_weight
= newa0 ( uint64_t , display_columns
);
987 for ( j
= 0 ; j
< display_columns
; j
++) {
988 minimum_width
[ j
] = 1 ;
989 maximum_width
[ j
] = ( size_t ) - 1 ;
990 requested_width
[ j
] = ( size_t ) - 1 ;
993 /* First pass: determine column sizes */
994 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
997 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
998 * hence we don't care for sorted[] during the first pass. */
999 row
= t
-> data
+ i
* t
-> n_columns
;
1001 for ( j
= 0 ; j
< display_columns
; j
++) {
1005 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1007 r
= table_data_requested_width ( d
, & req
);
1011 /* Determine the biggest width that any cell in this column would like to have */
1012 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1013 requested_width
[ j
] < req
)
1014 requested_width
[ j
] = req
;
1016 /* Determine the minimum width any cell in this column needs */
1017 if ( minimum_width
[ j
] < d
-> minimum_width
)
1018 minimum_width
[ j
] = d
-> minimum_width
;
1020 /* Determine the maximum width any cell in this column needs */
1021 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1022 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1023 maximum_width
[ j
] > d
-> maximum_width
))
1024 maximum_width
[ j
] = d
-> maximum_width
;
1026 /* Determine the full columns weight */
1027 column_weight
[ j
] += d
-> weight
;
1031 /* One space between each column */
1032 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1034 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1036 for ( j
= 0 ; j
< display_columns
; j
++) {
1037 weight_sum
+= column_weight
[ j
];
1039 table_minimum_width
+= minimum_width
[ j
];
1041 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1042 table_maximum_width
= ( size_t ) - 1 ;
1044 table_maximum_width
+= maximum_width
[ j
];
1046 table_requested_width
+= requested_width
[ j
];
1049 /* Calculate effective table width */
1050 if ( t
-> width
== ( size_t ) - 1 )
1051 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1053 table_effective_width
= t
-> width
;
1055 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1056 table_effective_width
= table_maximum_width
;
1058 if ( table_effective_width
< table_minimum_width
)
1059 table_effective_width
= table_minimum_width
;
1061 if ( table_effective_width
>= table_requested_width
) {
1064 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1065 * each column with what it asked for and the distribute the rest. */
1067 extra
= table_effective_width
- table_requested_width
;
1069 for ( j
= 0 ; j
< display_columns
; j
++) {
1072 if ( weight_sum
== 0 )
1073 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1075 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1077 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1078 width
[ j
] = maximum_width
[ j
];
1080 if ( width
[ j
] < minimum_width
[ j
])
1081 width
[ j
] = minimum_width
[ j
];
1083 assert ( width
[ j
] >= requested_width
[ j
]);
1084 delta
= width
[ j
] - requested_width
[ j
];
1086 /* Subtract what we just added from the rest */
1092 assert ( weight_sum
>= column_weight
[ j
]);
1093 weight_sum
-= column_weight
[ j
];
1097 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1098 * with the minimum they need, and then distribute anything left. */
1099 bool finalize
= false ;
1102 extra
= table_effective_width
- table_minimum_width
;
1104 for ( j
= 0 ; j
< display_columns
; j
++)
1105 width
[ j
] = ( size_t ) - 1 ;
1108 bool restart
= false ;
1110 for ( j
= 0 ; j
< display_columns
; j
++) {
1113 /* Did this column already get something assigned? If so, let's skip to the next */
1114 if ( width
[ j
] != ( size_t ) - 1 )
1117 if ( weight_sum
== 0 )
1118 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1120 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1122 if ( w
>= requested_width
[ j
]) {
1123 /* Never give more than requested. If we hit a column like this, there's more
1124 * space to allocate to other columns which means we need to restart the
1125 * iteration. However, if we hit a column like this, let's assign it the space
1126 * it wanted for good early.*/
1128 w
= requested_width
[ j
];
1131 } else if (! finalize
)
1136 assert ( w
>= minimum_width
[ j
]);
1137 delta
= w
- minimum_width
[ j
];
1139 assert ( delta
<= extra
);
1142 assert ( weight_sum
>= column_weight
[ j
]);
1143 weight_sum
-= column_weight
[ j
];
1145 if ( restart
&& ! finalize
)
1157 /* Second pass: show output */
1158 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1162 row
= t
-> data
+ sorted
[ i
];
1164 row
= t
-> data
+ i
* t
-> n_columns
;
1166 for ( j
= 0 ; j
< display_columns
; j
++) {
1167 _cleanup_free_
char * buffer
= NULL
;
1172 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1174 field
= table_data_format ( d
);
1178 l
= utf8_console_width ( field
);
1180 /* Field is wider than allocated space. Let's ellipsize */
1182 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1188 } else if ( l
< width
[ j
]) {
1189 /* Field is shorter than allocated space. Let's align with spaces */
1191 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1198 if ( l
>= width
[ j
] && d
-> url
) {
1199 _cleanup_free_
char * clickable
= NULL
;
1201 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1205 free_and_replace ( buffer
, clickable
);
1210 fputc ( ' ' , f
); /* column separator */
1218 fputs ( ansi_normal (), f
);
1224 return fflush_and_check ( f
);
1227 int table_format ( Table
* t
, char ** ret
) {
1228 _cleanup_fclose_
FILE * f
= NULL
;
1233 f
= open_memstream (& buf
, & sz
);
1237 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1239 r
= table_print ( t
, f
);
1250 size_t table_get_rows ( Table
* t
) {
1254 assert ( t
-> n_columns
> 0 );
1255 return t
-> n_cells
/ t
-> n_columns
;
1258 size_t table_get_columns ( Table
* t
) {
1262 assert ( t
-> n_columns
> 0 );
1263 return t
-> n_columns
;