| /* |
| * TT - Table or Tree, features: |
| * - column width could be defined as absolute or relative to the terminal width |
| * - allows to truncate or wrap data in columns |
| * - prints tree if parent->child relation is defined |
| * - draws the tree by ASCII or UTF8 lines (depends on terminal setting) |
| * |
| * Copyright (C) 2010 Karel Zak <kzak@redhat.com> |
| * |
| * This file may be redistributed under the terms of the |
| * GNU Lesser General Public License. |
| */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <ctype.h> |
| |
| #include "c.h" |
| #include "nls.h" |
| #include "widechar.h" |
| #include "tt.h" |
| #include "mbsalign.h" |
| #include "ttyutils.h" |
| |
| struct tt_symbols { |
| const char *branch; |
| const char *vert; |
| const char *right; |
| }; |
| |
| static const struct tt_symbols ascii_tt_symbols = { |
| .branch = "|-", |
| .vert = "| ", |
| .right = "`-", |
| }; |
| |
| #ifdef HAVE_WIDECHAR |
| #define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char */ |
| #define UTF_VR "\342\224\234" /* U+251C, Vertical and right */ |
| #define UTF_H "\342\224\200" /* U+2500, Horizontal */ |
| #define UTF_UR "\342\224\224" /* U+2514, Up and right */ |
| |
| static const struct tt_symbols utf8_tt_symbols = { |
| .branch = UTF_VR UTF_H, |
| .vert = UTF_V " ", |
| .right = UTF_UR UTF_H, |
| }; |
| #endif /* !HAVE_WIDECHAR */ |
| |
| #define is_last_column(_tb, _cl) \ |
| list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) |
| |
| /* |
| * Counts number of cells in multibyte string. For all control and |
| * non-printable chars is the result width enlarged to store \x?? hex |
| * sequence. See mbs_safe_encode(). |
| */ |
| static size_t mbs_safe_width(const char *s) |
| { |
| mbstate_t st; |
| const char *p = s; |
| size_t width = 0; |
| |
| memset(&st, 0, sizeof(st)); |
| |
| while (p && *p) { |
| if (iscntrl((unsigned char) *p)) { |
| width += 4; /* *p encoded to \x?? */ |
| p++; |
| } |
| #ifdef HAVE_WIDECHAR |
| else { |
| wchar_t wc; |
| size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); |
| |
| if (len == 0) |
| break; |
| |
| if (len == (size_t) -1 || len == (size_t) -2) { |
| len = 1; |
| width += (isprint((unsigned char) *p) ? 1 : 4); |
| |
| } if (!iswprint(wc)) |
| width += len * 4; /* hex encode whole sequence */ |
| else |
| width += wcwidth(wc); /* number of cells */ |
| p += len; |
| } |
| #else |
| else if (!isprint((unsigned char) *p)) { |
| width += 4; /* *p encoded to \x?? */ |
| p++; |
| } else { |
| width++; |
| p++; |
| } |
| #endif |
| } |
| |
| return width; |
| } |
| |
| /* |
| * Returns allocated string where all control and non-printable chars are |
| * replaced with \x?? hex sequence. |
| */ |
| static char *mbs_safe_encode(const char *s, size_t *width) |
| { |
| mbstate_t st; |
| const char *p = s; |
| char *res, *r; |
| size_t sz = s ? strlen(s) : 0; |
| |
| |
| if (!sz) |
| return NULL; |
| |
| memset(&st, 0, sizeof(st)); |
| |
| res = malloc((sz * 4) + 1); |
| if (!res) |
| return NULL; |
| |
| r = res; |
| *width = 0; |
| |
| while (p && *p) { |
| if (iscntrl((unsigned char) *p)) { |
| sprintf(r, "\\x%02x", (unsigned char) *p); |
| r += 4; |
| *width += 4; |
| p++; |
| } |
| #ifdef HAVE_WIDECHAR |
| else { |
| wchar_t wc; |
| size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st); |
| |
| if (len == 0) |
| break; /* end of string */ |
| |
| if (len == (size_t) -1 || len == (size_t) -2) { |
| len = 1; |
| /* |
| * Not valid multibyte sequence -- maybe it's |
| * printable char according to the current locales. |
| */ |
| if (!isprint((unsigned char) *p)) { |
| sprintf(r, "\\x%02x", (unsigned char) *p); |
| r += 4; |
| *width += 4; |
| } else { |
| width++; |
| *r++ = *p; |
| } |
| } else if (!iswprint(wc)) { |
| size_t i; |
| for (i = 0; i < len; i++) { |
| sprintf(r, "\\x%02x", (unsigned char) *p); |
| r += 4; |
| *width += 4; |
| } |
| } else { |
| memcpy(r, p, len); |
| r += len; |
| *width += wcwidth(wc); |
| } |
| p += len; |
| } |
| #else |
| else if (!isprint((unsigned char) *p)) { |
| sprintf(r, "\\x%02x", (unsigned char) *p); |
| p++; |
| r += 4; |
| *width += 4; |
| } else { |
| *r++ = *p++; |
| *width++; |
| } |
| #endif |
| } |
| |
| *r = '\0'; |
| |
| return res; |
| } |
| |
| /* |
| * @flags: TT_FL_* flags (usually TT_FL_{ASCII,RAW}) |
| * |
| * Returns: newly allocated table |
| */ |
| struct tt *tt_new_table(int flags) |
| { |
| struct tt *tb; |
| |
| tb = calloc(1, sizeof(struct tt)); |
| if (!tb) |
| return NULL; |
| |
| tb->flags = flags; |
| INIT_LIST_HEAD(&tb->tb_lines); |
| INIT_LIST_HEAD(&tb->tb_columns); |
| |
| #if defined(HAVE_WIDECHAR) |
| if (!(flags & TT_FL_ASCII) && !strcmp(nl_langinfo(CODESET), "UTF-8")) |
| tb->symbols = &utf8_tt_symbols; |
| else |
| #endif |
| tb->symbols = &ascii_tt_symbols; |
| |
| tb->first_run = TRUE; |
| return tb; |
| } |
| |
| void tt_remove_lines(struct tt *tb) |
| { |
| if (!tb) |
| return; |
| |
| while (!list_empty(&tb->tb_lines)) { |
| struct tt_line *ln = list_entry(tb->tb_lines.next, |
| struct tt_line, ln_lines); |
| list_del(&ln->ln_lines); |
| free(ln->data); |
| free(ln); |
| } |
| } |
| |
| void tt_free_table(struct tt *tb) |
| { |
| if (!tb) |
| return; |
| |
| tt_remove_lines(tb); |
| |
| while (!list_empty(&tb->tb_columns)) { |
| struct tt_column *cl = list_entry(tb->tb_columns.next, |
| struct tt_column, cl_columns); |
| list_del(&cl->cl_columns); |
| free(cl); |
| } |
| free(tb); |
| } |
| |
| |
| /* |
| * @tb: table |
| * @name: column header |
| * @whint: column width hint (absolute width: N > 1; relative width: N < 1) |
| * @flags: usually TT_FL_{TREE,TRUNCATE} |
| * |
| * The column width is possible to define by three ways: |
| * |
| * @whint = 0..1 : relative width, percent of terminal width |
| * |
| * @whint = 1..N : absolute width, empty colum will be truncated to |
| * the column header width |
| * |
| * @whint = 1..N |
| * @flags = TT_FL_STRICTWIDTH |
| * : absolute width, empty colum won't be truncated |
| * |
| * The column is necessary to address (for example for tt_line_set_data()) by |
| * sequential number. The first defined column has the colnum = 0. For example: |
| * |
| * tt_define_column(tab, "FOO", 0.5, 0); // colnum = 0 |
| * tt_define_column(tab, "BAR", 0.5, 0); // colnum = 1 |
| * . |
| * . |
| * tt_line_set_data(line, 0, "foo-data"); // FOO column |
| * tt_line_set_data(line, 1, "bar-data"); // BAR column |
| * |
| * Returns: newly allocated column definition |
| */ |
| struct tt_column *tt_define_column(struct tt *tb, const char *name, |
| double whint, int flags) |
| { |
| struct tt_column *cl; |
| |
| if (!tb) |
| return NULL; |
| cl = calloc(1, sizeof(*cl)); |
| if (!cl) |
| return NULL; |
| |
| cl->name = name; |
| cl->width_hint = whint; |
| cl->flags = flags; |
| cl->seqnum = tb->ncols++; |
| |
| if (flags & TT_FL_TREE) |
| tb->flags |= TT_FL_TREE; |
| |
| INIT_LIST_HEAD(&cl->cl_columns); |
| list_add_tail(&cl->cl_columns, &tb->tb_columns); |
| return cl; |
| } |
| |
| /* |
| * @tb: table |
| * @parent: parental line or NULL |
| * |
| * Returns: newly allocate line |
| */ |
| struct tt_line *tt_add_line(struct tt *tb, struct tt_line *parent) |
| { |
| struct tt_line *ln = NULL; |
| |
| if (!tb || !tb->ncols) |
| goto err; |
| ln = calloc(1, sizeof(*ln)); |
| if (!ln) |
| goto err; |
| ln->data = calloc(tb->ncols, sizeof(char *)); |
| if (!ln->data) |
| goto err; |
| |
| ln->table = tb; |
| ln->parent = parent; |
| INIT_LIST_HEAD(&ln->ln_lines); |
| INIT_LIST_HEAD(&ln->ln_children); |
| INIT_LIST_HEAD(&ln->ln_branch); |
| |
| list_add_tail(&ln->ln_lines, &tb->tb_lines); |
| |
| if (parent) |
| list_add_tail(&ln->ln_children, &parent->ln_branch); |
| return ln; |
| err: |
| free(ln); |
| return NULL; |
| } |
| |
| /* |
| * @tb: table |
| * @colnum: number of column (0..N) |
| * |
| * Returns: pointer to column or NULL |
| */ |
| struct tt_column *tt_get_column(struct tt *tb, size_t colnum) |
| { |
| struct list_head *p; |
| |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| if (cl->seqnum == colnum) |
| return cl; |
| } |
| return NULL; |
| } |
| |
| /* |
| * @ln: line |
| * @colnum: number of column (0..N) |
| * @data: printable data |
| * |
| * Stores data that will be printed to the table cell. |
| */ |
| int tt_line_set_data(struct tt_line *ln, int colnum, const char *data) |
| { |
| struct tt_column *cl; |
| |
| if (!ln) |
| return -1; |
| cl = tt_get_column(ln->table, colnum); |
| if (!cl) |
| return -1; |
| |
| if (ln->data[cl->seqnum]) { |
| size_t sz = strlen(ln->data[cl->seqnum]);; |
| ln->data_sz = ln->data_sz > sz ? ln->data_sz - sz : 0; |
| } |
| |
| ln->data[cl->seqnum] = data; |
| if (data) |
| ln->data_sz += strlen(data); |
| return 0; |
| } |
| |
| int tt_line_set_userdata(struct tt_line *ln, void *data) |
| { |
| if (!ln) |
| return -1; |
| ln->userdata = data; |
| return 0; |
| } |
| |
| static char *line_get_ascii_art(struct tt_line *ln, char *buf, size_t *bufsz) |
| { |
| const char *art; |
| size_t len; |
| |
| if (!ln->parent) |
| return buf; |
| |
| buf = line_get_ascii_art(ln->parent, buf, bufsz); |
| if (!buf) |
| return NULL; |
| |
| if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) |
| art = " "; |
| else |
| art = ln->table->symbols->vert; |
| |
| len = strlen(art); |
| if (*bufsz < len) |
| return NULL; /* no space, internal error */ |
| |
| memcpy(buf, art, len); |
| *bufsz -= len; |
| return buf + len; |
| } |
| |
| static char *line_get_data(struct tt_line *ln, struct tt_column *cl, |
| char *buf, size_t bufsz) |
| { |
| const char *data = ln->data[cl->seqnum]; |
| const struct tt_symbols *sym; |
| char *p = buf; |
| |
| memset(buf, 0, bufsz); |
| |
| if (!data) |
| return NULL; |
| if (!(cl->flags & TT_FL_TREE)) { |
| strncpy(buf, data, bufsz); |
| buf[bufsz - 1] = '\0'; |
| return buf; |
| } |
| |
| /* |
| * Tree stuff |
| */ |
| if (ln->parent) { |
| p = line_get_ascii_art(ln->parent, buf, &bufsz); |
| if (!p) |
| return NULL; |
| } |
| |
| sym = ln->table->symbols; |
| |
| if (!ln->parent) |
| snprintf(p, bufsz, "%s", data); /* root node */ |
| else if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) |
| snprintf(p, bufsz, "%s%s", sym->right, data); /* last chaild */ |
| else |
| snprintf(p, bufsz, "%s%s", sym->branch, data); /* any child */ |
| |
| return buf; |
| } |
| |
| /* |
| * This function counts column width. |
| * |
| * For the TT_FL_NOEXTREMES columns is possible to call this function two |
| * times. The first pass counts width and average width. If the column |
| * contains too large fields (width greater than 2 * average) then the column |
| * is marked as "extreme". In the second pass all extreme fields are ignored |
| * and column width is counted from non-extreme fields only. |
| */ |
| static void count_column_width(struct tt *tb, struct tt_column *cl, |
| char *buf, size_t bufsz) |
| { |
| struct list_head *lp; |
| int count = 0; |
| size_t sum = 0; |
| |
| cl->width = 0; |
| |
| list_for_each(lp, &tb->tb_lines) { |
| struct tt_line *ln = list_entry(lp, struct tt_line, ln_lines); |
| char *data = line_get_data(ln, cl, buf, bufsz); |
| size_t len = data ? mbs_safe_width(data) : 0; |
| |
| if (len == (size_t) -1) /* ignore broken multibyte strings */ |
| len = 0; |
| |
| if (len > cl->width_max) |
| cl->width_max = len; |
| |
| if (cl->is_extreme && len > cl->width_avg * 2) |
| continue; |
| else if (cl->flags & TT_FL_NOEXTREMES) { |
| sum += len; |
| count++; |
| } |
| if (len > cl->width) |
| cl->width = len; |
| } |
| |
| if (count && cl->width_avg == 0) { |
| cl->width_avg = sum / count; |
| |
| if (cl->width_max > cl->width_avg * 2) |
| cl->is_extreme = 1; |
| } |
| |
| /* check and set minimal column width */ |
| if (cl->name) |
| cl->width_min = mbs_safe_width(cl->name); |
| |
| /* enlarge to minimal width */ |
| if (cl->width < cl->width_min && !(cl->flags & TT_FL_STRICTWIDTH)) |
| cl->width = cl->width_min; |
| |
| /* use relative size for large columns */ |
| else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint && |
| cl->width_min < (size_t) cl->width_hint) |
| |
| cl->width = (size_t) cl->width_hint; |
| } |
| |
| /* |
| * This is core of the tt_* voodo... |
| */ |
| static void recount_widths(struct tt *tb, char *buf, size_t bufsz) |
| { |
| struct list_head *p; |
| size_t width = 0; /* output width */ |
| int trunc_only; |
| int extremes = 0; |
| |
| /* set basic columns width |
| */ |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| |
| count_column_width(tb, cl, buf, bufsz); |
| width += cl->width + (is_last_column(tb, cl) ? 0 : 1); |
| extremes += cl->is_extreme; |
| } |
| |
| if (!tb->is_term) |
| return; |
| |
| /* reduce columns with extreme fields |
| */ |
| if (width > tb->termwidth && extremes) { |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = list_entry(p, struct tt_column, cl_columns); |
| size_t org_width; |
| |
| if (!cl->is_extreme) |
| continue; |
| |
| org_width = cl->width; |
| count_column_width(tb, cl, buf, bufsz); |
| |
| if (org_width > cl->width) |
| width -= org_width - cl->width; |
| else |
| extremes--; /* hmm... nothing reduced */ |
| } |
| } |
| |
| if (width < tb->termwidth) { |
| /* try to found extreme column which fits into available space |
| */ |
| if (extremes) { |
| /* enlarge the first extreme column */ |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| size_t add; |
| |
| if (!cl->is_extreme) |
| continue; |
| |
| /* this column is tooo large, ignore? |
| if (cl->width_max - cl->width > |
| (tb->termwidth - width)) |
| continue; |
| */ |
| |
| add = tb->termwidth - width; |
| if (add && cl->width + add > cl->width_max) |
| add = cl->width_max - cl->width; |
| |
| cl->width += add; |
| width += add; |
| |
| if (width == tb->termwidth) |
| break; |
| } |
| } |
| if (width < tb->termwidth) { |
| /* enalarge the last column */ |
| struct tt_column *cl = list_entry( |
| tb->tb_columns.prev, struct tt_column, cl_columns); |
| |
| if (!(cl->flags & TT_FL_RIGHT) && tb->termwidth - width > 0) { |
| cl->width += tb->termwidth - width; |
| width = tb->termwidth; |
| } |
| } |
| } |
| |
| /* bad, we have to reduce output width, this is done in two steps: |
| * 1/ reduce columns with a relative width and with truncate flag |
| * 2) reduce columns with a relative width without truncate flag |
| */ |
| trunc_only = 1; |
| while (width > tb->termwidth) { |
| size_t org = width; |
| |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| |
| if (width <= tb->termwidth) |
| break; |
| if (cl->width_hint > 1 && !(cl->flags & TT_FL_TRUNC)) |
| continue; /* never truncate columns with absolute sizes */ |
| if (cl->flags & TT_FL_TREE) |
| continue; /* never truncate the tree */ |
| if (trunc_only && !(cl->flags & TT_FL_TRUNC)) |
| continue; |
| if (cl->width == cl->width_min) |
| continue; |
| |
| /* truncate column with relative sizes */ |
| if (cl->width_hint < 1 && cl->width > 0 && width > 0 && |
| cl->width > cl->width_hint * tb->termwidth) { |
| cl->width--; |
| width--; |
| } |
| /* truncate column with absolute size */ |
| if (cl->width_hint > 1 && cl->width > 0 && width > 0 && |
| !trunc_only) { |
| cl->width--; |
| width--; |
| } |
| |
| } |
| if (org == width) { |
| if (trunc_only) |
| trunc_only = 0; |
| else |
| break; |
| } |
| } |
| |
| /* |
| fprintf(stderr, "terminal: %d, output: %d\n", tb->termwidth, width); |
| |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| |
| fprintf(stderr, "width: %s=%zd [hint=%d, avg=%zd, max=%zd, extreme=%s]\n", |
| cl->name, cl->width, |
| cl->width_hint > 1 ? (int) cl->width_hint : |
| (int) (cl->width_hint * tb->termwidth), |
| cl->width_avg, |
| cl->width_max, |
| cl->is_extreme ? "yes" : "not"); |
| } |
| */ |
| return; |
| } |
| |
| void tt_fputs_quoted(const char *data, FILE *out) |
| { |
| const char *p; |
| |
| fputc('"', out); |
| for (p = data; p && *p; p++) { |
| if ((unsigned char) *p == 0x22 || /* " */ |
| (unsigned char) *p == 0x5c || /* \ */ |
| !isprint((unsigned char) *p) || |
| iscntrl((unsigned char) *p)) { |
| |
| fprintf(out, "\\x%02x", (unsigned char) *p); |
| } else |
| fputc(*p, out); |
| } |
| fputc('"', out); |
| } |
| |
| void tt_fputs_nonblank(const char *data, FILE *out) |
| { |
| const char *p; |
| |
| for (p = data; p && *p; p++) { |
| if (isblank((unsigned char) *p) || |
| (unsigned char) *p == 0x5c || /* \ */ |
| !isprint((unsigned char) *p) || |
| iscntrl((unsigned char) *p)) { |
| |
| fprintf(out, "\\x%02x", (unsigned char) *p); |
| |
| } else |
| fputc(*p, out); |
| } |
| } |
| |
| /* |
| * Prints data, data maybe be printed in more formats (raw, NAME=xxx pairs) and |
| * control and non-printable chars maybe encoded in \x?? hex encoding. |
| */ |
| static void print_data(struct tt *tb, struct tt_column *cl, char *data) |
| { |
| size_t len = 0, i, width; |
| char *buf; |
| |
| if (!data) |
| data = ""; |
| |
| /* raw mode */ |
| if (tb->flags & TT_FL_RAW) { |
| tt_fputs_nonblank(data, stdout); |
| if (!is_last_column(tb, cl)) |
| fputc(' ', stdout); |
| return; |
| } |
| |
| /* NAME=value mode */ |
| if (tb->flags & TT_FL_EXPORT) { |
| fprintf(stdout, "%s=", cl->name); |
| tt_fputs_quoted(data, stdout); |
| if (!is_last_column(tb, cl)) |
| fputc(' ', stdout); |
| return; |
| } |
| |
| /* note that 'len' and 'width' are number of cells, not bytes */ |
| buf = mbs_safe_encode(data, &len); |
| data = buf; |
| if (!data) |
| data = ""; |
| |
| if (!len || len == (size_t) -1) { |
| len = 0; |
| data = NULL; |
| } |
| width = cl->width; |
| |
| if (is_last_column(tb, cl) && len < width) |
| width = len; |
| |
| /* truncate data */ |
| if (len > width && (cl->flags & TT_FL_TRUNC)) { |
| if (data) |
| len = mbs_truncate(data, &width); |
| if (!data || len == (size_t) -1) { |
| len = 0; |
| data = NULL; |
| } |
| } |
| if (data) { |
| if (!(tb->flags & TT_FL_RAW) && (cl->flags & TT_FL_RIGHT)) { |
| size_t xw = cl->width; |
| fprintf(stdout, "%*s", (int) xw, data); |
| if (len < xw) |
| len = xw; |
| } |
| else |
| fputs(data, stdout); |
| } |
| for (i = len; i < width; i++) |
| fputc(' ', stdout); /* padding */ |
| |
| if (!is_last_column(tb, cl)) { |
| if (len > width && !(cl->flags & TT_FL_TRUNC)) { |
| fputc('\n', stdout); |
| for (i = 0; i <= (size_t) cl->seqnum; i++) { |
| struct tt_column *x = tt_get_column(tb, i); |
| printf("%*s ", -((int)x->width), " "); |
| } |
| } else |
| fputc(' ', stdout); /* columns separator */ |
| } |
| |
| free(buf); |
| } |
| |
| static void print_line(struct tt_line *ln, char *buf, size_t bufsz) |
| { |
| struct list_head *p; |
| |
| /* set width according to the size of data |
| */ |
| list_for_each(p, &ln->table->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| |
| print_data(ln->table, cl, line_get_data(ln, cl, buf, bufsz)); |
| } |
| fputc('\n', stdout); |
| } |
| |
| static void print_header(struct tt *tb, char *buf, size_t bufsz) |
| { |
| struct list_head *p; |
| |
| if (!tb->first_run || |
| (tb->flags & TT_FL_NOHEADINGS) || |
| (tb->flags & TT_FL_EXPORT) || |
| list_empty(&tb->tb_lines)) |
| return; |
| |
| /* set width according to the size of data |
| */ |
| list_for_each(p, &tb->tb_columns) { |
| struct tt_column *cl = |
| list_entry(p, struct tt_column, cl_columns); |
| |
| strncpy(buf, cl->name, bufsz); |
| buf[bufsz - 1] = '\0'; |
| print_data(tb, cl, buf); |
| } |
| fputc('\n', stdout); |
| } |
| |
| static void print_table(struct tt *tb, char *buf, size_t bufsz) |
| { |
| struct list_head *p; |
| |
| print_header(tb, buf, bufsz); |
| |
| list_for_each(p, &tb->tb_lines) { |
| struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); |
| |
| print_line(ln, buf, bufsz); |
| } |
| } |
| |
| static void print_tree_line(struct tt_line *ln, char *buf, size_t bufsz) |
| { |
| struct list_head *p; |
| |
| print_line(ln, buf, bufsz); |
| |
| if (list_empty(&ln->ln_branch)) |
| return; |
| |
| /* print all children */ |
| list_for_each(p, &ln->ln_branch) { |
| struct tt_line *chld = |
| list_entry(p, struct tt_line, ln_children); |
| print_tree_line(chld, buf, bufsz); |
| } |
| } |
| |
| static void print_tree(struct tt *tb, char *buf, size_t bufsz) |
| { |
| struct list_head *p; |
| |
| print_header(tb, buf, bufsz); |
| |
| list_for_each(p, &tb->tb_lines) { |
| struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); |
| |
| if (ln->parent) |
| continue; |
| |
| print_tree_line(ln, buf, bufsz); |
| } |
| } |
| |
| /* |
| * @tb: table |
| * |
| * Prints the table to stdout |
| */ |
| int tt_print_table(struct tt *tb) |
| { |
| char *line; |
| size_t line_sz; |
| struct list_head *p; |
| |
| if (!tb) |
| return -1; |
| |
| if (tb->first_run) { |
| tb->is_term = isatty(STDOUT_FILENO); |
| |
| if (tb->is_term && !tb->termwidth) |
| tb->termwidth = get_terminal_width(); |
| if (tb->termwidth <= 0) |
| tb->termwidth = 80; |
| } |
| |
| line_sz = tb->termwidth; |
| |
| list_for_each(p, &tb->tb_lines) { |
| struct tt_line *ln = list_entry(p, struct tt_line, ln_lines); |
| if (ln->data_sz > line_sz) |
| line_sz = ln->data_sz; |
| } |
| |
| line_sz++; /* make a space for \0 */ |
| line = malloc(line_sz); |
| if (!line) |
| return -1; |
| |
| if (tb->first_run && |
| !((tb->flags & TT_FL_RAW) || (tb->flags & TT_FL_EXPORT))) |
| recount_widths(tb, line, line_sz); |
| |
| if (tb->flags & TT_FL_TREE) |
| print_tree(tb, line, line_sz); |
| else |
| print_table(tb, line, line_sz); |
| |
| free(line); |
| |
| tb->first_run = FALSE; |
| return 0; |
| } |
| |
| #ifdef TEST_PROGRAM |
| #include <errno.h> |
| |
| enum { MYCOL_NAME, MYCOL_FOO, MYCOL_BAR, MYCOL_PATH }; |
| |
| int main(int argc, char *argv[]) |
| { |
| struct tt *tb; |
| struct tt_line *ln, *pr, *root; |
| int flags = 0, notree = 0, i; |
| |
| if (argc == 2 && !strcmp(argv[1], "--help")) { |
| printf("%s [--ascii | --raw | --list]\n", |
| program_invocation_short_name); |
| return EXIT_SUCCESS; |
| } else if (argc == 2 && !strcmp(argv[1], "--ascii")) { |
| flags |= TT_FL_ASCII; |
| } else if (argc == 2 && !strcmp(argv[1], "--raw")) { |
| flags |= TT_FL_RAW; |
| notree = 1; |
| } else if (argc == 2 && !strcmp(argv[1], "--export")) { |
| flags |= TT_FL_EXPORT; |
| notree = 1; |
| } else if (argc == 2 && !strcmp(argv[1], "--list")) |
| notree = 1; |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| |
| tb = tt_new_table(flags); |
| if (!tb) |
| err(EXIT_FAILURE, "table initialization failed"); |
| |
| tt_define_column(tb, "NAME", 0.3, notree ? 0 : TT_FL_TREE); |
| tt_define_column(tb, "FOO", 0.3, TT_FL_TRUNC); |
| tt_define_column(tb, "BAR", 0.3, 0); |
| tt_define_column(tb, "PATH", 0.3, 0); |
| |
| for (i = 0; i < 2; i++) { |
| root = ln = tt_add_line(tb, NULL); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA"); |
| tt_line_set_data(ln, MYCOL_FOO, "a-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA"); |
| |
| pr = ln = tt_add_line(tb, ln); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA.A"); |
| tt_line_set_data(ln, MYCOL_FOO, "a.a-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A"); |
| |
| ln = tt_add_line(tb, pr); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA.A.AAA"); |
| tt_line_set_data(ln, MYCOL_FOO, "a.a.a-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.A"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/AAA"); |
| |
| ln = tt_add_line(tb, root); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA.B"); |
| tt_line_set_data(ln, MYCOL_FOO, "a.b-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A.B"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/B"); |
| |
| ln = tt_add_line(tb, pr); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA.A.BBB"); |
| tt_line_set_data(ln, MYCOL_FOO, "a.a.b-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.BBB"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/BBB"); |
| |
| ln = tt_add_line(tb, pr); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA.A.CCC"); |
| tt_line_set_data(ln, MYCOL_FOO, "a.a.c-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.CCC"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/CCC"); |
| |
| ln = tt_add_line(tb, root); |
| tt_line_set_data(ln, MYCOL_NAME, "AAA.C"); |
| tt_line_set_data(ln, MYCOL_FOO, "a.c-foo-foo"); |
| tt_line_set_data(ln, MYCOL_BAR, "barBar-A.C"); |
| tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/C"); |
| } |
| |
| tt_print_table(tb); |
| tt_free_table(tb); |
| |
| return EXIT_SUCCESS; |
| } |
| #endif |