]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/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 "string-util.h"
13 #include "terminal-util.h"
14 #include "time-util.h"
18 #define DEFAULT_WEIGHT 100
21 A few notes on implementation details:
23 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
24 table. It can be easily converted to an index number and back.
26 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
27 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
28 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
29 outside only sees Table and TableCell.
31 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
34 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
35 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
36 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
37 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
39 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
40 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
41 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
42 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
45 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
46 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
47 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
50 typedef struct TableData
{
54 size_t minimum_width
; /* minimum width for the column */
55 size_t maximum_width
; /* maximum width for the column */
56 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
57 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
58 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
60 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 */
61 char * formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
64 uint8_t data
[ 0 ]; /* data is generic array */
71 /* … add more here as we start supporting more cell data types … */
75 static size_t TABLE_CELL_TO_INDEX ( TableCell
* cell
) {
80 i
= PTR_TO_UINT ( cell
);
86 static TableCell
* TABLE_INDEX_TO_CELL ( size_t index
) {
87 assert ( index
!= ( size_t ) - 1 );
88 return UINT_TO_PTR (( unsigned ) ( index
+ 1 ));
95 bool header
; /* Whether to show the header row? */
96 size_t width
; /* If != (size_t) -1 the width to format this table in */
101 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 */
102 size_t n_display_map
;
104 size_t * sort_map
; /* The columns to order rows by, in order of preference. */
108 Table
* table_new_raw ( size_t n_columns
) {
109 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
111 assert ( n_columns
> 0 );
117 * t
= ( struct Table
) {
118 . n_columns
= n_columns
,
120 . width
= ( size_t ) - 1 ,
126 Table
* table_new_internal ( const char * first_header
, ...) {
127 _cleanup_ ( table_unrefp
) Table
* t
= NULL
;
128 size_t n_columns
= 1 ;
132 assert ( first_header
);
134 va_start ( ap
, first_header
);
138 h
= va_arg ( ap
, const char *);
146 t
= table_new_raw ( n_columns
);
150 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, first_header
);
154 va_start ( ap
, first_header
);
158 h
= va_arg ( ap
, const char *);
162 r
= table_add_cell ( t
, NULL
, TABLE_STRING
, h
);
170 assert ( t
-> n_columns
== t
-> n_cells
);
174 static TableData
* table_data_free ( TableData
* d
) {
181 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC ( TableData
, table_data
, table_data_free
);
182 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
184 Table
* table_unref ( Table
* t
) {
190 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
191 table_data_unref ( t
-> data
[ i
]);
194 free ( t
-> display_map
);
200 static size_t table_data_size ( TableDataType type
, const void * data
) {
208 return strlen ( data
) + 1 ;
213 case TABLE_TIMESTAMP
:
215 return sizeof ( usec_t
);
218 return sizeof ( uint64_t );
221 return sizeof ( uint32_t );
224 assert_not_reached ( "Uh? Unexpected cell type" );
228 static bool table_data_matches (
232 size_t minimum_width
,
233 size_t maximum_width
,
235 unsigned align_percent
,
236 unsigned ellipsize_percent
) {
244 if ( d
-> minimum_width
!= minimum_width
)
247 if ( d
-> maximum_width
!= maximum_width
)
250 if ( d
-> weight
!= weight
)
253 if ( d
-> align_percent
!= align_percent
)
256 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
259 k
= table_data_size ( type
, data
);
260 l
= table_data_size ( d
-> type
, d
-> data
);
265 return memcmp ( data
, d
-> data
, l
) == 0 ;
268 static TableData
* table_data_new (
271 size_t minimum_width
,
272 size_t maximum_width
,
274 unsigned align_percent
,
275 unsigned ellipsize_percent
) {
280 data_size
= table_data_size ( type
, data
);
282 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
288 d
-> minimum_width
= minimum_width
;
289 d
-> maximum_width
= maximum_width
;
291 d
-> align_percent
= align_percent
;
292 d
-> ellipsize_percent
= ellipsize_percent
;
293 memcpy_safe ( d
-> data
, data
, data_size
);
298 int table_add_cell_full (
300 TableCell
** ret_cell
,
303 size_t minimum_width
,
304 size_t maximum_width
,
306 unsigned align_percent
,
307 unsigned ellipsize_percent
) {
309 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
314 assert ( type
< _TABLE_DATA_TYPE_MAX
);
316 /* Determine the cell adjacent to the current one, but one row up */
317 if ( t
-> n_cells
>= t
-> n_columns
)
318 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
322 /* If formatting parameters are left unspecified, copy from the previous row */
323 if ( minimum_width
== ( size_t ) - 1 )
324 minimum_width
= p
? p
-> minimum_width
: 1 ;
326 if ( weight
== ( unsigned ) - 1 )
327 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
329 if ( align_percent
== ( unsigned ) - 1 )
330 align_percent
= p
? p
-> align_percent
: 0 ;
332 if ( ellipsize_percent
== ( unsigned ) - 1 )
333 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
335 assert ( align_percent
<= 100 );
336 assert ( ellipsize_percent
<= 100 );
338 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
339 * formatting. Let's see if we can reuse the cell data and ref it once more. */
341 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
342 d
= table_data_ref ( p
);
344 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
349 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
353 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
355 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
360 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
365 /* Add the data of the specified cell a second time as a new cell to the end. */
367 i
= TABLE_CELL_TO_INDEX ( cell
);
371 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
374 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
378 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
384 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
385 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
387 i
= TABLE_CELL_TO_INDEX ( cell
);
391 assert_se ( od
= t
-> data
[ i
]);
395 assert ( od
-> n_ref
> 1 );
397 nd
= table_data_new ( od
-> type
, od
-> data
, od
-> minimum_width
, od
-> maximum_width
, od
-> weight
, od
-> align_percent
, od
-> ellipsize_percent
);
401 table_data_unref ( od
);
404 assert ( nd
-> n_ref
== 1 );
409 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
415 /* Get the data object of the specified cell, or NULL if it doesn't exist */
417 i
= TABLE_CELL_TO_INDEX ( cell
);
422 assert ( t
-> data
[ i
]-> n_ref
> 0 );
427 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
433 if ( minimum_width
== ( size_t ) - 1 )
436 r
= table_dedup_cell ( t
, cell
);
440 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
444 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
450 r
= table_dedup_cell ( t
, cell
);
454 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
458 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
464 if ( weight
== ( unsigned ) - 1 )
465 weight
= DEFAULT_WEIGHT
;
467 r
= table_dedup_cell ( t
, cell
);
471 table_get_data ( t
, cell
)-> weight
= weight
;
475 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
481 if ( percent
== ( unsigned ) - 1 )
484 assert ( percent
<= 100 );
486 r
= table_dedup_cell ( t
, cell
);
490 table_get_data ( t
, cell
)-> align_percent
= percent
;
494 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
500 if ( percent
== ( unsigned ) - 1 )
503 assert ( percent
<= 100 );
505 r
= table_dedup_cell ( t
, cell
);
509 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
513 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
519 r
= table_dedup_cell ( t
, cell
);
523 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
527 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
533 assert ( first_type
>= 0 );
534 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
538 va_start ( ap
, first_type
);
555 data
= va_arg ( ap
, const char *);
559 buffer
. b
= va_arg ( ap
, int );
563 case TABLE_TIMESTAMP
:
565 buffer
. usec
= va_arg ( ap
, usec_t
);
570 buffer
. size
= va_arg ( ap
, uint64_t );
575 buffer
. uint32
= va_arg ( ap
, uint32_t );
576 data
= & buffer
. uint32
;
579 case _TABLE_DATA_TYPE_MAX
:
580 /* Used as end marker */
585 assert_not_reached ( "Uh? Unexpected data type." );
588 r
= table_add_cell ( t
, NULL
, type
, data
);
594 type
= va_arg ( ap
, TableDataType
);
598 void table_set_header ( Table
* t
, bool b
) {
604 void table_set_width ( Table
* t
, size_t width
) {
610 int table_set_display ( Table
* t
, size_t first_column
, ...) {
611 size_t allocated
, column
;
616 allocated
= t
-> n_display_map
;
617 column
= first_column
;
619 va_start ( ap
, first_column
);
621 assert ( column
< t
-> n_columns
);
623 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
628 t
-> display_map
[ t
-> n_display_map
++] = column
;
630 column
= va_arg ( ap
, size_t );
631 if ( column
== ( size_t ) - 1 )
640 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
641 size_t allocated
, column
;
646 allocated
= t
-> n_sort_map
;
647 column
= first_column
;
649 va_start ( ap
, first_column
);
651 assert ( column
< t
-> n_columns
);
653 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
658 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
660 column
= va_arg ( ap
, size_t );
661 if ( column
== ( size_t ) - 1 )
669 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
673 if ( a
-> type
== b
-> type
) {
675 /* We only define ordering for cells of the same data type. If cells with different data types are
676 * compared we follow the order the cells were originally added in */
681 return strcmp ( a
-> string
, b
-> string
);
684 if (! a
-> boolean
&& b
-> boolean
)
686 if ( a
-> boolean
&& ! b
-> boolean
)
690 case TABLE_TIMESTAMP
:
691 if ( a
-> timestamp
< b
-> timestamp
)
693 if ( a
-> timestamp
> b
-> timestamp
)
698 if ( a
-> timespan
< b
-> timespan
)
700 if ( a
-> timespan
> b
-> timespan
)
705 if ( a
-> size
< b
-> size
)
707 if ( a
-> size
> b
-> size
)
712 if ( a
-> uint32
< b
-> uint32
)
714 if ( a
-> uint32
> b
-> uint32
)
723 /* Generic fallback using the orginal order in which the cells where added. */
724 if ( index_a
< index_b
)
726 if ( index_a
> index_b
)
732 static int table_data_compare ( const void * x
, const void * y
, void * userdata
) {
733 const size_t * a
= x
, * b
= y
;
741 /* Make sure the header stays at the beginning */
742 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
744 if (* a
< t
-> n_columns
)
746 if (* b
< t
-> n_columns
)
749 /* Order other lines by the sorting map */
750 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
753 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
754 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
756 r
= cell_data_compare ( d
, * a
, dd
, * b
);
761 /* Order identical lines by the order there were originally added in */
770 static const char * table_data_format ( TableData
* d
) {
784 return yes_no ( d
-> boolean
);
786 case TABLE_TIMESTAMP
: {
787 _cleanup_free_
char * p
;
789 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
793 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
796 d
-> formatted
= TAKE_PTR ( p
);
800 case TABLE_TIMESPAN
: {
801 _cleanup_free_
char * p
;
803 p
= new ( char , FORMAT_TIMESPAN_MAX
);
807 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timestamp
, 0 ))
810 d
-> formatted
= TAKE_PTR ( p
);
815 _cleanup_free_
char * p
;
817 p
= new ( char , FORMAT_BYTES_MAX
);
821 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
824 d
-> formatted
= TAKE_PTR ( p
);
829 _cleanup_free_
char * p
;
831 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
835 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
836 d
-> formatted
= TAKE_PTR ( p
);
841 assert_not_reached ( "Unexpected type?" );
847 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
851 t
= table_data_format ( d
);
855 l
= utf8_console_width ( t
);
856 if ( l
== ( size_t ) - 1 )
859 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
860 l
= d
-> maximum_width
;
862 if ( l
< d
-> minimum_width
)
863 l
= d
-> minimum_width
;
869 static char * align_string_mem ( const char * str
, size_t new_length
, unsigned percent
) {
870 size_t w
= 0 , space
, lspace
, old_length
;
875 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
878 assert ( percent
<= 100 );
880 old_length
= strlen ( str
);
882 /* Determine current width on screen */
884 while ( p
< str
+ old_length
) {
887 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
888 p
++, w
++; /* count invalid chars as 1 */
892 p
= utf8_next_char ( p
);
893 w
+= unichar_iswide ( c
) ? 2 : 1 ;
896 /* Already wider than the target, if so, don't do anything */
898 return strndup ( str
, old_length
);
900 /* How much spaces shall we add? An how much on the left side? */
901 space
= new_length
- w
;
902 lspace
= space
* percent
/ 100U ;
904 ret
= new ( char , space
+ old_length
+ 1 );
908 for ( i
= 0 ; i
< lspace
; i
++)
910 memcpy ( ret
+ lspace
, str
, old_length
);
911 for ( i
= lspace
+ old_length
; i
< space
+ old_length
; i
++)
914 ret
[ space
+ old_length
] = 0 ;
918 int table_print ( Table
* t
, FILE * f
) {
919 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
920 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
922 _cleanup_free_
size_t * sorted
= NULL
;
923 uint64_t * column_weight
, weight_sum
;
931 /* Ensure we have no incomplete rows */
932 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
934 n_rows
= t
-> n_cells
/ t
-> n_columns
;
935 assert ( n_rows
> 0 ); /* at least the header row must be complete */
938 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
940 sorted
= new ( size_t , n_rows
);
944 for ( i
= 0 ; i
< n_rows
; i
++)
945 sorted
[ i
] = i
* t
-> n_columns
;
947 qsort_r_safe ( sorted
, n_rows
, sizeof ( size_t ), table_data_compare
, t
);
951 display_columns
= t
-> n_display_map
;
953 display_columns
= t
-> n_columns
;
955 assert ( display_columns
> 0 );
957 minimum_width
= newa ( size_t , display_columns
);
958 maximum_width
= newa ( size_t , display_columns
);
959 requested_width
= newa ( size_t , display_columns
);
960 width
= newa ( size_t , display_columns
);
961 column_weight
= newa0 ( uint64_t , display_columns
);
963 for ( j
= 0 ; j
< display_columns
; j
++) {
964 minimum_width
[ j
] = 1 ;
965 maximum_width
[ j
] = ( size_t ) - 1 ;
966 requested_width
[ j
] = ( size_t ) - 1 ;
969 /* First pass: determine column sizes */
970 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
973 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
974 * hence we don't care for sorted[] during the first pass. */
975 row
= t
-> data
+ i
* t
-> n_columns
;
977 for ( j
= 0 ; j
< display_columns
; j
++) {
981 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
983 r
= table_data_requested_width ( d
, & req
);
987 /* Determine the biggest width that any cell in this column would like to have */
988 if ( requested_width
[ j
] == ( size_t ) - 1 ||
989 requested_width
[ j
] < req
)
990 requested_width
[ j
] = req
;
992 /* Determine the minimum width any cell in this column needs */
993 if ( minimum_width
[ j
] < d
-> minimum_width
)
994 minimum_width
[ j
] = d
-> minimum_width
;
996 /* Determine the maximum width any cell in this column needs */
997 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
998 ( maximum_width
[ j
] == ( size_t ) - 1 ||
999 maximum_width
[ j
] > d
-> maximum_width
))
1000 maximum_width
[ j
] = d
-> maximum_width
;
1002 /* Determine the full columns weight */
1003 column_weight
[ j
] += d
-> weight
;
1007 /* One space between each column */
1008 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1010 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1012 for ( j
= 0 ; j
< display_columns
; j
++) {
1013 weight_sum
+= column_weight
[ j
];
1015 table_minimum_width
+= minimum_width
[ j
];
1017 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1018 table_maximum_width
= ( size_t ) - 1 ;
1020 table_maximum_width
+= maximum_width
[ j
];
1022 table_requested_width
+= requested_width
[ j
];
1025 /* Calculate effective table width */
1026 if ( t
-> width
== ( size_t ) - 1 )
1027 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1029 table_effective_width
= t
-> width
;
1031 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1032 table_effective_width
= table_maximum_width
;
1034 if ( table_effective_width
< table_minimum_width
)
1035 table_effective_width
= table_minimum_width
;
1037 if ( table_effective_width
>= table_requested_width
) {
1040 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1041 * each column with what it asked for and the distribute the rest. */
1043 extra
= table_effective_width
- table_requested_width
;
1045 for ( j
= 0 ; j
< display_columns
; j
++) {
1048 if ( weight_sum
== 0 )
1049 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1051 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1053 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1054 width
[ j
] = maximum_width
[ j
];
1056 if ( width
[ j
] < minimum_width
[ j
])
1057 width
[ j
] = minimum_width
[ j
];
1059 assert ( width
[ j
] >= requested_width
[ j
]);
1060 delta
= width
[ j
] - requested_width
[ j
];
1062 /* Subtract what we just added from the rest */
1068 assert ( weight_sum
>= column_weight
[ j
]);
1069 weight_sum
-= column_weight
[ j
];
1073 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1074 * with the minimum they need, and then distribute anything left. */
1075 bool finalize
= false ;
1078 extra
= table_effective_width
- table_minimum_width
;
1080 for ( j
= 0 ; j
< display_columns
; j
++)
1081 width
[ j
] = ( size_t ) - 1 ;
1084 bool restart
= false ;
1086 for ( j
= 0 ; j
< display_columns
; j
++) {
1089 /* Did this column already get something assigned? If so, let's skip to the next */
1090 if ( width
[ j
] != ( size_t ) - 1 )
1093 if ( weight_sum
== 0 )
1094 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1096 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1098 if ( w
>= requested_width
[ j
]) {
1099 /* Never give more than requested. If we hit a column like this, there's more
1100 * space to allocate to other columns which means we need to restart the
1101 * iteration. However, if we hit a column like this, let's assign it the space
1102 * it wanted for good early.*/
1104 w
= requested_width
[ j
];
1107 } else if (! finalize
)
1112 assert ( w
>= minimum_width
[ j
]);
1113 delta
= w
- minimum_width
[ j
];
1115 assert ( delta
<= extra
);
1118 assert ( weight_sum
>= column_weight
[ j
]);
1119 weight_sum
-= column_weight
[ j
];
1121 if ( restart
&& ! finalize
)
1133 /* Second pass: show output */
1134 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1138 row
= t
-> data
+ sorted
[ i
];
1140 row
= t
-> data
+ i
* t
-> n_columns
;
1142 for ( j
= 0 ; j
< display_columns
; j
++) {
1143 _cleanup_free_
char * buffer
= NULL
;
1148 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1150 field
= table_data_format ( d
);
1154 l
= utf8_console_width ( field
);
1156 /* Field is wider than allocated space. Let's ellipsize */
1158 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1164 } else if ( l
< width
[ j
]) {
1165 /* Field is shorter than allocated space. Let's align with spaces */
1167 buffer
= align_string_mem ( field
, width
[ j
], d
-> align_percent
);
1175 fputc ( ' ' , f
); /* column separator */
1183 fputs ( ansi_normal (), f
);
1189 return fflush_and_check ( f
);
1192 int table_format ( Table
* t
, char ** ret
) {
1193 _cleanup_fclose_
FILE * f
= NULL
;
1198 f
= open_memstream (& buf
, & sz
);
1202 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1204 r
= table_print ( t
, f
);
1215 size_t table_get_rows ( Table
* t
) {
1219 assert ( t
-> n_columns
> 0 );
1220 return t
-> n_cells
/ t
-> n_columns
;
1223 size_t table_get_columns ( Table
* t
) {
1227 assert ( t
-> n_columns
> 0 );
1228 return t
-> n_columns
;