]>
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 approproate 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_unref ( TableData
* d
) {
178 assert ( d
-> n_ref
> 0 );
188 DEFINE_TRIVIAL_CLEANUP_FUNC ( TableData
*, table_data_unref
);
190 static TableData
* table_data_ref ( TableData
* d
) {
194 assert ( d
-> n_ref
> 0 );
200 Table
* table_unref ( Table
* t
) {
206 for ( i
= 0 ; i
< t
-> n_cells
; i
++)
207 table_data_unref ( t
-> data
[ i
]);
210 free ( t
-> display_map
);
216 static size_t table_data_size ( TableDataType type
, const void * data
) {
224 return strlen ( data
) + 1 ;
229 case TABLE_TIMESTAMP
:
231 return sizeof ( usec_t
);
234 return sizeof ( uint64_t );
237 return sizeof ( uint32_t );
240 assert_not_reached ( "Uh? Unexpected cell type" );
244 static bool table_data_matches (
248 size_t minimum_width
,
249 size_t maximum_width
,
251 unsigned align_percent
,
252 unsigned ellipsize_percent
) {
260 if ( d
-> minimum_width
!= minimum_width
)
263 if ( d
-> maximum_width
!= maximum_width
)
266 if ( d
-> weight
!= weight
)
269 if ( d
-> align_percent
!= align_percent
)
272 if ( d
-> ellipsize_percent
!= ellipsize_percent
)
275 k
= table_data_size ( type
, data
);
276 l
= table_data_size ( d
-> type
, d
-> data
);
281 return memcmp ( data
, d
-> data
, l
) == 0 ;
284 static TableData
* table_data_new (
287 size_t minimum_width
,
288 size_t maximum_width
,
290 unsigned align_percent
,
291 unsigned ellipsize_percent
) {
296 data_size
= table_data_size ( type
, data
);
298 d
= malloc0 ( offsetof ( TableData
, data
) + data_size
);
304 d
-> minimum_width
= minimum_width
;
305 d
-> maximum_width
= maximum_width
;
307 d
-> align_percent
= align_percent
;
308 d
-> ellipsize_percent
= ellipsize_percent
;
309 memcpy_safe ( d
-> data
, data
, data_size
);
314 int table_add_cell_full (
316 TableCell
** ret_cell
,
319 size_t minimum_width
,
320 size_t maximum_width
,
322 unsigned align_percent
,
323 unsigned ellipsize_percent
) {
325 _cleanup_ ( table_data_unrefp
) TableData
* d
= NULL
;
330 assert ( type
< _TABLE_DATA_TYPE_MAX
);
332 /* Determine the cell adjacent to the current one, but one row up */
333 if ( t
-> n_cells
>= t
-> n_columns
)
334 assert_se ( p
= t
-> data
[ t
-> n_cells
- t
-> n_columns
]);
338 /* If formatting parameters are left unspecified, copy from the previous row */
339 if ( minimum_width
== ( size_t ) - 1 )
340 minimum_width
= p
? p
-> minimum_width
: 1 ;
342 if ( weight
== ( unsigned ) - 1 )
343 weight
= p
? p
-> weight
: DEFAULT_WEIGHT
;
345 if ( align_percent
== ( unsigned ) - 1 )
346 align_percent
= p
? p
-> align_percent
: 0 ;
348 if ( ellipsize_percent
== ( unsigned ) - 1 )
349 ellipsize_percent
= p
? p
-> ellipsize_percent
: 100 ;
351 assert ( align_percent
<= 100 );
352 assert ( ellipsize_percent
<= 100 );
354 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
355 * formatting. Let's see if we can reuse the cell data and ref it once more. */
357 if ( p
&& table_data_matches ( p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
358 d
= table_data_ref ( p
);
360 d
= table_data_new ( type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
365 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
369 * ret_cell
= TABLE_INDEX_TO_CELL ( t
-> n_cells
);
371 t
-> data
[ t
-> n_cells
++] = TAKE_PTR ( d
);
376 int table_dup_cell ( Table
* t
, TableCell
* cell
) {
381 /* Add the data of the specified cell a second time as a new cell to the end. */
383 i
= TABLE_CELL_TO_INDEX ( cell
);
387 if (! GREEDY_REALLOC ( t
-> data
, t
-> n_allocated
, MAX ( t
-> n_cells
+ 1 , t
-> n_columns
)))
390 t
-> data
[ t
-> n_cells
++] = table_data_ref ( t
-> data
[ i
]);
394 static int table_dedup_cell ( Table
* t
, TableCell
* cell
) {
400 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
401 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
403 i
= TABLE_CELL_TO_INDEX ( cell
);
407 assert_se ( od
= t
-> data
[ i
]);
411 assert ( od
-> n_ref
> 1 );
413 nd
= table_data_new ( od
-> type
, od
-> data
, od
-> minimum_width
, od
-> maximum_width
, od
-> weight
, od
-> align_percent
, od
-> ellipsize_percent
);
417 table_data_unref ( od
);
420 assert ( nd
-> n_ref
== 1 );
425 static TableData
* table_get_data ( Table
* t
, TableCell
* cell
) {
431 /* Get the data object of the specified cell, or NULL if it doesn't exist */
433 i
= TABLE_CELL_TO_INDEX ( cell
);
438 assert ( t
-> data
[ i
]-> n_ref
> 0 );
443 int table_set_minimum_width ( Table
* t
, TableCell
* cell
, size_t minimum_width
) {
449 if ( minimum_width
== ( size_t ) - 1 )
452 r
= table_dedup_cell ( t
, cell
);
456 table_get_data ( t
, cell
)-> minimum_width
= minimum_width
;
460 int table_set_maximum_width ( Table
* t
, TableCell
* cell
, size_t maximum_width
) {
466 r
= table_dedup_cell ( t
, cell
);
470 table_get_data ( t
, cell
)-> maximum_width
= maximum_width
;
474 int table_set_weight ( Table
* t
, TableCell
* cell
, unsigned weight
) {
480 if ( weight
== ( unsigned ) - 1 )
481 weight
= DEFAULT_WEIGHT
;
483 r
= table_dedup_cell ( t
, cell
);
487 table_get_data ( t
, cell
)-> weight
= weight
;
491 int table_set_align_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
497 if ( percent
== ( unsigned ) - 1 )
500 assert ( percent
<= 100 );
502 r
= table_dedup_cell ( t
, cell
);
506 table_get_data ( t
, cell
)-> align_percent
= percent
;
510 int table_set_ellipsize_percent ( Table
* t
, TableCell
* cell
, unsigned percent
) {
516 if ( percent
== ( unsigned ) - 1 )
519 assert ( percent
<= 100 );
521 r
= table_dedup_cell ( t
, cell
);
525 table_get_data ( t
, cell
)-> ellipsize_percent
= percent
;
529 int table_set_color ( Table
* t
, TableCell
* cell
, const char * color
) {
535 r
= table_dedup_cell ( t
, cell
);
539 table_get_data ( t
, cell
)-> color
= empty_to_null ( color
);
543 int table_add_many_internal ( Table
* t
, TableDataType first_type
, ...) {
549 assert ( first_type
>= 0 );
550 assert ( first_type
< _TABLE_DATA_TYPE_MAX
);
554 va_start ( ap
, first_type
);
571 data
= va_arg ( ap
, const char *);
575 buffer
. b
= !! va_arg ( ap
, int );
579 case TABLE_TIMESTAMP
:
581 buffer
. usec
= va_arg ( ap
, usec_t
);
586 buffer
. size
= va_arg ( ap
, uint64_t );
591 buffer
. uint32
= va_arg ( ap
, uint32_t );
592 data
= & buffer
. uint32
;
595 case _TABLE_DATA_TYPE_MAX
:
596 /* Used as end marker */
601 assert_not_reached ( "Uh? Unexpected data type." );
604 r
= table_add_cell ( t
, NULL
, type
, data
);
610 type
= va_arg ( ap
, TableDataType
);
614 void table_set_header ( Table
* t
, bool b
) {
620 void table_set_width ( Table
* t
, size_t width
) {
626 int table_set_display ( Table
* t
, size_t first_column
, ...) {
627 size_t allocated
, column
;
632 allocated
= t
-> n_display_map
;
633 column
= first_column
;
635 va_start ( ap
, first_column
);
637 assert ( column
< t
-> n_columns
);
639 if (! GREEDY_REALLOC ( t
-> display_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_display_map
+ 1 ))) {
644 t
-> display_map
[ t
-> n_display_map
++] = column
;
646 column
= va_arg ( ap
, size_t );
647 if ( column
== ( size_t ) - 1 )
656 int table_set_sort ( Table
* t
, size_t first_column
, ...) {
657 size_t allocated
, column
;
662 allocated
= t
-> n_sort_map
;
663 column
= first_column
;
665 va_start ( ap
, first_column
);
667 assert ( column
< t
-> n_columns
);
669 if (! GREEDY_REALLOC ( t
-> sort_map
, allocated
, MAX ( t
-> n_columns
, t
-> n_sort_map
+ 1 ))) {
674 t
-> sort_map
[ t
-> n_sort_map
++] = column
;
676 column
= va_arg ( ap
, size_t );
677 if ( column
== ( size_t ) - 1 )
685 static int cell_data_compare ( TableData
* a
, size_t index_a
, TableData
* b
, size_t index_b
) {
689 if ( a
-> type
== b
-> type
) {
691 /* We only define ordering for cells of the same data type. If cells with different data types are
692 * compared we follow the order the cells were originally added in */
697 return strcmp ( a
-> string
, b
-> string
);
700 if (! a
-> boolean
&& b
-> boolean
)
702 if ( a
-> boolean
&& ! b
-> boolean
)
706 case TABLE_TIMESTAMP
:
707 if ( a
-> timestamp
< b
-> timestamp
)
709 if ( a
-> timestamp
> b
-> timestamp
)
714 if ( a
-> timespan
< b
-> timespan
)
716 if ( a
-> timespan
> b
-> timespan
)
721 if ( a
-> size
< b
-> size
)
723 if ( a
-> size
> b
-> size
)
728 if ( a
-> uint32
< b
-> uint32
)
730 if ( a
-> uint32
> b
-> uint32
)
739 /* Generic fallback using the orginal order in which the cells where added. */
740 if ( index_a
< index_b
)
742 if ( index_a
> index_b
)
748 static int table_data_compare ( const void * x
, const void * y
, void * userdata
) {
749 const size_t * a
= x
, * b
= y
;
757 /* Make sure the header stays at the beginning */
758 if (* a
< t
-> n_columns
&& * b
< t
-> n_columns
)
760 if (* a
< t
-> n_columns
)
762 if (* b
< t
-> n_columns
)
765 /* Order other lines by the sorting map */
766 for ( i
= 0 ; i
< t
-> n_sort_map
; i
++) {
769 d
= t
-> data
[* a
+ t
-> sort_map
[ i
]];
770 dd
= t
-> data
[* b
+ t
-> sort_map
[ i
]];
772 r
= cell_data_compare ( d
, * a
, dd
, * b
);
777 /* Order identical lines by the order there were originally added in */
786 static const char * table_data_format ( TableData
* d
) {
800 return yes_no ( d
-> boolean
);
802 case TABLE_TIMESTAMP
: {
803 _cleanup_free_
char * p
;
805 p
= new ( char , FORMAT_TIMESTAMP_MAX
);
809 if (! format_timestamp ( p
, FORMAT_TIMESTAMP_MAX
, d
-> timestamp
))
812 d
-> formatted
= TAKE_PTR ( p
);
816 case TABLE_TIMESPAN
: {
817 _cleanup_free_
char * p
;
819 p
= new ( char , FORMAT_TIMESPAN_MAX
);
823 if (! format_timespan ( p
, FORMAT_TIMESPAN_MAX
, d
-> timestamp
, 0 ))
826 d
-> formatted
= TAKE_PTR ( p
);
831 _cleanup_free_
char * p
;
833 p
= new ( char , FORMAT_BYTES_MAX
);
837 if (! format_bytes ( p
, FORMAT_BYTES_MAX
, d
-> size
))
840 d
-> formatted
= TAKE_PTR ( p
);
845 _cleanup_free_
char * p
;
847 p
= new ( char , DECIMAL_STR_WIDTH ( d
-> uint32
) + 1 );
851 sprintf ( p
, "%" PRIu32
, d
-> uint32
);
852 d
-> formatted
= TAKE_PTR ( p
);
857 assert_not_reached ( "Unexpected type?" );
863 static int table_data_requested_width ( TableData
* d
, size_t * ret
) {
867 t
= table_data_format ( d
);
871 l
= utf8_console_width ( t
);
872 if ( l
== ( size_t ) - 1 )
875 if ( d
-> maximum_width
!= ( size_t ) - 1 && l
> d
-> maximum_width
)
876 l
= d
-> maximum_width
;
878 if ( l
< d
-> minimum_width
)
879 l
= d
-> minimum_width
;
885 static char * align_string_mem ( const char * str
, size_t old_length
, size_t new_length
, unsigned percent
) {
886 size_t w
= 0 , space
, lspace
;
891 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
894 assert ( percent
<= 100 );
896 if ( old_length
== ( size_t ) - 1 )
897 old_length
= strlen ( str
);
899 /* Determine current width on screen */
901 while ( p
< str
+ old_length
) {
904 if ( utf8_encoded_to_unichar ( p
, & c
) < 0 ) {
905 p
++, w
++; /* count invalid chars as 1 */
909 p
= utf8_next_char ( p
);
910 w
+= unichar_iswide ( c
) ? 2 : 1 ;
913 /* Already wider than the target, if so, don't do anything */
915 return strndup ( str
, old_length
);
917 /* How much spaces shall we add? An how much on the left side? */
918 space
= new_length
- w
;
919 lspace
= space
* percent
/ 100U ;
921 ret
= new ( char , space
+ old_length
+ 1 );
925 for ( i
= 0 ; i
< lspace
; i
++)
927 memcpy ( ret
+ lspace
, str
, old_length
);
928 for ( i
= lspace
+ old_length
; i
< space
+ old_length
; i
++)
931 ret
[ space
+ old_length
] = 0 ;
935 int table_print ( Table
* t
, FILE * f
) {
936 size_t n_rows
, * minimum_width
, * maximum_width
, display_columns
, * requested_width
,
937 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
939 _cleanup_free_
size_t * sorted
= NULL
;
940 uint64_t * column_weight
, weight_sum
;
948 /* Ensure we have no incomplete rows */
949 assert ( t
-> n_cells
% t
-> n_columns
== 0 );
951 n_rows
= t
-> n_cells
/ t
-> n_columns
;
952 assert ( n_rows
> 0 ); /* at least the header row must be complete */
955 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
957 sorted
= new ( size_t , n_rows
);
961 for ( i
= 0 ; i
< n_rows
; i
++)
962 sorted
[ i
] = i
* t
-> n_columns
;
964 qsort_r_safe ( sorted
, n_rows
, sizeof ( size_t ), table_data_compare
, t
);
968 display_columns
= t
-> n_display_map
;
970 display_columns
= t
-> n_columns
;
972 assert ( display_columns
> 0 );
974 minimum_width
= newa ( size_t , display_columns
);
975 maximum_width
= newa ( size_t , display_columns
);
976 requested_width
= newa ( size_t , display_columns
);
977 width
= newa ( size_t , display_columns
);
978 column_weight
= newa0 ( uint64_t , display_columns
);
980 for ( j
= 0 ; j
< display_columns
; j
++) {
981 minimum_width
[ j
] = 1 ;
982 maximum_width
[ j
] = ( size_t ) - 1 ;
983 requested_width
[ j
] = ( size_t ) - 1 ;
986 /* First pass: determine column sizes */
987 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
990 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
991 * hence we don't care for sorted[] during the first pass. */
992 row
= t
-> data
+ i
* t
-> n_columns
;
994 for ( j
= 0 ; j
< display_columns
; j
++) {
998 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1000 r
= table_data_requested_width ( d
, & req
);
1004 /* Determine the biggest width that any cell in this column would like to have */
1005 if ( requested_width
[ j
] == ( size_t ) - 1 ||
1006 requested_width
[ j
] < req
)
1007 requested_width
[ j
] = req
;
1009 /* Determine the minimum width any cell in this column needs */
1010 if ( minimum_width
[ j
] < d
-> minimum_width
)
1011 minimum_width
[ j
] = d
-> minimum_width
;
1013 /* Determine the maximum width any cell in this column needs */
1014 if ( d
-> maximum_width
!= ( size_t ) - 1 &&
1015 ( maximum_width
[ j
] == ( size_t ) - 1 ||
1016 maximum_width
[ j
] > d
-> maximum_width
))
1017 maximum_width
[ j
] = d
-> maximum_width
;
1019 /* Determine the full columns weight */
1020 column_weight
[ j
] += d
-> weight
;
1024 /* One space between each column */
1025 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1 ;
1027 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1029 for ( j
= 0 ; j
< display_columns
; j
++) {
1030 weight_sum
+= column_weight
[ j
];
1032 table_minimum_width
+= minimum_width
[ j
];
1034 if ( maximum_width
[ j
] == ( size_t ) - 1 )
1035 table_maximum_width
= ( size_t ) - 1 ;
1037 table_maximum_width
+= maximum_width
[ j
];
1039 table_requested_width
+= requested_width
[ j
];
1042 /* Calculate effective table width */
1043 if ( t
-> width
== ( size_t ) - 1 )
1044 table_effective_width
= pager_have () ? table_requested_width
: MIN ( table_requested_width
, columns ());
1046 table_effective_width
= t
-> width
;
1048 if ( table_maximum_width
!= ( size_t ) - 1 && table_effective_width
> table_maximum_width
)
1049 table_effective_width
= table_maximum_width
;
1051 if ( table_effective_width
< table_minimum_width
)
1052 table_effective_width
= table_minimum_width
;
1054 if ( table_effective_width
>= table_requested_width
) {
1057 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1058 * each column with what it asked for and the distribute the rest. */
1060 extra
= table_effective_width
- table_requested_width
;
1062 for ( j
= 0 ; j
< display_columns
; j
++) {
1065 if ( weight_sum
== 0 )
1066 width
[ j
] = requested_width
[ j
] + extra
/ ( display_columns
- j
); /* Avoid division by zero */
1068 width
[ j
] = requested_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1070 if ( maximum_width
[ j
] != ( size_t ) - 1 && width
[ j
] > maximum_width
[ j
])
1071 width
[ j
] = maximum_width
[ j
];
1073 if ( width
[ j
] < minimum_width
[ j
])
1074 width
[ j
] = minimum_width
[ j
];
1076 assert ( width
[ j
] >= requested_width
[ j
]);
1077 delta
= width
[ j
] - requested_width
[ j
];
1079 /* Subtract what we just added from the rest */
1085 assert ( weight_sum
>= column_weight
[ j
]);
1086 weight_sum
-= column_weight
[ j
];
1090 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1091 * with the minimum they need, and then distribute anything left. */
1092 bool finalize
= false ;
1095 extra
= table_effective_width
- table_minimum_width
;
1097 for ( j
= 0 ; j
< display_columns
; j
++)
1098 width
[ j
] = ( size_t ) - 1 ;
1101 bool restart
= false ;
1103 for ( j
= 0 ; j
< display_columns
; j
++) {
1106 /* Did this column already get something assigned? If so, let's skip to the next */
1107 if ( width
[ j
] != ( size_t ) - 1 )
1110 if ( weight_sum
== 0 )
1111 w
= minimum_width
[ j
] + extra
/ ( display_columns
- j
); /* avoid division by zero */
1113 w
= minimum_width
[ j
] + ( extra
* column_weight
[ j
]) / weight_sum
;
1115 if ( w
>= requested_width
[ j
]) {
1116 /* Never give more than requested. If we hit a column like this, there's more
1117 * space to allocate to other columns which means we need to restart the
1118 * iteration. However, if we hit a column like this, let's assign it the space
1119 * it wanted for good early.*/
1121 w
= requested_width
[ j
];
1124 } else if (! finalize
)
1129 assert ( w
>= minimum_width
[ j
]);
1130 delta
= w
- minimum_width
[ j
];
1132 assert ( delta
<= extra
);
1135 assert ( weight_sum
>= column_weight
[ j
]);
1136 weight_sum
-= column_weight
[ j
];
1152 /* Second pass: show output */
1153 for ( i
= t
-> header
? 0 : 1 ; i
< n_rows
; i
++) {
1157 row
= t
-> data
+ sorted
[ i
];
1159 row
= t
-> data
+ i
* t
-> n_columns
;
1161 for ( j
= 0 ; j
< display_columns
; j
++) {
1162 _cleanup_free_
char * buffer
= NULL
;
1167 assert_se ( d
= row
[ t
-> display_map
? t
-> display_map
[ j
] : j
]);
1169 field
= table_data_format ( d
);
1173 l
= utf8_console_width ( field
);
1175 /* Field is wider than allocated space. Let's ellipsize */
1177 buffer
= ellipsize ( field
, width
[ j
], d
-> ellipsize_percent
);
1183 } else if ( l
< width
[ j
]) {
1184 /* Field is shorter than allocated space. Let's align with spaces */
1186 buffer
= align_string_mem ( field
, ( size_t ) - 1 , width
[ j
], d
-> align_percent
);
1194 fputc ( ' ' , f
); /* column separator */
1202 fputs ( ansi_normal (), f
);
1208 return fflush_and_check ( f
);
1211 int table_format ( Table
* t
, char ** ret
) {
1212 _cleanup_fclose_
FILE * f
= NULL
;
1217 f
= open_memstream (& buf
, & sz
);
1221 ( void ) __fsetlocking ( f
, FSETLOCKING_BYCALLER
);
1223 r
= table_print ( t
, f
);
1234 size_t table_get_rows ( Table
* t
) {
1238 assert ( t
-> n_columns
> 0 );
1239 return t
-> n_cells
/ t
-> n_columns
;
1242 size_t table_get_columns ( Table
* t
) {
1246 assert ( t
-> n_columns
> 0 );
1247 return t
-> n_columns
;