diff options
Diffstat (limited to '')
-rw-r--r-- | libblkid/tt.c | 1005 |
1 files changed, 1005 insertions, 0 deletions
diff --git a/libblkid/tt.c b/libblkid/tt.c new file mode 100644 index 000000000..cbe4e3b4d --- /dev/null +++ b/libblkid/tt.c @@ -0,0 +1,1005 @@ +/* + * 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 |