]>
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. */
114 Table
* table_new_raw ( size_t n_columns
) {
115 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
117 assert ( n_columns
> 0 );
123 * t
= ( struct Table
) {
124 . n_columns
= n_columns
,
126 . width
= ( size_t ) - 1 ,
132 Table
* table_new_internal ( const char * first_header
, ...) {
133 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
134 size_t n_columns
= 1 ;
138 assert ( first_header
);
140 va_start ( ap
, first_header
);
144 h
= va_arg ( ap
, const char *);
152 t
= table_new_raw ( n_columns
);
156 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, first_header
);
160 va_start ( ap
, first_header
);
164 h
= va_arg ( ap
, const char *);
168 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, h
);
176 assert ( t
-> n_columns
== t
-> n_cells
);
180 static TableData
* table_data_free ( TableData
* d
) {
189 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
190 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
192 Table
* table_unref ( Table
* t
) {
198 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
199 table_data_unref ( t
-> data
[ i
]);
202 free ( t
-> display_map
);
204 free ( t
-> reverse_map
);
209 static size_t table_data_size ( TableDataType type
, const void * data
) {
217 return strlen ( data
) + 1 ;
222 case TABLE_TIMESTAMP
:
224 return sizeof ( usec_t
);
228 return sizeof ( uint64_t );
231 return sizeof ( uint32_t );
237 assert_not_reached ( "Uh? Unexpected cell type" );
241 static bool table_data_matches (
245 size_t minimum_width
,
246 size_t maximum_width
,
248 unsigned align_percent
,
249 unsigned ellipsize_percent
) {
257 if ( d
-> minimum_width
!= minimum_width
)
260 if ( d
-> maximum_width
!= maximum_width
)
263 if ( d
-> weight
!= weight
)
266 if ( d
-> align_percent
!= align_percent
)
269 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
272 k
= table_data_size ( type
, data
);
273 l
= table_data_size ( d
-> type
, d
-> data
);
278 return memcmp_safe ( data
, d
-> data
, l
) == 0 ;
281 static TableData
* table_data_new (
284 size_t minimum_width
,
285 size_t maximum_width
,
287 unsigned align_percent
,
288 unsigned ellipsize_percent
) {
293 data_size
= table_data_size ( type
, data
);
295 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
301 d
-> minimum_width
= minimum_width
;
302 d
-> maximum_width
= maximum_width
;
304 d
-> align_percent
= align_percent
;
305 d
-> ellipsize_percent
= ellipsize_percent
;
306 memcpy_safe ( d
-> data
, data
, data_size
);
311 int table_add_cell_full (
313 TableCell
** ret_cell
,
316 size_t minimum_width
,
317 size_t maximum_width
,
319 unsigned align_percent
,
320 unsigned ellipsize_percent
) {
322 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
327 assert ( type
< _TABLE_DATA_TYPE_MAX
);
329 /* Determine the cell adjacent to the current one, but one row up */
330 if ( t
-> n_cells
>= t
-> n_columns
)
331 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
335 /* If formatting parameters are left unspecified, copy from the previous row */
336 if ( minimum_width
== ( size_t ) - 1 )
337 minimum_width
= p
? p
-> minimum_width
: 1 ;
339 if ( weight
== ( unsigned ) - 1 )
340 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
342 if ( align_percent
== ( unsigned ) - 1 )
343 align_percent
= p
? p
-> align_percent
: 0 ;
345 if ( ellipsize_percent
== ( unsigned ) - 1 )
346 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
348 assert ( align_percent
<= 100 );
349 assert ( ellipsize_percent
<= 100 );
351 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
352 * formatting. Let's see if we can reuse the cell data and ref it once more. */
354 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
355 d
= table_data_ref ( p
);
357 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
362 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
366 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
368 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
373 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
378 /* Add the data of the specified cell a second time as a new cell to the end. */
380 i
= TABLE_CELL_TO_INDEX ( cell
);
384 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
387 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
391 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
392 _cleanup_free_
char * curl
= NULL
;
398 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
399 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
401 i
= TABLE_CELL_TO_INDEX ( cell
);
405 assert_se ( od
= t
-> data
[ i
]);
409 assert ( od
-> n_ref
> 1 );
412 curl
= strdup ( od
-> url
);
424 od
-> ellipsize_percent
);
428 nd
-> color
= od
-> color
;
429 nd
-> url
= TAKE_PTR ( curl
);
431 table_data_unref ( od
);
434 assert ( nd
-> n_ref
== 1 );
439 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
445 /* Get the data object of the specified cell, or NULL if it doesn't exist */
447 i
= TABLE_CELL_TO_INDEX ( cell
);
452 assert ( t
-> data
[ i
]-> n_ref
> 0 );
457 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
463 if ( minimum_width
== ( size_t ) - 1 )
466 r
= table_dedup_cell ( t
, cell
);
470 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
474 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
480 r
= table_dedup_cell ( t
, cell
);
484 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
488 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
494 if ( weight
== ( unsigned ) - 1 )
495 weight
= DEFAULT_WEIGHT
;
497 r
= table_dedup_cell ( t
, cell
);
501 table_get_data ( t
, cell
)-> weight
= weight
;
505 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
511 if ( percent
== ( unsigned ) - 1 )
514 assert ( percent
<= 100 );
516 r
= table_dedup_cell ( t
, cell
);
520 table_get_data ( t
, cell
)-> align_percent
= percent
;
524 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
530 if ( percent
== ( unsigned ) - 1 )
533 assert ( percent
<= 100 );
535 r
= table_dedup_cell ( t
, cell
);
539 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
543 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
549 r
= table_dedup_cell ( t
, cell
);
553 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
557 int table_set_url ( Table
* t
, TableCell
* cell
, const char * url
) {
558 _cleanup_free_
char * copy
= NULL
;
570 r
= table_dedup_cell ( t
, cell
);
574 return free_and_replace ( table_get_data ( t
, cell
)-> url
, copy
);
577 int table_update ( Table
* t
, TableCell
* cell
, TableDataType type
, const void * data
) {
578 _cleanup_free_
char * curl
= NULL
;
585 i
= TABLE_CELL_TO_INDEX ( cell
);
589 assert_se ( od
= t
-> data
[ i
]);
592 curl
= strdup ( od
-> url
);
604 od
-> ellipsize_percent
);
608 nd
-> color
= od
-> color
;
609 nd
-> url
= TAKE_PTR ( curl
);
611 table_data_unref ( od
);
617 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
623 assert ( first_type
>= 0 );
624 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
628 va_start ( ap
, first_type
);
647 data
= va_arg ( ap
, const char *);
651 buffer
. b
= va_arg ( ap
, int );
655 case TABLE_TIMESTAMP
:
657 buffer
. usec
= va_arg ( ap
, usec_t
);
662 buffer
. size
= va_arg ( ap
, uint64_t );
667 buffer
. uint32
= va_arg ( ap
, uint32_t );
668 data
= & buffer
. uint32
;
672 buffer
. uint64
= va_arg ( ap
, uint64_t );
673 data
= & buffer
. uint64
;
677 buffer
. percent
= va_arg ( ap
, int );
678 data
= & buffer
. percent
;
681 case _TABLE_DATA_TYPE_MAX
:
682 /* Used as end marker */
687 assert_not_reached ( "Uh? Unexpected data type." );
690 r
= table_add_cell ( t
, NULL
, type
, data
);
696 type
= va_arg ( ap
, TableDataType
);
700 void table_set_header ( Table
* t
, bool b
) {
706 void table_set_width ( Table
* t
, size_t width
) {
712 int table_set_display ( Table
* t
, size_t first_column
, ...) {
713 size_t allocated
, column
;
718 allocated
= t
-> n_display_map
;
719 column
= first_column
;
721 va_start ( ap
, first_column
);
723 assert ( column
< t
-> n_columns
);
725 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
730 t
-> display_map
[ t
-> n_display_map
++] = column
;
732 column
= va_arg ( ap
, size_t );
733 if ( column
== ( size_t ) - 1 )
742 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
743 size_t allocated
, column
;
748 allocated
= t
-> n_sort_map
;
749 column
= first_column
;
751 va_start ( ap
, first_column
);
753 assert ( column
< t
-> n_columns
);
755 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
760 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
762 column
= va_arg ( ap
, size_t );
763 if ( column
== ( size_t ) - 1 )
771 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
775 if ( a
-> type
== b
-> type
) {
777 /* We only define ordering for cells of the same data type. If cells with different data types are
778 * compared we follow the order the cells were originally added in */
783 return strcmp ( a
-> string
, b
-> string
);
786 if (! a
-> boolean
&& b
-> boolean
)
788 if ( a
-> boolean
&& ! b
-> boolean
)
792 case TABLE_TIMESTAMP
:
793 return CMP ( a
-> timestamp
, b
-> timestamp
);
796 return CMP ( a
-> timespan
, b
-> timespan
);
799 return CMP ( a
-> size
, b
-> size
);
802 return CMP ( a
-> uint32
, b
-> uint32
);
805 return CMP ( a
-> uint64
, b
-> uint64
);
808 return CMP ( a
-> percent
, b
-> percent
);
815 /* Generic fallback using the orginal order in which the cells where added. */
816 return CMP ( index_a
, index_b
);
819 static int table_data_compare ( const size_t * a
, const size_t * b
, Table
* t
) {
826 /* Make sure the header stays at the beginning */
827 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
829 if (* a
< t
-> n_columns
)
831 if (* b
< t
-> n_columns
)
834 /* Order other lines by the sorting map */
835 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
838 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
839 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
841 r
= cell_data_compare ( d
, * a
, dd
, * b
);
843 return t
-> reverse_map
&& t
-> reverse_map
[ t
-> sort_map
[ i
]] ? - r
: r
;
846 /* Order identical lines by the order there were originally added in */
850 static const char * table_data_format ( TableData
* d
) {
864 return yes_no ( d
-> boolean
);
866 case TABLE_TIMESTAMP
: {
867 _cleanup_free_
char * p
;
869 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
873 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
876 d
-> formatted
= TAKE_PTR ( p
);
880 case TABLE_TIMESPAN
: {
881 _cleanup_free_
char * p
;
883 p
= new ( char , FORMAT_TIMESPAN_MAX
);
887 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timestamp
, 0 ))
890 d
-> formatted
= TAKE_PTR ( p
);
895 _cleanup_free_
char * p
;
897 p
= new ( char , FORMAT_BYTES_MAX
);
901 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
904 d
-> formatted
= TAKE_PTR ( p
);
909 _cleanup_free_
char * p
;
911 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
915 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
916 d
-> formatted
= TAKE_PTR ( p
);
921 _cleanup_free_
char * p
;
923 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint64
) + 1 );
927 sprintf ( p
, "%" PRIu64
, d
-> uint64
);
928 d
-> formatted
= TAKE_PTR ( p
);
932 case TABLE_PERCENT
: {
933 _cleanup_free_
char * p
;
935 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> percent
) + 2 );
939 sprintf ( p
, "%i%%" , d
-> percent
);
940 d
-> formatted
= TAKE_PTR ( p
);
945 assert_not_reached ( "Unexpected type?" );
951 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
955 t
= table_data_format ( d
);
959 l
= utf8_console_width ( t
);
960 if ( l
== ( size_t ) - 1 )
963 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
964 l
= d
-> maximum_width
;
966 if ( l
< d
-> minimum_width
)
967 l
= d
-> minimum_width
;
973 static char * align_string_mem ( const char * str
, const char * url
, size_t new_length
, unsigned percent
) {
974 size_t w
= 0 , space
, lspace
, old_length
, clickable_length
;
975 _cleanup_free_
char * clickable
= NULL
;
981 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
984 assert ( percent
<= 100 );
986 old_length
= strlen ( str
);
989 r
= terminal_urlify ( url
, str
, & clickable
);
993 clickable_length
= strlen ( clickable
);
995 clickable_length
= old_length
;
997 /* Determine current width on screen */
999 while ( p
< str
+ old_length
) {
1002 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
1003 p
++, w
++; /* count invalid chars as 1 */
1007 p
= utf8_next_char ( p
);
1008 w
+= unichar_iswide ( c
) ? 2 : 1 ;
1011 /* Already wider than the target, if so, don't do anything */
1012 if ( w
>= new_length
)
1013 return clickable
? TAKE_PTR ( clickable
) : strdup ( str
);
1015 /* How much spaces shall we add? An how much on the left side? */
1016 space
= new_length
- w
;
1017 lspace
= space
* percent
/ 100U ;
1019 ret
= new ( char , space
+ clickable_length
+ 1 );
1023 for ( i
= 0 ; i
< lspace
; i
++)
1025 memcpy ( ret
+ lspace
, clickable
?: str
, clickable_length
);
1026 for ( i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1029 ret
[ space
+ clickable_length
] = 0 ;
1033 int table_print ( Table
* t
, FILE * f
) {
1034 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
1035 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1037 _cleanup_free_
size_t * sorted
= NULL
;
1038 uint64_t * column_weight
, weight_sum
;
1046 /* Ensure we have no incomplete rows */
1047 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
1049 n_rows
= t
-> n_cells
/ t
-> n_columns
;
1050 assert ( n_rows
> 0 ); /* at least the header row must be complete */
1053 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1055 sorted
= new ( size_t , n_rows
);
1059 for ( i
= 0 ; i
< n_rows
; i
++)
1060 sorted
[ i
] = i
* t
-> n_columns
;
1062 typesafe_qsort_r ( sorted
, n_rows
, table_data_compare
, t
);
1066 display_columns
= t
-> n_display_map
;
1068 display_columns
= t
-> n_columns
;
1070 assert ( display_columns
> 0 );
1072 minimum_width
= newa ( size_t , display_columns
);
1073 maximum_width
= newa ( size_t , display_columns
);
1074 requested_width
= newa ( size_t , display_columns
);
1075 width
= newa ( size_t , display_columns
);
1076 column_weight
= newa0 ( uint64_t , display_columns
);
1078 for ( j
= 0 ; j
< display_columns
; j
++) {
1079 minimum_width
[ j
] = 1 ;
1080 maximum_width
[ j
] = ( size_t ) - 1 ;
1081 requested_width
[ j
] = ( size_t ) - 1 ;
1084 /* First pass: determine column sizes */
1085 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1088 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1089 * hence we don't care for sorted[] during the first pass. */
1090 row
= t
-> data
+ i
* t
-> n_columns
;
1092 for ( j
= 0 ; j
< display_columns
; j
++) {
1096 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1098 r
= table_data_requested_width ( d
, & req
);
1102 /* Determine the biggest width that any cell in this column would like to have */
1103 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1104 requested_width
[ j
] < req
)
1105 requested_width
[ j
] = req
;
1107 /* Determine the minimum width any cell in this column needs */
1108 if ( minimum_width
[ j
] < d
-> minimum_width
)
1109 minimum_width
[ j
] = d
-> minimum_width
;
1111 /* Determine the maximum width any cell in this column needs */
1112 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1113 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1114 maximum_width
[ j
] > d
-> maximum_width
))
1115 maximum_width
[ j
] = d
-> maximum_width
;
1117 /* Determine the full columns weight */
1118 column_weight
[ j
] += d
-> weight
;
1122 /* One space between each column */
1123 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1125 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1127 for ( j
= 0 ; j
< display_columns
; j
++) {
1128 weight_sum
+= column_weight
[ j
];
1130 table_minimum_width
+= minimum_width
[ j
];
1132 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1133 table_maximum_width
= ( size_t ) - 1 ;
1135 table_maximum_width
+= maximum_width
[ j
];
1137 table_requested_width
+= requested_width
[ j
];
1140 /* Calculate effective table width */
1141 if ( t
-> width
== ( size_t ) - 1 )
1142 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1144 table_effective_width
= t
-> width
;
1146 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1147 table_effective_width
= table_maximum_width
;
1149 if ( table_effective_width
< table_minimum_width
)
1150 table_effective_width
= table_minimum_width
;
1152 if ( table_effective_width
>= table_requested_width
) {
1155 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1156 * each column with what it asked for and the distribute the rest. */
1158 extra
= table_effective_width
- table_requested_width
;
1160 for ( j
= 0 ; j
< display_columns
; j
++) {
1163 if ( weight_sum
== 0 )
1164 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1166 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1168 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1169 width
[ j
] = maximum_width
[ j
];
1171 if ( width
[ j
] < minimum_width
[ j
])
1172 width
[ j
] = minimum_width
[ j
];
1174 assert ( width
[ j
] >= requested_width
[ j
]);
1175 delta
= width
[ j
] - requested_width
[ j
];
1177 /* Subtract what we just added from the rest */
1183 assert ( weight_sum
>= column_weight
[ j
]);
1184 weight_sum
-= column_weight
[ j
];
1188 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1189 * with the minimum they need, and then distribute anything left. */
1190 bool finalize
= false ;
1193 extra
= table_effective_width
- table_minimum_width
;
1195 for ( j
= 0 ; j
< display_columns
; j
++)
1196 width
[ j
] = ( size_t ) - 1 ;
1199 bool restart
= false ;
1201 for ( j
= 0 ; j
< display_columns
; j
++) {
1204 /* Did this column already get something assigned? If so, let's skip to the next */
1205 if ( width
[ j
] != ( size_t ) - 1 )
1208 if ( weight_sum
== 0 )
1209 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1211 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1213 if ( w
>= requested_width
[ j
]) {
1214 /* Never give more than requested. If we hit a column like this, there's more
1215 * space to allocate to other columns which means we need to restart the
1216 * iteration. However, if we hit a column like this, let's assign it the space
1217 * it wanted for good early.*/
1219 w
= requested_width
[ j
];
1222 } else if (! finalize
)
1227 assert ( w
>= minimum_width
[ j
]);
1228 delta
= w
- minimum_width
[ j
];
1230 assert ( delta
<= extra
);
1233 assert ( weight_sum
>= column_weight
[ j
]);
1234 weight_sum
-= column_weight
[ j
];
1236 if ( restart
&& ! finalize
)
1248 /* Second pass: show output */
1249 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1253 row
= t
-> data
+ sorted
[ i
];
1255 row
= t
-> data
+ i
* t
-> n_columns
;
1257 for ( j
= 0 ; j
< display_columns
; j
++) {
1258 _cleanup_free_
char * buffer
= NULL
;
1263 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1265 field
= table_data_format ( d
);
1269 l
= utf8_console_width ( field
);
1271 /* Field is wider than allocated space. Let's ellipsize */
1273 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1279 } else if ( l
< width
[ j
]) {
1280 /* Field is shorter than allocated space. Let's align with spaces */
1282 buffer
= align_string_mem ( field
, d
-> url
, width
[ j
], d
-> align_percent
);
1289 if ( l
>= width
[ j
] && d
-> url
) {
1290 _cleanup_free_
char * clickable
= NULL
;
1292 r
= terminal_urlify ( d
-> url
, field
, & clickable
);
1296 free_and_replace ( buffer
, clickable
);
1300 if ( row
== t
-> data
) /* underline header line fully, including the column separator */
1301 fputs ( ansi_underline (), f
);
1304 fputc ( ' ' , f
); /* column separator */
1306 if ( d
-> color
&& colors_enabled ()) {
1307 if ( row
== t
-> data
) /* first undo header underliner */
1308 fputs ( ANSI_NORMAL
, f
);
1315 if ( colors_enabled () && ( d
-> color
|| row
== t
-> data
))
1316 fputs ( ANSI_NORMAL
, f
);
1322 return fflush_and_check ( f
);
1325 int table_format ( Table
* t
, char ** ret
) {
1326 _cleanup_fclose_
FILE * f
= NULL
;
1331 f
= open_memstream (& buf
, & sz
);
1335 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1337 r
= table_print ( t
, f
);
1348 size_t table_get_rows ( Table
* t
) {
1352 assert ( t
-> n_columns
> 0 );
1353 return t
-> n_cells
/ t
-> n_columns
;
1356 size_t table_get_columns ( Table
* t
) {
1360 assert ( t
-> n_columns
> 0 );
1361 return t
-> n_columns
;
1364 int table_set_reverse ( Table
* t
, size_t column
, bool b
) {
1366 assert ( column
< t
-> n_columns
);
1368 if (! t
-> reverse_map
) {
1372 t
-> reverse_map
= new0 ( bool , t
-> n_columns
);
1373 if (! t
-> reverse_map
)
1377 t
-> reverse_map
[ column
] = b
;
1381 TableCell
* table_get_cell ( Table
* t
, size_t row
, size_t column
) {
1386 if ( column
>= t
-> n_columns
)
1389 i
= row
* t
-> n_columns
+ column
;
1390 if ( i
>= t
-> n_cells
)
1393 return TABLE_INDEX_TO_CELL ( i
);
1396 const void * table_get ( Table
* t
, TableCell
* cell
) {
1401 d
= table_get_data ( t
, cell
);
1408 const void * table_get_at ( Table
* t
, size_t row
, size_t column
) {
1411 cell
= table_get_cell ( t
, row
, column
);
1415 return table_get ( t
, cell
);