1 /* print.c - functions to print table
2 *
3 * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
4 * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com>
5 *
6 * This file may be redistributed under the terms of the
7 * GNU Lesser General Public License.
8 */
9
10 /**
11 * SECTION: table_print
12 * @title: Table print
13 * @short_description: output functions
14 *
15 * Table output API.
16 */
17
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <termios.h>
22 #include <ctype.h>
23
24 #include "mbsalign.h"
25 #include "carefulputc.h"
26 #include "smartcolsP.h"
27
28 /* Fallback for symbols
29 *
30 * Note that by default library define all the symbols, but in case user does
31 * not define all symbols or if we extended the symbols struct then we need
32 * fallback to be more robust and backwardly compatible.
33 */
34 #define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ")
35 #define branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-")
36 #define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ")
37 #define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-")
38
39 #define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|")
40 #define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-")
41 #define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->")
42 #define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->")
43 #define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->")
44 #define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-")
45 #define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-")
46
47 #define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \
48 ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " "))
49
50 #define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used)
51
52 static int is_next_columns_empty(
53 struct libscols_table *tb,
54 struct libscols_column *cl,
55 struct libscols_line *ln)
56 {
57 struct libscols_iter itr;
58
59 if (!tb || !cl)
60 return 0;
61 if (is_last_column(cl))
62 return 1;
63 if (!ln)
64 return 0;
65
66 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
67 scols_table_set_columns_iter(tb, &itr, cl);
68
69 /* skip current column */
70 scols_table_next_column(tb, &itr, &cl);
71
72 while (scols_table_next_column(tb, &itr, &cl) == 0) {
73 struct libscols_cell *ce;
74 const char *data = NULL;
75
76 if (scols_column_is_hidden(cl))
77 continue;
78 if (scols_column_is_tree(cl))
79 return 0;
80
81 ce = scols_line_get_cell(ln, cl->seqnum);
82 if (ce)
83 data = scols_cell_get_data(ce);
84 if (data && *data)
85 return 0;
86 }
87 return 1;
88 }
89
90 /* returns pointer to the end of used data */
91 static int tree_ascii_art_to_buffer(struct libscols_table *tb,
92 struct libscols_line *ln,
93 struct ul_buffer *buf)
94 {
95 const char *art;
96 int rc;
97
98 assert(ln);
99 assert(buf);
100
101 if (!ln->parent)
102 return 0;
103
104 rc = tree_ascii_art_to_buffer(tb, ln->parent, buf);
105 if (rc)
106 return rc;
107
108 if (is_last_child(ln))
109 art = " ";
110 else
111 art = vertical_symbol(tb);
112
113 return ul_buffer_append_string(buf, art);
114 }
115
116 static int grpset_is_empty( struct libscols_table *tb,
117 size_t idx,
118 size_t *rest)
119 {
120 size_t i;
121
122 for (i = idx; i < tb->grpset_size; i++) {
123 if (tb->grpset[i] == NULL) {
124 if (rest)
125 (*rest)++;
126 } else
127 return 0;
128 }
129 return 1;
130 }
131
132 static int groups_ascii_art_to_buffer( struct libscols_table *tb,
133 struct libscols_line *ln,
134 struct ul_buffer *buf,
135 int empty)
136 {
137 int filled = 0;
138 size_t i, rest = 0;
139 const char *filler = cellpadding_symbol(tb);
140
141 if (!has_groups(tb))
142 return 0;
143
144 DBG(LINE, ul_debugobj(ln, "printing groups chart"));
145
146 if (tb->is_dummy_print)
147 return 0; /* allocate grpset[] only */
148
149 for (i = 0; i < tb->grpset_size; i += SCOLS_GRPSET_CHUNKSIZ) {
150 struct libscols_group *gr = tb->grpset[i];
151
152 if (!gr) {
153 ul_buffer_append_ntimes(buf, SCOLS_GRPSET_CHUNKSIZ, cellpadding_symbol(tb));
154 continue;
155 }
156
157 /*
158 * Empty cells (multi-line entries, etc.), print vertical symbols only
159 * to show that the group continues.
160 */
161 if (empty) {
162 switch (gr->state) {
163 case SCOLS_GSTATE_FIRST_MEMBER:
164 case SCOLS_GSTATE_MIDDLE_MEMBER:
165 case SCOLS_GSTATE_CONT_MEMBERS:
166 ul_buffer_append_string(buf, grp_vertical_symbol(tb));
167 ul_buffer_append_ntimes(buf, 2, filler);
168 break;
169
170 case SCOLS_GSTATE_LAST_MEMBER:
171 case SCOLS_GSTATE_MIDDLE_CHILD:
172 case SCOLS_GSTATE_CONT_CHILDREN:
173 ul_buffer_append_string(buf, filler);
174 ul_buffer_append_string(buf, grp_vertical_symbol(tb));
175 ul_buffer_append_string(buf, filler);
176 break;
177 case SCOLS_GSTATE_LAST_CHILD:
178 ul_buffer_append_ntimes(buf, 3, filler);
179 break;
180 }
181 continue;
182 }
183
184 /*
185 * Regular cell
186 */
187 switch (gr->state) {
188 case SCOLS_GSTATE_FIRST_MEMBER:
189 ul_buffer_append_string(buf, grp_m_first_symbol(tb));
190 break;
191 case SCOLS_GSTATE_MIDDLE_MEMBER:
192 ul_buffer_append_string(buf, grp_m_middle_symbol(tb));
193 break;
194 case SCOLS_GSTATE_LAST_MEMBER:
195 ul_buffer_append_string(buf, grp_m_last_symbol(tb));
196 break;
197 case SCOLS_GSTATE_CONT_MEMBERS:
198 ul_buffer_append_string(buf, grp_vertical_symbol(tb));
199 ul_buffer_append_ntimes(buf, 2, filler);
200 break;
201 case SCOLS_GSTATE_MIDDLE_CHILD:
202 ul_buffer_append_string(buf, filler);
203 ul_buffer_append_string(buf, grp_c_middle_symbol(tb));
204 if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) {
205 ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb));
206 filled = 1;
207 }
208 filler = grp_horizontal_symbol(tb);
209 break;
210 case SCOLS_GSTATE_LAST_CHILD:
211 ul_buffer_append_string(buf, cellpadding_symbol(tb));
212 ul_buffer_append_string(buf, grp_c_last_symbol(tb));
213 if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) {
214 ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb));
215 filled = 1;
216 }
217 filler = grp_horizontal_symbol(tb);
218 break;
219 case SCOLS_GSTATE_CONT_CHILDREN:
220 ul_buffer_append_string(buf, filler);
221 ul_buffer_append_string(buf, grp_vertical_symbol(tb));
222 ul_buffer_append_string(buf, filler);
223 break;
224 }
225
226 if (filled)
227 break;
228 }
229
230 if (!filled)
231 ul_buffer_append_string(buf, filler);
232 return 0;
233 }
234
235 static int has_pending_data(struct libscols_table *tb)
236 {
237 struct libscols_column *cl;
238 struct libscols_iter itr;
239
240 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
241 while (scols_table_next_column(tb, &itr, &cl) == 0) {
242 if (scols_column_is_hidden(cl))
243 continue;
244 if (cl->pending_data)
245 return 1;
246 }
247 return 0;
248 }
249
250 static void fputs_color_reset(struct libscols_table *tb)
251 {
252 if (tb->cur_color) {
253 fputs(UL_COLOR_RESET, tb->out);
254 tb->cur_color = NULL;
255 }
256 }
257
258 static void fputs_color(struct libscols_table *tb, const char *color)
259 {
260 if (tb->cur_color)
261 fputs_color_reset(tb);
262
263 tb->cur_color = color;
264 if (color)
265 fputs(color, tb->out);
266 }
267
268 static const char *get_cell_color(struct libscols_table *tb,
269 struct libscols_column *cl,
270 struct libscols_line *ln,
271 struct libscols_cell *ce)
272 {
273 const char *color = NULL;
274
275 if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN)
276 return NULL;
277 if (ce)
278 color = ce->color;
279 if (!color && (!ln || !ln->color) && cl)
280 color = cl->color;
281 return color;
282 }
283
284 /* switch from line color to cell/column color */
285 static void fputs_color_cell_open(struct libscols_table *tb,
286 struct libscols_column *cl,
287 struct libscols_line *ln,
288 struct libscols_cell *ce)
289 {
290 const char *color = get_cell_color(tb, cl, ln, ce);
291
292 if (color)
293 fputs_color(tb, color);
294 }
295
296 /* switch from cell/column color to line color or reset */
297 static void fputs_color_cell_close(struct libscols_table *tb,
298 struct libscols_column *cl,
299 struct libscols_line *ln,
300 struct libscols_cell *ce)
301 {
302 const char *color = get_cell_color(tb, cl, ln, ce);
303
304 if (color)
305 fputs_color(tb, ln ? ln->color : NULL);
306 }
307
308 /* switch to line color */
309 static void fputs_color_line_open(struct libscols_table *tb,
310 struct libscols_line *ln)
311 {
312 if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN)
313 return;
314 fputs_color(tb, ln ? ln->color : NULL);
315 }
316
317 /* switch off all colors */
318 static void fputs_color_line_close(struct libscols_table *tb)
319 {
320 if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN)
321 return;
322 fputs_color_reset(tb);
323 }
324
325 /* print padding or ASCII-art instead of data of @cl */
326 static void print_empty_cell(struct libscols_table *tb,
327 struct libscols_column *cl,
328 struct libscols_line *ln, /* optional */
329 struct libscols_cell *ce,
330 size_t bufsz)
331 {
332 size_t len_pad = 0; /* in screen cells as opposed to bytes */
333
334 DBG(COL, ul_debugobj(cl, " printing empty cell"));
335
336 fputs_color_cell_open(tb, cl, ln, ce);
337
338 /* generate tree/group ASCII-art rather than padding
339 */
340 if (ln && scols_column_is_tree(cl)) {
341 struct ul_buffer art = UL_INIT_BUFFER;
342 char *data;
343
344 if (ul_buffer_alloc_data(&art, bufsz) != 0)
345 goto done;
346
347 if (cl->is_groups)
348 groups_ascii_art_to_buffer(tb, ln, &art, 1);
349
350 tree_ascii_art_to_buffer(tb, ln, &art);
351
352 if (!list_empty(&ln->ln_branch) && has_pending_data(tb))
353 ul_buffer_append_string(&art, vertical_symbol(tb));
354
355 if (scols_table_is_noencoding(tb))
356 data = ul_buffer_get_data(&art, NULL, &len_pad);
357 else
358 data = ul_buffer_get_safe_data(&art, NULL, &len_pad, NULL);
359
360 if (data && len_pad)
361 fputs(data, tb->out);
362 ul_buffer_free_data(&art);
363 }
364
365 done:
366 /* minout -- don't fill */
367 if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) {
368 fputs_color_cell_close(tb, cl, ln, ce);
369 return;
370 }
371
372 /* default -- fill except last column */
373 if (!scols_table_is_maxout(tb) && is_last_column(cl)) {
374 fputs_color_cell_close(tb, cl, ln, ce);
375 return;
376 }
377
378 /* fill rest of cell with space */
379 for(; len_pad < cl->width; ++len_pad)
380 fputs(cellpadding_symbol(tb), tb->out);
381
382 fputs_color_cell_close(tb, cl, ln, ce);
383
384 if (!is_last_column(cl))
385 fputs(colsep(tb), tb->out);
386 }
387
388
389
390 /* Fill the start of a line with padding (or with tree ascii-art).
391 *
392 * This is necessary after a long non-truncated column, as this requires the
393 * next column to be printed on the next line. For example (see 'DDD'):
394 *
395 * aaa bbb ccc ddd eee
396 * AAA BBB CCCCCCC
397 * DDD EEE
398 * ^^^^^^^^^^^^
399 * new line padding
400 */
401 static void print_newline_padding(struct libscols_table *tb,
402 struct libscols_column *cl,
403 struct libscols_line *ln, /* optional */
404 struct libscols_cell *ce,
405 size_t bufsz)
406 {
407 size_t i;
408
409 assert(tb);
410 assert(cl);
411
412 DBG(LINE, ul_debugobj(ln, "printing newline padding"));
413
414 fputs(linesep(tb), tb->out); /* line break */
415 tb->termlines_used++;
416
417 fputs_color_line_open(tb, ln);
418
419 /* fill cells after line break */
420 for (i = 0; i <= (size_t) cl->seqnum; i++)
421 print_empty_cell(tb, scols_table_get_column(tb, i), ln, ce, bufsz);
422
423 fputs_color_line_close(tb);
424 }
425
426 /*
427 * Pending data
428 *
429 * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is
430 * printed as usually and output is truncated to match column width.
431 *
432 * The rest of the long text is printed on next extra line(s). The extra lines
433 * don't exist in the table (not represented by libscols_line). The data for
434 * the extra lines are stored in libscols_column->pending_data_buf and the
435 * function print_line() adds extra lines until the buffer is not empty in all
436 * columns.
437 */
438
439 /* set data that will be printed by extra lines */
440 static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz)
441 {
442 char *p = NULL;
443
444 if (data && *data) {
445 DBG(COL, ul_debugobj(cl, "setting pending data"));
446 assert(sz);
447 p = strdup(data);
448 if (!p)
449 return -ENOMEM;
450 }
451
452 free(cl->pending_data_buf);
453 cl->pending_data_buf = p;
454 cl->pending_data_sz = sz;
455 cl->pending_data = cl->pending_data_buf;
456 return 0;
457 }
458
459 /* the next extra line has been printed, move pending data cursor */
460 static int step_pending_data(struct libscols_column *cl, size_t bytes)
461 {
462 DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes));
463
464 if (bytes >= cl->pending_data_sz)
465 return set_pending_data(cl, NULL, 0);
466
467 cl->pending_data += bytes;
468 cl->pending_data_sz -= bytes;
469 return 0;
470 }
471
472 /* print next pending data for the column @cl */
473 static int print_pending_data(
474 struct libscols_table *tb,
475 struct libscols_column *cl,
476 struct libscols_line *ln, /* optional */
477 struct libscols_cell *ce)
478 {
479 size_t width = cl->width, bytes;
480 size_t len = width, i;
481 char *data;
482 char *nextchunk = NULL;
483
484 if (!cl->pending_data)
485 return 0;
486 if (!width)
487 return -EINVAL;
488
489 DBG(COL, ul_debugobj(cl, "printing pending data"));
490
491 data = strdup(cl->pending_data);
492 if (!data)
493 goto err;
494
495 if (scols_column_is_customwrap(cl)
496 && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) {
497 bytes = nextchunk - data;
498
499 len = scols_table_is_noencoding(tb) ?
500 mbs_nwidth(data, bytes) :
501 mbs_safe_nwidth(data, bytes, NULL);
502 } else
503 bytes = mbs_truncate(data, &len);
504
505 if (bytes == (size_t) -1)
506 goto err;
507
508 if (bytes)
509 step_pending_data(cl, bytes);
510
511 fputs_color_cell_open(tb, cl, ln, ce);
512
513 fputs(data, tb->out);
514 free(data);
515
516 /* minout -- don't fill */
517 if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) {
518 fputs_color_cell_close(tb, cl, ln, ce);
519 return 0;
520 }
521
522 /* default -- fill except last column */
523 if (!scols_table_is_maxout(tb) && is_last_column(cl)) {
524 fputs_color_cell_close(tb, cl, ln, ce);
525 return 0;
526 }
527
528 /* fill rest of cell with space */
529 for(i = len; i < width; i++)
530 fputs(cellpadding_symbol(tb), tb->out);
531
532 fputs_color_cell_close(tb, cl, ln, ce);
533
534 if (!is_last_column(cl))
535 fputs(colsep(tb), tb->out);
536
537 return 0;
538 err:
539 free(data);
540 return -errno;
541 }
542
543 static void print_json_data(struct libscols_table *tb,
544 struct libscols_column *cl,
545 const char *name,
546 char *data)
547 {
548 switch (cl->json_type) {
549 case SCOLS_JSON_STRING:
550 /* name: "aaa" */
551 ul_jsonwrt_value_s(&tb->json, name, data);
552 break;
553 case SCOLS_JSON_NUMBER:
554 /* name: 123 */
555 ul_jsonwrt_value_raw(&tb->json, name, data);
556 break;
557 case SCOLS_JSON_BOOLEAN:
558 case SCOLS_JSON_BOOLEAN_OPTIONAL:
559 /* name: true|false|null */
560 if (cl->json_type == SCOLS_JSON_BOOLEAN_OPTIONAL && (!*data || !strcmp(data, "-"))) {
561 ul_jsonwrt_value_null(&tb->json, name);
562 } else {
563 ul_jsonwrt_value_boolean(&tb->json, name,
564 !*data ? 0 :
565 *data == '0' ? 0 :
566 *data == 'N' || *data == 'n' ? 0 : 1);
567 }
568 break;
569 case SCOLS_JSON_ARRAY_STRING:
570 case SCOLS_JSON_ARRAY_NUMBER:
571 /* name: [ "aaa", "bbb", "ccc" ] */
572 ul_jsonwrt_array_open(&tb->json, name);
573
574 if (!scols_column_is_customwrap(cl))
575 ul_jsonwrt_value_s(&tb->json, NULL, data);
576 else do {
577 char *next = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data);
578
579 if (cl->json_type == SCOLS_JSON_ARRAY_STRING)
580 ul_jsonwrt_value_s(&tb->json, NULL, data);
581 else
582 ul_jsonwrt_value_raw(&tb->json, NULL, data);
583 data = next;
584 } while (data);
585
586 ul_jsonwrt_array_close(&tb->json);
587 break;
588 }
589 }
590
591 static int print_data(struct libscols_table *tb,
592 struct libscols_column *cl,
593 struct libscols_line *ln, /* optional */
594 struct libscols_cell *ce, /* optional */
595 struct ul_buffer *buf)
596 {
597 size_t len = 0, i, width, bytes;
598 char *data, *nextchunk;
599 const char *name = NULL;
600 int is_last;
601
602 assert(tb);
603 assert(cl);
604
605 data = ul_buffer_get_data(buf, NULL, NULL);
606 if (!data)
607 data = "";
608
609 if (tb->format != SCOLS_FMT_HUMAN) {
610 name = scols_table_is_shellvar(tb) ?
611 scols_column_get_name_as_shellvar(cl) :
612 scols_column_get_name(cl);
613 }
614
615 is_last = is_last_column(cl);
616
617 if (is_last && scols_table_is_json(tb) &&
618 scols_table_is_tree(tb) && has_children(ln))
619 /* "children": [] is the real last value */
620 is_last = 0;
621
622 switch (tb->format) {
623 case SCOLS_FMT_RAW:
624 fputs_nonblank(data, tb->out);
625 if (!is_last)
626 fputs(colsep(tb), tb->out);
627 return 0;
628
629 case SCOLS_FMT_EXPORT:
630 fputs(name, tb->out);
631 fputc('=', tb->out);
632 fputs_quoted(data, tb->out);
633 if (!is_last)
634 fputs(colsep(tb), tb->out);
635 return 0;
636
637 case SCOLS_FMT_JSON:
638 print_json_data(tb, cl, name, data);
639 return 0;
640
641 case SCOLS_FMT_HUMAN:
642 break; /* continue below */
643 }
644
645 /* Encode. Note that 'len' and 'width' are number of cells, not bytes.
646 */
647 if (scols_table_is_noencoding(tb))
648 data = ul_buffer_get_data(buf, &bytes, &len);
649 else
650 data = ul_buffer_get_safe_data(buf, &bytes, &len, scols_column_get_safechars(cl));
651
652 if (!data)
653 data = "";
654 width = cl->width;
655
656 /* custom multi-line cell based */
657 if (*data && scols_column_is_customwrap(cl)
658 && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) {
659 set_pending_data(cl, nextchunk, bytes - (nextchunk - data));
660 bytes = nextchunk - data;
661
662 len = scols_table_is_noencoding(tb) ?
663 mbs_nwidth(data, bytes) :
664 mbs_safe_nwidth(data, bytes, NULL);
665 }
666
667 if (is_last
668 && len < width
669 && !scols_table_is_maxout(tb)
670 && !scols_column_is_right(cl))
671 width = len;
672
673 /* truncate data */
674 if (len > width && scols_column_is_trunc(cl)) {
675 len = width;
676 bytes = mbs_truncate(data, &len); /* updates 'len' */
677 }
678
679 /* standard multi-line cell */
680 if (len > width && scols_column_is_wrap(cl)
681 && !scols_column_is_customwrap(cl)) {
682 set_pending_data(cl, data, bytes);
683
684 len = width;
685 bytes = mbs_truncate(data, &len);
686 if (bytes != (size_t) -1 && bytes > 0)
687 step_pending_data(cl, bytes);
688 }
689
690 if (bytes == (size_t) -1) {
691 bytes = len = 0;
692 data = NULL;
693 }
694
695 fputs_color_cell_open(tb, cl, ln, ce);
696
697 if (data && *data) {
698 if (scols_column_is_right(cl)) {
699 for (i = len; i < width; i++)
700 fputs(cellpadding_symbol(tb), tb->out);
701 len = width;
702 }
703 fputs(data, tb->out);
704
705 }
706
707 /* minout -- don't fill */
708 if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) {
709 fputs_color_cell_close(tb, cl, ln, ce);
710 return 0;
711 }
712
713 /* default -- fill except last column */
714 if (!scols_table_is_maxout(tb) && is_last) {
715 fputs_color_cell_close(tb, cl, ln, ce);
716 return 0;
717 }
718
719 /* fill rest of cell with space */
720 for(i = len; i < width; i++)
721 fputs(cellpadding_symbol(tb), tb->out);
722
723 fputs_color_cell_close(tb, cl, ln, ce);
724
725 if (len > width && !scols_column_is_trunc(cl)) {
726 DBG(COL, ul_debugobj(cl, "*** data len=%zu > column width=%zu", len, width));
727 print_newline_padding(tb, cl, ln, ce, ul_buffer_get_bufsiz(buf)); /* next column starts on next line */
728
729 } else if (!is_last)
730 fputs(colsep(tb), tb->out); /* columns separator */
731
732 return 0;
733 }
734
735 int __cell_to_buffer(struct libscols_table *tb,
736 struct libscols_line *ln,
737 struct libscols_column *cl,
738 struct ul_buffer *buf)
739 {
740 const char *data;
741 struct libscols_cell *ce;
742 int rc = 0;
743
744 assert(tb);
745 assert(ln);
746 assert(cl);
747 assert(buf);
748 assert(cl->seqnum <= tb->ncols);
749
750 ul_buffer_reset_data(buf);
751
752 ce = scols_line_get_cell(ln, cl->seqnum);
753 data = ce ? scols_cell_get_data(ce) : NULL;
754
755 if (!scols_column_is_tree(cl))
756 return data ? ul_buffer_append_string(buf, data) : 0;
757
758 /*
759 * Group stuff
760 */
761 if (!scols_table_is_json(tb) && cl->is_groups)
762 rc = groups_ascii_art_to_buffer(tb, ln, buf, 0);
763
764 /*
765 * Tree stuff
766 */
767 if (!rc && ln->parent && !scols_table_is_json(tb)) {
768 rc = tree_ascii_art_to_buffer(tb, ln->parent, buf);
769
770 if (!rc && is_last_child(ln))
771 rc = ul_buffer_append_string(buf, right_symbol(tb));
772 else if (!rc)
773 rc = ul_buffer_append_string(buf, branch_symbol(tb));
774 }
775
776 if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb))
777 ul_buffer_save_pointer(buf, SCOLS_BUFPTR_TREEEND);
778
779 if (!rc && data)
780 rc = ul_buffer_append_string(buf, data);
781 return rc;
782 }
783
784 /*
785 * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and
786 * control and non-printable characters can be encoded in the \x?? encoding.
787 */
788 static int print_line(struct libscols_table *tb,
789 struct libscols_line *ln,
790 struct ul_buffer *buf)
791 {
792 int rc = 0, pending = 0;
793 struct libscols_column *cl;
794 struct libscols_iter itr;
795
796 assert(ln);
797
798 DBG(LINE, ul_debugobj(ln, " printing line"));
799
800 fputs_color_line_open(tb, ln);
801
802 /* regular line */
803 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
804 while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) {
805 if (scols_column_is_hidden(cl))
806 continue;
807 rc = __cell_to_buffer(tb, ln, cl, buf);
808 if (rc == 0)
809 rc = print_data(tb, cl, ln,
810 scols_line_get_cell(ln, cl->seqnum),
811 buf);
812 if (rc == 0 && cl->pending_data)
813 pending = 1;
814 }
815 fputs_color_line_close(tb);
816
817 /* extra lines of the multi-line cells */
818 while (rc == 0 && pending) {
819 DBG(LINE, ul_debugobj(ln, "printing pending data"));
820 pending = 0;
821 fputs(linesep(tb), tb->out);
822 fputs_color_line_open(tb, ln);
823 tb->termlines_used++;
824 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
825 while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) {
826 if (scols_column_is_hidden(cl))
827 continue;
828 if (cl->pending_data) {
829 rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum));
830 if (rc == 0 && cl->pending_data)
831 pending = 1;
832 } else
833 print_empty_cell(tb, cl, ln, NULL, ul_buffer_get_bufsiz(buf));
834 }
835 fputs_color_line_close(tb);
836 }
837
838 return 0;
839 }
840
841 int __scols_print_title(struct libscols_table *tb)
842 {
843 int rc;
844 mbs_align_t align;
845 size_t width, len = 0, bufsz, titlesz;
846 char *title = NULL, *buf = NULL;
847
848 assert(tb);
849
850 if (!tb->title.data)
851 return 0;
852
853 DBG(TAB, ul_debugobj(tb, "printing title"));
854
855 /* encode data */
856 if (tb->no_encode) {
857 len = bufsz = strlen(tb->title.data) + 1;
858 buf = strdup(tb->title.data);
859 if (!buf) {
860 rc = -ENOMEM;
861 goto done;
862 }
863 } else {
864 bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1;
865 if (bufsz == 1) {
866 DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore"));
867 return 0;
868 }
869 buf = malloc(bufsz);
870 if (!buf) {
871 rc = -ENOMEM;
872 goto done;
873 }
874
875 if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) ||
876 !len || len == (size_t) -1) {
877 rc = -EINVAL;
878 goto done;
879 }
880 }
881
882 /* truncate and align */
883 width = tb->is_term ? tb->termwidth : 80;
884 titlesz = width + bufsz;
885
886 title = malloc(titlesz);
887 if (!title) {
888 rc = -EINVAL;
889 goto done;
890 }
891
892 switch (scols_cell_get_alignment(&tb->title)) {
893 case SCOLS_CELL_FL_RIGHT:
894 align = MBS_ALIGN_RIGHT;
895 break;
896 case SCOLS_CELL_FL_CENTER:
897 align = MBS_ALIGN_CENTER;
898 break;
899 case SCOLS_CELL_FL_LEFT:
900 default:
901 align = MBS_ALIGN_LEFT;
902 /*
903 * Don't print extra blank chars after the title if on left
904 * (that's same as we use for the last column in the table).
905 */
906 if (len < width
907 && !scols_table_is_maxout(tb)
908 && isblank(*titlepadding_symbol(tb)))
909 width = len;
910 break;
911
912 }
913
914 /* copy from buf to title and align to width with title_padding */
915 rc = mbsalign_with_padding(buf, title, titlesz,
916 &width, align,
917 0, (int) *titlepadding_symbol(tb));
918
919 if (rc == -1) {
920 rc = -EINVAL;
921 goto done;
922 }
923
924
925 if (tb->colors_wanted)
926 fputs_color(tb, tb->title.color);
927
928 fputs(title, tb->out);
929
930 if (tb->colors_wanted)
931 fputs_color_reset(tb);
932
933 fputc('\n', tb->out);
934 rc = 0;
935 done:
936 free(buf);
937 free(title);
938 DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc));
939 return rc;
940 }
941
942 int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf)
943 {
944 int rc = 0;
945 struct libscols_column *cl;
946 struct libscols_iter itr;
947
948 assert(tb);
949
950 if ((tb->header_printed == 1 && tb->header_repeat == 0) ||
951 scols_table_is_noheadings(tb) ||
952 scols_table_is_export(tb) ||
953 scols_table_is_json(tb) ||
954 list_empty(&tb->tb_lines))
955 return 0;
956
957 DBG(TAB, ul_debugobj(tb, "printing header"));
958
959 /* set the width according to the size of the data */
960 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
961 while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) {
962 if (scols_column_is_hidden(cl))
963 continue;
964
965 ul_buffer_reset_data(buf);
966
967 if (cl->is_groups
968 && scols_table_is_tree(tb) && scols_column_is_tree(cl)) {
969 size_t i;
970 for (i = 0; i < tb->grpset_size + 1; i++) {
971 rc = ul_buffer_append_data(buf, " ", 1);
972 if (rc)
973 break;
974 }
975 }
976 if (!rc)
977 rc = ul_buffer_append_string(buf,
978 scols_table_is_shellvar(tb) ?
979 scols_column_get_name_as_shellvar(cl) :
980 scols_column_get_name(cl));
981 if (!rc)
982 rc = print_data(tb, cl, NULL, &cl->header, buf);
983 }
984
985 if (rc == 0) {
986 fputs(linesep(tb), tb->out);
987 tb->termlines_used++;
988 }
989
990 tb->header_printed = 1;
991 tb->header_next = tb->termlines_used + tb->termheight;
992 if (tb->header_repeat)
993 DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]",
994 tb->header_next, tb->termlines_used, rc));
995 return rc;
996 }
997
998
999 int __scols_print_range(struct libscols_table *tb,
1000 struct ul_buffer *buf,
1001 struct libscols_iter *itr,
1002 struct libscols_line *end)
1003 {
1004 int rc = 0;
1005 struct libscols_line *ln;
1006
1007 assert(tb);
1008 DBG(TAB, ul_debugobj(tb, "printing range"));
1009
1010 while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) {
1011
1012 int last = scols_iter_is_last(itr);
1013
1014 if (scols_table_is_json(tb))
1015 ul_jsonwrt_object_open(&tb->json, NULL);
1016
1017 rc = print_line(tb, ln, buf);
1018
1019 if (scols_table_is_json(tb))
1020 ul_jsonwrt_object_close(&tb->json);
1021 else if (last == 0 && tb->no_linesep == 0) {
1022 fputs(linesep(tb), tb->out);
1023 tb->termlines_used++;
1024 }
1025
1026 if (end && ln == end)
1027 break;
1028
1029 if (!last && want_repeat_header(tb))
1030 __scols_print_header(tb, buf);
1031 }
1032
1033 return rc;
1034
1035 }
1036
1037 int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf)
1038 {
1039 struct libscols_iter itr;
1040
1041 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
1042 return __scols_print_range(tb, buf, &itr, NULL);
1043 }
1044
1045 /* scols_walk_tree() callback to print tree line */
1046 static int print_tree_line(struct libscols_table *tb,
1047 struct libscols_line *ln,
1048 struct libscols_column *cl __attribute__((__unused__)),
1049 void *data)
1050 {
1051 struct ul_buffer *buf = (struct ul_buffer *) data;
1052 int rc;
1053
1054 DBG(LINE, ul_debugobj(ln, " printing tree line"));
1055
1056 if (scols_table_is_json(tb))
1057 ul_jsonwrt_object_open(&tb->json, NULL);
1058
1059 rc = print_line(tb, ln, buf);
1060 if (rc)
1061 return rc;
1062
1063 if (has_children(ln)) {
1064 if (scols_table_is_json(tb))
1065 ul_jsonwrt_array_open(&tb->json, "children");
1066 else {
1067 /* between parent and child is separator */
1068 fputs(linesep(tb), tb->out);
1069 tb->termlines_used++;
1070 }
1071 } else {
1072 int last;
1073
1074 /* terminate all open last children for JSON */
1075 if (scols_table_is_json(tb)) {
1076 do {
1077 last = (is_child(ln) && is_last_child(ln)) ||
1078 (is_tree_root(ln) && is_last_tree_root(tb, ln));
1079
1080 ul_jsonwrt_object_close(&tb->json);
1081 if (last && is_child(ln))
1082 ul_jsonwrt_array_close(&tb->json);
1083 ln = ln->parent;
1084 } while(ln && last);
1085
1086 } else if (tb->no_linesep == 0) {
1087 int last_in_tree = scols_walk_is_last(tb, ln);
1088
1089 if (last_in_tree == 0) {
1090 /* standard output */
1091 fputs(linesep(tb), tb->out);
1092 tb->termlines_used++;
1093 }
1094 }
1095 }
1096
1097 return 0;
1098 }
1099
1100 int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf)
1101 {
1102 assert(tb);
1103 DBG(TAB, ul_debugobj(tb, "----printing-tree-----"));
1104
1105 return scols_walk_tree(tb, NULL, print_tree_line, (void *) buf);
1106 }
1107
1108 static size_t strlen_line(struct libscols_line *ln)
1109 {
1110 size_t i, sz = 0;
1111
1112 assert(ln);
1113
1114 for (i = 0; i < ln->ncells; i++) {
1115 struct libscols_cell *ce = scols_line_get_cell(ln, i);
1116 const char *data = ce ? scols_cell_get_data(ce) : NULL;
1117
1118 sz += data ? strlen(data) : 0;
1119 }
1120
1121 return sz;
1122 }
1123
1124 void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf)
1125 {
1126 if (!tb)
1127 return;
1128
1129 ul_buffer_free_data(buf);
1130
1131 if (tb->priv_symbols) {
1132 scols_table_set_symbols(tb, NULL);
1133 tb->priv_symbols = 0;
1134 }
1135 }
1136
1137 int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf)
1138 {
1139 size_t bufsz, extra_bufsz = 0;
1140 struct libscols_line *ln;
1141 struct libscols_iter itr;
1142 int rc;
1143
1144 DBG(TAB, ul_debugobj(tb, "initialize printing"));
1145
1146 if (!tb->symbols) {
1147 rc = scols_table_set_default_symbols(tb);
1148 if (rc)
1149 goto err;
1150 tb->priv_symbols = 1;
1151 } else
1152 tb->priv_symbols = 0;
1153
1154 if (tb->format == SCOLS_FMT_HUMAN)
1155 tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 :
1156 tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 :
1157 isatty(STDOUT_FILENO);
1158
1159 if (tb->is_term) {
1160 size_t width = (size_t) scols_table_get_termwidth(tb);
1161
1162 if (tb->termreduce > 0 && tb->termreduce < width) {
1163 width -= tb->termreduce;
1164 scols_table_set_termwidth(tb, width);
1165 }
1166 bufsz = width;
1167 } else
1168 bufsz = BUFSIZ;
1169
1170 if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb))
1171 tb->header_repeat = 0;
1172
1173 /*
1174 * Estimate extra space necessary for tree, JSON or another output
1175 * decoration.
1176 */
1177 if (scols_table_is_tree(tb))
1178 extra_bufsz += tb->nlines * strlen(vertical_symbol(tb));
1179
1180 switch (tb->format) {
1181 case SCOLS_FMT_RAW:
1182 extra_bufsz += tb->ncols; /* separator between columns */
1183 break;
1184 case SCOLS_FMT_JSON:
1185 ul_jsonwrt_init(&tb->json, tb->out, 0);
1186 extra_bufsz += tb->nlines * 3; /* indentation */
1187 /* fallthrough */
1188 case SCOLS_FMT_EXPORT:
1189 {
1190 struct libscols_column *cl;
1191
1192 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
1193
1194 while (scols_table_next_column(tb, &itr, &cl) == 0) {
1195 if (scols_column_is_hidden(cl))
1196 continue;
1197
1198 if (scols_column_get_name(cl))
1199 extra_bufsz += strlen(scols_column_get_name(cl)); /* data */
1200 extra_bufsz += 2; /* separators */
1201 }
1202 break;
1203 }
1204 case SCOLS_FMT_HUMAN:
1205 break;
1206 }
1207
1208 /*
1209 * Enlarge buffer if necessary, the buffer should be large enough to
1210 * store line data and tree ascii art (or another decoration).
1211 */
1212 scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
1213 while (scols_table_next_line(tb, &itr, &ln) == 0) {
1214 size_t sz;
1215
1216 sz = strlen_line(ln) + extra_bufsz;
1217 if (sz > bufsz)
1218 bufsz = sz;
1219 }
1220
1221 /* pre-allocate space for data */
1222 rc = ul_buffer_alloc_data(buf, bufsz + 1); /* data + space for \0 */
1223 if (rc)
1224 goto err;
1225
1226 /*
1227 * Make sure groups members are in the same orders as the tree
1228 */
1229 if (has_groups(tb) && scols_table_is_tree(tb))
1230 scols_groups_fix_members_order(tb);
1231
1232 if (tb->format == SCOLS_FMT_HUMAN) {
1233 rc = __scols_calculate(tb, buf);
1234 if (rc != 0)
1235 goto err;
1236 }
1237
1238 return 0;
1239 err:
1240 __scols_cleanup_printing(tb, buf);
1241 return rc;
1242 }
1243