blob: cbe4e3b4d05fc0782cbcfe19969695659c05c8d6 [file] [log] [blame]
bigbiff bigbiffe60683a2013-02-22 20:55:50 -05001/*
2 * TT - Table or Tree, features:
3 * - column width could be defined as absolute or relative to the terminal width
4 * - allows to truncate or wrap data in columns
5 * - prints tree if parent->child relation is defined
6 * - draws the tree by ASCII or UTF8 lines (depends on terminal setting)
7 *
8 * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
9 *
10 * This file may be redistributed under the terms of the
11 * GNU Lesser General Public License.
12 */
13#include <stdio.h>
14#include <stdlib.h>
15#include <unistd.h>
16#include <string.h>
17#include <termios.h>
18#include <ctype.h>
19
20#include "c.h"
21#include "nls.h"
22#include "widechar.h"
23#include "tt.h"
24#include "mbsalign.h"
25#include "ttyutils.h"
26
27struct tt_symbols {
28 const char *branch;
29 const char *vert;
30 const char *right;
31};
32
33static const struct tt_symbols ascii_tt_symbols = {
34 .branch = "|-",
35 .vert = "| ",
36 .right = "`-",
37};
38
39#ifdef HAVE_WIDECHAR
40#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */
41#define UTF_VR "\342\224\234" /* U+251C, Vertical and right */
42#define UTF_H "\342\224\200" /* U+2500, Horizontal */
43#define UTF_UR "\342\224\224" /* U+2514, Up and right */
44
45static const struct tt_symbols utf8_tt_symbols = {
46 .branch = UTF_VR UTF_H,
47 .vert = UTF_V " ",
48 .right = UTF_UR UTF_H,
49};
50#endif /* !HAVE_WIDECHAR */
51
52#define is_last_column(_tb, _cl) \
53 list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns)
54
55/*
56 * Counts number of cells in multibyte string. For all control and
57 * non-printable chars is the result width enlarged to store \x?? hex
58 * sequence. See mbs_safe_encode().
59 */
60static size_t mbs_safe_width(const char *s)
61{
62 mbstate_t st;
63 const char *p = s;
64 size_t width = 0;
65
66 memset(&st, 0, sizeof(st));
67
68 while (p && *p) {
69 if (iscntrl((unsigned char) *p)) {
70 width += 4; /* *p encoded to \x?? */
71 p++;
72 }
73#ifdef HAVE_WIDECHAR
74 else {
75 wchar_t wc;
76 size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
77
78 if (len == 0)
79 break;
80
81 if (len == (size_t) -1 || len == (size_t) -2) {
82 len = 1;
83 width += (isprint((unsigned char) *p) ? 1 : 4);
84
85 } if (!iswprint(wc))
86 width += len * 4; /* hex encode whole sequence */
87 else
88 width += wcwidth(wc); /* number of cells */
89 p += len;
90 }
91#else
92 else if (!isprint((unsigned char) *p)) {
93 width += 4; /* *p encoded to \x?? */
94 p++;
95 } else {
96 width++;
97 p++;
98 }
99#endif
100 }
101
102 return width;
103}
104
105/*
106 * Returns allocated string where all control and non-printable chars are
107 * replaced with \x?? hex sequence.
108 */
109static char *mbs_safe_encode(const char *s, size_t *width)
110{
111 mbstate_t st;
112 const char *p = s;
113 char *res, *r;
114 size_t sz = s ? strlen(s) : 0;
115
116
117 if (!sz)
118 return NULL;
119
120 memset(&st, 0, sizeof(st));
121
122 res = malloc((sz * 4) + 1);
123 if (!res)
124 return NULL;
125
126 r = res;
127 *width = 0;
128
129 while (p && *p) {
130 if (iscntrl((unsigned char) *p)) {
131 sprintf(r, "\\x%02x", (unsigned char) *p);
132 r += 4;
133 *width += 4;
134 p++;
135 }
136#ifdef HAVE_WIDECHAR
137 else {
138 wchar_t wc;
139 size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
140
141 if (len == 0)
142 break; /* end of string */
143
144 if (len == (size_t) -1 || len == (size_t) -2) {
145 len = 1;
146 /*
147 * Not valid multibyte sequence -- maybe it's
148 * printable char according to the current locales.
149 */
150 if (!isprint((unsigned char) *p)) {
151 sprintf(r, "\\x%02x", (unsigned char) *p);
152 r += 4;
153 *width += 4;
154 } else {
155 width++;
156 *r++ = *p;
157 }
158 } else if (!iswprint(wc)) {
159 size_t i;
160 for (i = 0; i < len; i++) {
161 sprintf(r, "\\x%02x", (unsigned char) *p);
162 r += 4;
163 *width += 4;
164 }
165 } else {
166 memcpy(r, p, len);
167 r += len;
168 *width += wcwidth(wc);
169 }
170 p += len;
171 }
172#else
173 else if (!isprint((unsigned char) *p)) {
174 sprintf(r, "\\x%02x", (unsigned char) *p);
175 p++;
176 r += 4;
177 *width += 4;
178 } else {
179 *r++ = *p++;
180 *width++;
181 }
182#endif
183 }
184
185 *r = '\0';
186
187 return res;
188}
189
190/*
191 * @flags: TT_FL_* flags (usually TT_FL_{ASCII,RAW})
192 *
193 * Returns: newly allocated table
194 */
195struct tt *tt_new_table(int flags)
196{
197 struct tt *tb;
198
199 tb = calloc(1, sizeof(struct tt));
200 if (!tb)
201 return NULL;
202
203 tb->flags = flags;
204 INIT_LIST_HEAD(&tb->tb_lines);
205 INIT_LIST_HEAD(&tb->tb_columns);
206
207#if defined(HAVE_WIDECHAR)
208 if (!(flags & TT_FL_ASCII) && !strcmp(nl_langinfo(CODESET), "UTF-8"))
209 tb->symbols = &utf8_tt_symbols;
210 else
211#endif
212 tb->symbols = &ascii_tt_symbols;
213
214 tb->first_run = TRUE;
215 return tb;
216}
217
218void tt_remove_lines(struct tt *tb)
219{
220 if (!tb)
221 return;
222
223 while (!list_empty(&tb->tb_lines)) {
224 struct tt_line *ln = list_entry(tb->tb_lines.next,
225 struct tt_line, ln_lines);
226 list_del(&ln->ln_lines);
227 free(ln->data);
228 free(ln);
229 }
230}
231
232void tt_free_table(struct tt *tb)
233{
234 if (!tb)
235 return;
236
237 tt_remove_lines(tb);
238
239 while (!list_empty(&tb->tb_columns)) {
240 struct tt_column *cl = list_entry(tb->tb_columns.next,
241 struct tt_column, cl_columns);
242 list_del(&cl->cl_columns);
243 free(cl);
244 }
245 free(tb);
246}
247
248
249/*
250 * @tb: table
251 * @name: column header
252 * @whint: column width hint (absolute width: N > 1; relative width: N < 1)
253 * @flags: usually TT_FL_{TREE,TRUNCATE}
254 *
255 * The column width is possible to define by three ways:
256 *
257 * @whint = 0..1 : relative width, percent of terminal width
258 *
259 * @whint = 1..N : absolute width, empty colum will be truncated to
260 * the column header width
261 *
262 * @whint = 1..N
263 * @flags = TT_FL_STRICTWIDTH
264 * : absolute width, empty colum won't be truncated
265 *
266 * The column is necessary to address (for example for tt_line_set_data()) by
267 * sequential number. The first defined column has the colnum = 0. For example:
268 *
269 * tt_define_column(tab, "FOO", 0.5, 0); // colnum = 0
270 * tt_define_column(tab, "BAR", 0.5, 0); // colnum = 1
271 * .
272 * .
273 * tt_line_set_data(line, 0, "foo-data"); // FOO column
274 * tt_line_set_data(line, 1, "bar-data"); // BAR column
275 *
276 * Returns: newly allocated column definition
277 */
278struct tt_column *tt_define_column(struct tt *tb, const char *name,
279 double whint, int flags)
280{
281 struct tt_column *cl;
282
283 if (!tb)
284 return NULL;
285 cl = calloc(1, sizeof(*cl));
286 if (!cl)
287 return NULL;
288
289 cl->name = name;
290 cl->width_hint = whint;
291 cl->flags = flags;
292 cl->seqnum = tb->ncols++;
293
294 if (flags & TT_FL_TREE)
295 tb->flags |= TT_FL_TREE;
296
297 INIT_LIST_HEAD(&cl->cl_columns);
298 list_add_tail(&cl->cl_columns, &tb->tb_columns);
299 return cl;
300}
301
302/*
303 * @tb: table
304 * @parent: parental line or NULL
305 *
306 * Returns: newly allocate line
307 */
308struct tt_line *tt_add_line(struct tt *tb, struct tt_line *parent)
309{
310 struct tt_line *ln = NULL;
311
312 if (!tb || !tb->ncols)
313 goto err;
314 ln = calloc(1, sizeof(*ln));
315 if (!ln)
316 goto err;
317 ln->data = calloc(tb->ncols, sizeof(char *));
318 if (!ln->data)
319 goto err;
320
321 ln->table = tb;
322 ln->parent = parent;
323 INIT_LIST_HEAD(&ln->ln_lines);
324 INIT_LIST_HEAD(&ln->ln_children);
325 INIT_LIST_HEAD(&ln->ln_branch);
326
327 list_add_tail(&ln->ln_lines, &tb->tb_lines);
328
329 if (parent)
330 list_add_tail(&ln->ln_children, &parent->ln_branch);
331 return ln;
332err:
333 free(ln);
334 return NULL;
335}
336
337/*
338 * @tb: table
339 * @colnum: number of column (0..N)
340 *
341 * Returns: pointer to column or NULL
342 */
343struct tt_column *tt_get_column(struct tt *tb, size_t colnum)
344{
345 struct list_head *p;
346
347 list_for_each(p, &tb->tb_columns) {
348 struct tt_column *cl =
349 list_entry(p, struct tt_column, cl_columns);
350 if (cl->seqnum == colnum)
351 return cl;
352 }
353 return NULL;
354}
355
356/*
357 * @ln: line
358 * @colnum: number of column (0..N)
359 * @data: printable data
360 *
361 * Stores data that will be printed to the table cell.
362 */
363int tt_line_set_data(struct tt_line *ln, int colnum, const char *data)
364{
365 struct tt_column *cl;
366
367 if (!ln)
368 return -1;
369 cl = tt_get_column(ln->table, colnum);
370 if (!cl)
371 return -1;
372
373 if (ln->data[cl->seqnum]) {
374 size_t sz = strlen(ln->data[cl->seqnum]);;
375 ln->data_sz = ln->data_sz > sz ? ln->data_sz - sz : 0;
376 }
377
378 ln->data[cl->seqnum] = data;
379 if (data)
380 ln->data_sz += strlen(data);
381 return 0;
382}
383
384int tt_line_set_userdata(struct tt_line *ln, void *data)
385{
386 if (!ln)
387 return -1;
388 ln->userdata = data;
389 return 0;
390}
391
392static char *line_get_ascii_art(struct tt_line *ln, char *buf, size_t *bufsz)
393{
394 const char *art;
395 size_t len;
396
397 if (!ln->parent)
398 return buf;
399
400 buf = line_get_ascii_art(ln->parent, buf, bufsz);
401 if (!buf)
402 return NULL;
403
404 if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch))
405 art = " ";
406 else
407 art = ln->table->symbols->vert;
408
409 len = strlen(art);
410 if (*bufsz < len)
411 return NULL; /* no space, internal error */
412
413 memcpy(buf, art, len);
414 *bufsz -= len;
415 return buf + len;
416}
417
418static char *line_get_data(struct tt_line *ln, struct tt_column *cl,
419 char *buf, size_t bufsz)
420{
421 const char *data = ln->data[cl->seqnum];
422 const struct tt_symbols *sym;
423 char *p = buf;
424
425 memset(buf, 0, bufsz);
426
427 if (!data)
428 return NULL;
429 if (!(cl->flags & TT_FL_TREE)) {
430 strncpy(buf, data, bufsz);
431 buf[bufsz - 1] = '\0';
432 return buf;
433 }
434
435 /*
436 * Tree stuff
437 */
438 if (ln->parent) {
439 p = line_get_ascii_art(ln->parent, buf, &bufsz);
440 if (!p)
441 return NULL;
442 }
443
444 sym = ln->table->symbols;
445
446 if (!ln->parent)
447 snprintf(p, bufsz, "%s", data); /* root node */
448 else if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch))
449 snprintf(p, bufsz, "%s%s", sym->right, data); /* last chaild */
450 else
451 snprintf(p, bufsz, "%s%s", sym->branch, data); /* any child */
452
453 return buf;
454}
455
456/*
457 * This function counts column width.
458 *
459 * For the TT_FL_NOEXTREMES columns is possible to call this function two
460 * times. The first pass counts width and average width. If the column
461 * contains too large fields (width greater than 2 * average) then the column
462 * is marked as "extreme". In the second pass all extreme fields are ignored
463 * and column width is counted from non-extreme fields only.
464 */
465static void count_column_width(struct tt *tb, struct tt_column *cl,
466 char *buf, size_t bufsz)
467{
468 struct list_head *lp;
469 int count = 0;
470 size_t sum = 0;
471
472 cl->width = 0;
473
474 list_for_each(lp, &tb->tb_lines) {
475 struct tt_line *ln = list_entry(lp, struct tt_line, ln_lines);
476 char *data = line_get_data(ln, cl, buf, bufsz);
477 size_t len = data ? mbs_safe_width(data) : 0;
478
479 if (len == (size_t) -1) /* ignore broken multibyte strings */
480 len = 0;
481
482 if (len > cl->width_max)
483 cl->width_max = len;
484
485 if (cl->is_extreme && len > cl->width_avg * 2)
486 continue;
487 else if (cl->flags & TT_FL_NOEXTREMES) {
488 sum += len;
489 count++;
490 }
491 if (len > cl->width)
492 cl->width = len;
493 }
494
495 if (count && cl->width_avg == 0) {
496 cl->width_avg = sum / count;
497
498 if (cl->width_max > cl->width_avg * 2)
499 cl->is_extreme = 1;
500 }
501
502 /* check and set minimal column width */
503 if (cl->name)
504 cl->width_min = mbs_safe_width(cl->name);
505
506 /* enlarge to minimal width */
507 if (cl->width < cl->width_min && !(cl->flags & TT_FL_STRICTWIDTH))
508 cl->width = cl->width_min;
509
510 /* use relative size for large columns */
511 else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint &&
512 cl->width_min < (size_t) cl->width_hint)
513
514 cl->width = (size_t) cl->width_hint;
515}
516
517/*
518 * This is core of the tt_* voodo...
519 */
520static void recount_widths(struct tt *tb, char *buf, size_t bufsz)
521{
522 struct list_head *p;
523 size_t width = 0; /* output width */
524 int trunc_only;
525 int extremes = 0;
526
527 /* set basic columns width
528 */
529 list_for_each(p, &tb->tb_columns) {
530 struct tt_column *cl =
531 list_entry(p, struct tt_column, cl_columns);
532
533 count_column_width(tb, cl, buf, bufsz);
534 width += cl->width + (is_last_column(tb, cl) ? 0 : 1);
535 extremes += cl->is_extreme;
536 }
537
538 if (!tb->is_term)
539 return;
540
541 /* reduce columns with extreme fields
542 */
543 if (width > tb->termwidth && extremes) {
544 list_for_each(p, &tb->tb_columns) {
545 struct tt_column *cl = list_entry(p, struct tt_column, cl_columns);
546 size_t org_width;
547
548 if (!cl->is_extreme)
549 continue;
550
551 org_width = cl->width;
552 count_column_width(tb, cl, buf, bufsz);
553
554 if (org_width > cl->width)
555 width -= org_width - cl->width;
556 else
557 extremes--; /* hmm... nothing reduced */
558 }
559 }
560
561 if (width < tb->termwidth) {
562 /* try to found extreme column which fits into available space
563 */
564 if (extremes) {
565 /* enlarge the first extreme column */
566 list_for_each(p, &tb->tb_columns) {
567 struct tt_column *cl =
568 list_entry(p, struct tt_column, cl_columns);
569 size_t add;
570
571 if (!cl->is_extreme)
572 continue;
573
574 /* this column is tooo large, ignore?
575 if (cl->width_max - cl->width >
576 (tb->termwidth - width))
577 continue;
578 */
579
580 add = tb->termwidth - width;
581 if (add && cl->width + add > cl->width_max)
582 add = cl->width_max - cl->width;
583
584 cl->width += add;
585 width += add;
586
587 if (width == tb->termwidth)
588 break;
589 }
590 }
591 if (width < tb->termwidth) {
592 /* enalarge the last column */
593 struct tt_column *cl = list_entry(
594 tb->tb_columns.prev, struct tt_column, cl_columns);
595
596 if (!(cl->flags & TT_FL_RIGHT) && tb->termwidth - width > 0) {
597 cl->width += tb->termwidth - width;
598 width = tb->termwidth;
599 }
600 }
601 }
602
603 /* bad, we have to reduce output width, this is done in two steps:
604 * 1/ reduce columns with a relative width and with truncate flag
605 * 2) reduce columns with a relative width without truncate flag
606 */
607 trunc_only = 1;
608 while (width > tb->termwidth) {
609 size_t org = width;
610
611 list_for_each(p, &tb->tb_columns) {
612 struct tt_column *cl =
613 list_entry(p, struct tt_column, cl_columns);
614
615 if (width <= tb->termwidth)
616 break;
617 if (cl->width_hint > 1 && !(cl->flags & TT_FL_TRUNC))
618 continue; /* never truncate columns with absolute sizes */
619 if (cl->flags & TT_FL_TREE)
620 continue; /* never truncate the tree */
621 if (trunc_only && !(cl->flags & TT_FL_TRUNC))
622 continue;
623 if (cl->width == cl->width_min)
624 continue;
625
626 /* truncate column with relative sizes */
627 if (cl->width_hint < 1 && cl->width > 0 && width > 0 &&
628 cl->width > cl->width_hint * tb->termwidth) {
629 cl->width--;
630 width--;
631 }
632 /* truncate column with absolute size */
633 if (cl->width_hint > 1 && cl->width > 0 && width > 0 &&
634 !trunc_only) {
635 cl->width--;
636 width--;
637 }
638
639 }
640 if (org == width) {
641 if (trunc_only)
642 trunc_only = 0;
643 else
644 break;
645 }
646 }
647
648/*
649 fprintf(stderr, "terminal: %d, output: %d\n", tb->termwidth, width);
650
651 list_for_each(p, &tb->tb_columns) {
652 struct tt_column *cl =
653 list_entry(p, struct tt_column, cl_columns);
654
655 fprintf(stderr, "width: %s=%zd [hint=%d, avg=%zd, max=%zd, extreme=%s]\n",
656 cl->name, cl->width,
657 cl->width_hint > 1 ? (int) cl->width_hint :
658 (int) (cl->width_hint * tb->termwidth),
659 cl->width_avg,
660 cl->width_max,
661 cl->is_extreme ? "yes" : "not");
662 }
663*/
664 return;
665}
666
667void tt_fputs_quoted(const char *data, FILE *out)
668{
669 const char *p;
670
671 fputc('"', out);
672 for (p = data; p && *p; p++) {
673 if ((unsigned char) *p == 0x22 || /* " */
674 (unsigned char) *p == 0x5c || /* \ */
675 !isprint((unsigned char) *p) ||
676 iscntrl((unsigned char) *p)) {
677
678 fprintf(out, "\\x%02x", (unsigned char) *p);
679 } else
680 fputc(*p, out);
681 }
682 fputc('"', out);
683}
684
685void tt_fputs_nonblank(const char *data, FILE *out)
686{
687 const char *p;
688
689 for (p = data; p && *p; p++) {
690 if (isblank((unsigned char) *p) ||
691 (unsigned char) *p == 0x5c || /* \ */
692 !isprint((unsigned char) *p) ||
693 iscntrl((unsigned char) *p)) {
694
695 fprintf(out, "\\x%02x", (unsigned char) *p);
696
697 } else
698 fputc(*p, out);
699 }
700}
701
702/*
703 * Prints data, data maybe be printed in more formats (raw, NAME=xxx pairs) and
704 * control and non-printable chars maybe encoded in \x?? hex encoding.
705 */
706static void print_data(struct tt *tb, struct tt_column *cl, char *data)
707{
708 size_t len = 0, i, width;
709 char *buf;
710
711 if (!data)
712 data = "";
713
714 /* raw mode */
715 if (tb->flags & TT_FL_RAW) {
716 tt_fputs_nonblank(data, stdout);
717 if (!is_last_column(tb, cl))
718 fputc(' ', stdout);
719 return;
720 }
721
722 /* NAME=value mode */
723 if (tb->flags & TT_FL_EXPORT) {
724 fprintf(stdout, "%s=", cl->name);
725 tt_fputs_quoted(data, stdout);
726 if (!is_last_column(tb, cl))
727 fputc(' ', stdout);
728 return;
729 }
730
731 /* note that 'len' and 'width' are number of cells, not bytes */
732 buf = mbs_safe_encode(data, &len);
733 data = buf;
734 if (!data)
735 data = "";
736
737 if (!len || len == (size_t) -1) {
738 len = 0;
739 data = NULL;
740 }
741 width = cl->width;
742
743 if (is_last_column(tb, cl) && len < width)
744 width = len;
745
746 /* truncate data */
747 if (len > width && (cl->flags & TT_FL_TRUNC)) {
748 if (data)
749 len = mbs_truncate(data, &width);
750 if (!data || len == (size_t) -1) {
751 len = 0;
752 data = NULL;
753 }
754 }
755 if (data) {
756 if (!(tb->flags & TT_FL_RAW) && (cl->flags & TT_FL_RIGHT)) {
757 size_t xw = cl->width;
758 fprintf(stdout, "%*s", (int) xw, data);
759 if (len < xw)
760 len = xw;
761 }
762 else
763 fputs(data, stdout);
764 }
765 for (i = len; i < width; i++)
766 fputc(' ', stdout); /* padding */
767
768 if (!is_last_column(tb, cl)) {
769 if (len > width && !(cl->flags & TT_FL_TRUNC)) {
770 fputc('\n', stdout);
771 for (i = 0; i <= (size_t) cl->seqnum; i++) {
772 struct tt_column *x = tt_get_column(tb, i);
773 printf("%*s ", -((int)x->width), " ");
774 }
775 } else
776 fputc(' ', stdout); /* columns separator */
777 }
778
779 free(buf);
780}
781
782static void print_line(struct tt_line *ln, char *buf, size_t bufsz)
783{
784 struct list_head *p;
785
786 /* set width according to the size of data
787 */
788 list_for_each(p, &ln->table->tb_columns) {
789 struct tt_column *cl =
790 list_entry(p, struct tt_column, cl_columns);
791
792 print_data(ln->table, cl, line_get_data(ln, cl, buf, bufsz));
793 }
794 fputc('\n', stdout);
795}
796
797static void print_header(struct tt *tb, char *buf, size_t bufsz)
798{
799 struct list_head *p;
800
801 if (!tb->first_run ||
802 (tb->flags & TT_FL_NOHEADINGS) ||
803 (tb->flags & TT_FL_EXPORT) ||
804 list_empty(&tb->tb_lines))
805 return;
806
807 /* set width according to the size of data
808 */
809 list_for_each(p, &tb->tb_columns) {
810 struct tt_column *cl =
811 list_entry(p, struct tt_column, cl_columns);
812
813 strncpy(buf, cl->name, bufsz);
814 buf[bufsz - 1] = '\0';
815 print_data(tb, cl, buf);
816 }
817 fputc('\n', stdout);
818}
819
820static void print_table(struct tt *tb, char *buf, size_t bufsz)
821{
822 struct list_head *p;
823
824 print_header(tb, buf, bufsz);
825
826 list_for_each(p, &tb->tb_lines) {
827 struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
828
829 print_line(ln, buf, bufsz);
830 }
831}
832
833static void print_tree_line(struct tt_line *ln, char *buf, size_t bufsz)
834{
835 struct list_head *p;
836
837 print_line(ln, buf, bufsz);
838
839 if (list_empty(&ln->ln_branch))
840 return;
841
842 /* print all children */
843 list_for_each(p, &ln->ln_branch) {
844 struct tt_line *chld =
845 list_entry(p, struct tt_line, ln_children);
846 print_tree_line(chld, buf, bufsz);
847 }
848}
849
850static void print_tree(struct tt *tb, char *buf, size_t bufsz)
851{
852 struct list_head *p;
853
854 print_header(tb, buf, bufsz);
855
856 list_for_each(p, &tb->tb_lines) {
857 struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
858
859 if (ln->parent)
860 continue;
861
862 print_tree_line(ln, buf, bufsz);
863 }
864}
865
866/*
867 * @tb: table
868 *
869 * Prints the table to stdout
870 */
871int tt_print_table(struct tt *tb)
872{
873 char *line;
874 size_t line_sz;
875 struct list_head *p;
876
877 if (!tb)
878 return -1;
879
880 if (tb->first_run) {
881 tb->is_term = isatty(STDOUT_FILENO);
882
883 if (tb->is_term && !tb->termwidth)
884 tb->termwidth = get_terminal_width();
885 if (tb->termwidth <= 0)
886 tb->termwidth = 80;
887 }
888
889 line_sz = tb->termwidth;
890
891 list_for_each(p, &tb->tb_lines) {
892 struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
893 if (ln->data_sz > line_sz)
894 line_sz = ln->data_sz;
895 }
896
897 line_sz++; /* make a space for \0 */
898 line = malloc(line_sz);
899 if (!line)
900 return -1;
901
902 if (tb->first_run &&
903 !((tb->flags & TT_FL_RAW) || (tb->flags & TT_FL_EXPORT)))
904 recount_widths(tb, line, line_sz);
905
906 if (tb->flags & TT_FL_TREE)
907 print_tree(tb, line, line_sz);
908 else
909 print_table(tb, line, line_sz);
910
911 free(line);
912
913 tb->first_run = FALSE;
914 return 0;
915}
916
917#ifdef TEST_PROGRAM
918#include <errno.h>
919
920enum { MYCOL_NAME, MYCOL_FOO, MYCOL_BAR, MYCOL_PATH };
921
922int main(int argc, char *argv[])
923{
924 struct tt *tb;
925 struct tt_line *ln, *pr, *root;
926 int flags = 0, notree = 0, i;
927
928 if (argc == 2 && !strcmp(argv[1], "--help")) {
929 printf("%s [--ascii | --raw | --list]\n",
930 program_invocation_short_name);
931 return EXIT_SUCCESS;
932 } else if (argc == 2 && !strcmp(argv[1], "--ascii")) {
933 flags |= TT_FL_ASCII;
934 } else if (argc == 2 && !strcmp(argv[1], "--raw")) {
935 flags |= TT_FL_RAW;
936 notree = 1;
937 } else if (argc == 2 && !strcmp(argv[1], "--export")) {
938 flags |= TT_FL_EXPORT;
939 notree = 1;
940 } else if (argc == 2 && !strcmp(argv[1], "--list"))
941 notree = 1;
942
943 setlocale(LC_ALL, "");
944 bindtextdomain(PACKAGE, LOCALEDIR);
945 textdomain(PACKAGE);
946
947 tb = tt_new_table(flags);
948 if (!tb)
949 err(EXIT_FAILURE, "table initialization failed");
950
951 tt_define_column(tb, "NAME", 0.3, notree ? 0 : TT_FL_TREE);
952 tt_define_column(tb, "FOO", 0.3, TT_FL_TRUNC);
953 tt_define_column(tb, "BAR", 0.3, 0);
954 tt_define_column(tb, "PATH", 0.3, 0);
955
956 for (i = 0; i < 2; i++) {
957 root = ln = tt_add_line(tb, NULL);
958 tt_line_set_data(ln, MYCOL_NAME, "AAA");
959 tt_line_set_data(ln, MYCOL_FOO, "a-foo-foo");
960 tt_line_set_data(ln, MYCOL_BAR, "barBar-A");
961 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA");
962
963 pr = ln = tt_add_line(tb, ln);
964 tt_line_set_data(ln, MYCOL_NAME, "AAA.A");
965 tt_line_set_data(ln, MYCOL_FOO, "a.a-foo-foo");
966 tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A");
967 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A");
968
969 ln = tt_add_line(tb, pr);
970 tt_line_set_data(ln, MYCOL_NAME, "AAA.A.AAA");
971 tt_line_set_data(ln, MYCOL_FOO, "a.a.a-foo-foo");
972 tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.A");
973 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/AAA");
974
975 ln = tt_add_line(tb, root);
976 tt_line_set_data(ln, MYCOL_NAME, "AAA.B");
977 tt_line_set_data(ln, MYCOL_FOO, "a.b-foo-foo");
978 tt_line_set_data(ln, MYCOL_BAR, "barBar-A.B");
979 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/B");
980
981 ln = tt_add_line(tb, pr);
982 tt_line_set_data(ln, MYCOL_NAME, "AAA.A.BBB");
983 tt_line_set_data(ln, MYCOL_FOO, "a.a.b-foo-foo");
984 tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.BBB");
985 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/BBB");
986
987 ln = tt_add_line(tb, pr);
988 tt_line_set_data(ln, MYCOL_NAME, "AAA.A.CCC");
989 tt_line_set_data(ln, MYCOL_FOO, "a.a.c-foo-foo");
990 tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.CCC");
991 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/CCC");
992
993 ln = tt_add_line(tb, root);
994 tt_line_set_data(ln, MYCOL_NAME, "AAA.C");
995 tt_line_set_data(ln, MYCOL_FOO, "a.c-foo-foo");
996 tt_line_set_data(ln, MYCOL_BAR, "barBar-A.C");
997 tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/C");
998 }
999
1000 tt_print_table(tb);
1001 tt_free_table(tb);
1002
1003 return EXIT_SUCCESS;
1004}
1005#endif