From 3e542c765412904331fddb722b52fcdf31997d44 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Wed, 19 Mar 2014 16:33:09 +0100 Subject: [PATCH] libsmartcols: add table code Signed-off-by: Karel Zak --- libsmartcols/src/Makemodule.am | 3 + libsmartcols/src/libsmartcols.h.in | 72 +++- libsmartcols/src/table.c | 507 +++++++++++++++++++++++++ libsmartcols/src/table_print.c | 577 +++++++++++++++++++++++++++++ 4 files changed, 1157 insertions(+), 2 deletions(-) create mode 100644 libsmartcols/src/table.c create mode 100644 libsmartcols/src/table_print.c diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am index 4c4fa4aa46..bf218f8f33 100644 --- a/libsmartcols/src/Makemodule.am +++ b/libsmartcols/src/Makemodule.am @@ -13,6 +13,9 @@ libsmartcols_la_SOURCES= \ libsmartcols/src/symbols.c \ libsmartcols/src/cell.c \ libsmartcols/src/column.c \ + libsmartcols/src/line.c \ + libsmartcols/src/table.c \ + libsmartcols/src/table_print.c \ $(smartcolsinc_HEADERS) nodist_libsmartcols_la_SOURCES = libsmartcols/src/smartcolsP.h diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in index f0291756f4..81283fb0e9 100644 --- a/libsmartcols/src/libsmartcols.h.in +++ b/libsmartcols/src/libsmartcols.h.in @@ -22,6 +22,8 @@ extern "C" { struct libscols_iter; struct libscols_symbols; struct libscols_cell; +struct libscols_line; +struct libscols_table; /* iter.c */ @@ -31,6 +33,26 @@ enum { SCOLS_ITER_BACKWARD }; + +enum { + /* + * Global flags + */ + SCOLS_FL_RAW = (1 << 1), + SCOLS_FL_ASCII = (1 << 2), + SCOLS_FL_NOHEADINGS = (1 << 3), + SCOLS_FL_EXPORT = (1 << 4), + + /* + * Column flags + */ + SCOLS_FL_TRUNC = (1 << 5), /* truncate fields data if necessary */ + SCOLS_FL_TREE = (1 << 6), /* use tree "ascii art" */ + SCOLS_FL_RIGHT = (1 << 7), /* align to the right */ + SCOLS_FL_STRICTWIDTH = (1 << 8), /* don't reduce width if column is empty */ + SCOLS_FL_NOEXTREMES = (1 << 9), /* ignore extreme fields when count column width*/ +}; + extern struct libscols_iter *scols_new_iter(int direction); extern void scols_free_iter(struct libscols_iter *itr); extern void scols_reset_iter(struct libscols_iter *itr, int direction); @@ -38,8 +60,9 @@ extern int scols_iter_get_direction(struct libscols_iter *itr); /* symbols.c */ extern struct libscols_symbols *scols_new_symbols(void); -struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sb); -extern void scols_free_symbols(struct libscols_symbols *sb); +extern void scols_ref_symbols(struct libscols_symbols *sy); +extern void scols_unref_symbols(struct libscols_symbols *sy); +extern struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sb); extern int scols_symbols_set_branch(struct libscols_symbols *sb, const char *str); extern int scols_symbols_set_vertical(struct libscols_symbols *sb, const char *str); extern int scols_symbols_set_right(struct libscols_symbols *sb, const char *str); @@ -49,6 +72,7 @@ extern int scols_reset_cell(struct libscols_cell *ce); extern int scols_cell_copy_content(struct libscols_cell *dest, const struct libscols_cell *src); extern int scols_cell_set_data(struct libscols_cell *ce, const char *str); +extern int scols_cell_refer_data(struct libscols_cell *ce, char *str); extern const char *scols_cell_get_data(const struct libscols_cell *ce); extern int scols_cell_set_color(struct libscols_cell *ce, const char *color); extern const char *scols_cell_get_color(const struct libscols_cell *ce); @@ -66,6 +90,50 @@ extern struct libscols_cell *scols_column_get_header(struct libscols_column *cl) extern int scols_column_set_color(struct libscols_column *cl, const char *color); extern const char *scols_column_get_color(struct libscols_column *cl); +/* line.c */ +extern struct libscols_line *scols_new_line(void); +extern void scols_ref_line(struct libscols_line *ln); +extern void scols_unref_line(struct libscols_line *ln); +extern int scols_line_alloc_cells(struct libscols_line *ln, size_t n); +extern void scols_line_free_cells(struct libscols_line *ln); +extern int scols_line_set_userdata(struct libscols_line *ln, void *data); +extern void *scols_line_get_userdata(struct libscols_line *ln); +extern int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child); +extern int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child); +extern struct libscols_line *scols_line_get_parent(struct libscols_line *ln); +extern int scols_line_set_color(struct libscols_line *ln, const char *color); +extern const char *scols_line_get_color(struct libscols_line *ln); +extern size_t scols_line_get_ncells(struct libscols_line *ln); +extern struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, size_t n); +extern int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data); +extern int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data); +extern struct libscols_line *scols_copy_line(struct libscols_line *ln); + +/* table */ +extern struct libscols_table *scols_new_table(int flags, struct libscols_symbols *syms); +extern void scols_ref_table(struct libscols_table *tb); +extern void scols_unref_table(struct libscols_table *tb); +extern int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_table_remove_column(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_table_remove_columns(struct libscols_table *tb); +extern struct libscols_column *scols_table_new_column(struct libscols_table *tb, const char *name, double whint, int flags); +extern int scols_table_next_column(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column **cl); +extern int scols_table_get_ncols(struct libscols_table *tb); +extern int scols_table_get_nlines(struct libscols_table *tb); +extern int scols_table_get_flags(struct libscols_table *tb); +extern struct libscols_column *scols_table_get_column(struct libscols_table *tb, size_t n); +extern int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln); +extern int scols_table_remove_line(struct libscols_table *tb, struct libscols_line *ln); +extern void scols_table_remove_lines(struct libscols_table *tb); +extern int scols_table_next_line(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_line **ln); +extern struct libscols_line *scols_table_new_line(struct libscols_table *tb, struct libscols_line *parent); +extern struct libscols_line *scols_table_get_line(struct libscols_table *tb, size_t n); +extern struct libscols_table *scols_copy_table(struct libscols_table *tb); +extern int scols_table_set_symbols(struct libscols_table *tb, struct libscols_symbols *sy); + +/* table_print.c */ +extern int scols_print_table(struct libscols_table *tb); + #ifdef __cplusplus } #endif diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c new file mode 100644 index 0000000000..e703f8f26c --- /dev/null +++ b/libsmartcols/src/table.c @@ -0,0 +1,507 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include +#include +#include +#include +#include +#include + +#include "nls.h" +#include "widechar.h" +#include "smartcolsP.h" + +#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 */ +#endif /* !HAVE_WIDECHAR */ + +#define is_last_column(_tb, _cl) \ + list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) + + +/* + * @flags: SCOLS_FL_* flags (usually SCOLS_FL_{ASCII,RAW}) + * @syms: tree symbols or NULL for default + * + * Note that this function add a new reference to @syms + * + * Returns: newly allocated table + */ +struct libscols_table *scols_new_table(int flags, struct libscols_symbols *syms) +{ + struct libscols_table *tb; + + tb = calloc(1, sizeof(struct libscols_table)); + if (!tb) + return NULL; + + tb->flags = flags; + tb->refcount = 1; + tb->first_run = TRUE; + + INIT_LIST_HEAD(&tb->tb_lines); + INIT_LIST_HEAD(&tb->tb_columns); + + if (scols_table_set_symbols(tb, syms) == 0) + return tb; + + scols_unref_table(tb); + return NULL; +} + +void scols_ref_table(struct libscols_table *tb) +{ + if (tb) + tb->refcount++; +} + +void scols_unref_table(struct libscols_table *tb) +{ + if (tb && (--tb->refcount <= 0)) { + scols_table_remove_lines(tb); + scols_table_remove_columns(tb); + scols_unref_symbols(tb->symbols); + free(tb); + } +} + +int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl) +{ + assert(tb); + assert(cl); + + if (!tb || !cl || !list_empty(&tb->tb_lines)) + return -EINVAL; + + list_add_tail(&cl->cl_columns, &tb->tb_columns); + cl->seqnum = tb->ncols++; + scols_ref_column(cl); + + /* TODO: + * + * Currently it's possible to add/remove columns only if the table is + * empty (see list_empty(tb->tb_lines) above). It would be nice to + * enlarge/reduce lines cells[] always when we add/remove a new column. + */ + return 0; +} + +int scols_table_remove_column(struct libscols_table *tb, + struct libscols_column *cl) +{ + assert(tb); + assert(cl); + + if (!tb || !cl || !list_empty(&tb->tb_lines)) + return -EINVAL; + + list_del_init(&cl->cl_columns); + tb->ncols--; + scols_unref_column(cl); + return 0; +} + +int scols_table_remove_columns(struct libscols_table *tb) +{ + assert(tb); + + if (!tb || !list_empty(&tb->tb_lines)) + return -EINVAL; + + while (!list_empty(&tb->tb_columns)) { + struct libscols_column *cl = list_entry(tb->tb_columns.next, + struct libscols_column, cl_columns); + scols_table_remove_column(tb, cl); + } + return 0; +} + + +/* + * @tb: table + * @name: column header + * @whint: column width hint (absolute width: N > 1; relative width: N < 1) + * @flags: usually SCOLS_FL_{TREE,TRUNCATE} + * + * This is shortcut for + * + * cl = scols_new_column(); + * scols_column_set_....(cl, ...); + * scols_table_add_column(tb, cl); + * + * 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 = SCOLS_FL_STRICTWIDTH + * : absolute width, empty colum won't be truncated + * + * The column is necessary to address (for example for scols_line_set_cell_data()) by + * sequential number. The first defined column has the colnum = 0. For example: + * + * scols_table_new_column(tab, "FOO", 0.5, 0); // colnum = 0 + * scols_table_new_column(tab, "BAR", 0.5, 0); // colnum = 1 + * . + * . + * scols_line_get_cell(line, 0); // FOO column + * scols_line_get_cell(line, 1); // BAR column + * + * Returns: newly allocated column + */ +struct libscols_column *scols_table_new_column(struct libscols_table *tb, + const char *name, + double whint, + int flags) +{ + struct libscols_column *cl; + struct libscols_cell *hr; + + assert (tb); + if (!tb) + return NULL; + cl = scols_new_column(); + if (!cl) + return NULL; + + /* set column name */ + hr = scols_column_get_header(cl); + if (!hr) + goto err; + if (scols_cell_set_data(hr, name)) + goto err; + + scols_column_set_whint(cl, whint); + scols_column_set_flags(cl, flags); + + if (flags & SCOLS_FL_TREE) + tb->flags |= SCOLS_FL_TREE; + + if (scols_table_add_column(tb, cl)) /* this increments column ref-counter */ + goto err; + + scols_unref_column(cl); + return cl; +err: + scols_unref_column(cl); + return NULL; +} + +int scols_table_next_column(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_column **cl) +{ + int rc = 1; + + if (!tb || !itr || !cl) + return -EINVAL; + *cl = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_columns); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *cl, struct libscols_column, cl_columns); + rc = 0; + } + + return rc; +} + + +/* + * @tb: table + * + * Returns: ncols integer + */ +int scols_table_get_ncols(struct libscols_table *tb) +{ + assert(tb); + return tb ? tb->ncols : -EINVAL; +} + +/* + * @tb: table + * + * Returns: nlines integer + */ +int scols_table_get_nlines(struct libscols_table *tb) +{ + assert(tb); + return tb ? tb->nlines : -EINVAL; +} + +/* + * @tb: table + * + * Returns: flags integer + */ +int scols_table_get_flags(struct libscols_table *tb) +{ + assert(tb); + return tb ? tb->flags: -EINVAL; +} + +/* + * @tb: table + * @: number of column (0..N) + * + * Returns: pointer to column or NULL + */ +struct libscols_column *scols_table_get_column(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + assert(tb); + if (!tb) + return NULL; + if (n >= tb->ncols) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (cl->seqnum == n) + return cl; + } + return NULL; +} + +/* + * Note that this functiion calls scols_line_alloc_cells() if number + * of the cells in the line is too small for @tb. + */ +int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) +{ + + assert(tb); + assert(ln); + + if (!tb || !ln) + return -EINVAL; + + if (tb->ncols > ln->ncells) { + int rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + return rc; + } + + list_add_tail(&ln->ln_lines, &tb->tb_lines); + ln->seqnum = tb->nlines++; + scols_ref_line(ln); + return 0; +} + +/* Note that this function does not destroy parent->child relation between lines. + * You have to call scols_line_remove_child() + */ +int scols_table_remove_line(struct libscols_table *tb, + struct libscols_line *ln) +{ + assert(tb); + assert(ln); + + if (!tb || !ln) + return -EINVAL; + + list_del_init(&ln->ln_lines); + tb->nlines--; + scols_unref_line(ln); + return 0; +} + +/* This make the table empty and also destroy all parent<->child relations */ +void scols_table_remove_lines(struct libscols_table *tb) +{ + assert(tb); + if (!tb) + return; + + while (!list_empty(&tb->tb_lines)) { + struct libscols_line *ln = list_entry(tb->tb_lines.next, + struct libscols_line, ln_lines); + if (ln->parent) + scols_line_remove_child(ln->parent, ln); + scols_table_remove_line(tb, ln); + } +} + +int scols_table_next_line(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_line **ln) +{ + int rc = 1; + + if (!tb || !itr || !ln) + return -EINVAL; + *ln = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_lines); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *ln, struct libscols_line, ln_lines); + rc = 0; + } + + return rc; +} + +/* + * @tb: table + * @parent: parental line or NULL + * + * This is shortcut for + * + * ln = scols_new_linen(); + * scols_line_set_....(cl, ...); + * scols_table_add_line(tb, ln); + + * + * Returns: newly allocate line + */ +struct libscols_line *scols_table_new_line(struct libscols_table *tb, + struct libscols_line *parent) +{ + struct libscols_line *ln; + + assert(tb); + assert(tb->ncols); + + if (!tb || !tb->ncols) + return NULL; + ln = scols_new_line(); + if (!ln) + return NULL; + + if (scols_table_add_line(tb, ln)) + goto err; + if (parent) + scols_line_add_child(parent, ln); + + scols_unref_line(ln); /* ref-counter incremented by scols_table_add_line() */ + return ln; +err: + scols_unref_line(ln); + return NULL; +} + + +struct libscols_line *scols_table_get_line(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_line *ln; + + assert(tb); + if (!tb) + return NULL; + if (n >= tb->nlines) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->seqnum == n) + return ln; + } + return NULL; +} + +/* + * Creates a new independent table copy, except struct libscols_symbols that + * are shared between the tables. + */ +struct libscols_table *scols_copy_table(struct libscols_table *tb) +{ + struct libscols_table *ret; + struct libscols_line *ln; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(tb); + if (!tb) + return NULL; + ret = scols_new_table(tb->flags, tb->symbols); + if (!ret) + return NULL; + + /* columns */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + cl = scols_copy_column(cl); + if (!cl) + goto err; + if (scols_table_add_column(ret, cl)) + goto err; + scols_unref_column(cl); + } + + /* lines */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + struct libscols_line *newln = scols_copy_line(ln); + if (!newln) + goto err; + if (scols_table_add_line(ret, newln)) + goto err; + if (ln->parent) { + struct libscols_line *p = + scols_table_get_line(ret, ln->parent->seqnum); + if (p) + scols_line_add_child(p, newln); + } + scols_unref_line(newln); + } + + return ret; +err: + scols_unref_table(ret); + return NULL; +} + +int scols_table_set_symbols(struct libscols_table *tb, + struct libscols_symbols *sy) +{ + assert(tb); + + if (!tb) + return -EINVAL; + + if (tb->symbols) /* unref old */ + scols_unref_symbols(tb->symbols); + if (sy) { /* ref user defined */ + tb->symbols = sy; + scols_ref_symbols(sy); + } else { /* default symbols */ + tb->symbols = scols_new_symbols(); + if (!tb->symbols) + return -ENOMEM; +#if defined(HAVE_WIDECHAR) + if (!(tb->flags & SCOLS_FL_ASCII) && + !strcmp(nl_langinfo(CODESET), "UTF-8")) { + scols_symbols_set_branch(tb->symbols, UTF_VR UTF_H); + scols_symbols_set_vertical(tb->symbols, UTF_V " "); + scols_symbols_set_right(tb->symbols, UTF_UR UTF_H); + } else +#endif + { + scols_symbols_set_branch(tb->symbols, "|-"); + scols_symbols_set_vertical(tb->symbols, "| "); + scols_symbols_set_right(tb->symbols, "`-"); + } + } + + return 0; +} + diff --git a/libsmartcols/src/table_print.c b/libsmartcols/src/table_print.c new file mode 100644 index 0000000000..694b9c9cae --- /dev/null +++ b/libsmartcols/src/table_print.c @@ -0,0 +1,577 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include +#include +#include +#include +#include +#include + +#include "nls.h" +#include "mbsalign.h" +#include "widechar.h" +#include "ttyutils.h" +#include "carefulputc.h" +#include "smartcolsP.h" + +#define is_last_column(_tb, _cl) \ + list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) + +static void print_data(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, /* optional */ + char *data) +{ + size_t len = 0, i, width; + char *buf; + const char *color = NULL; + + assert(tb); + assert(cl); + + if (!data) + data = ""; + + /* raw mode */ + if (tb->flags & SCOLS_FL_RAW) { + fputs_nonblank(data, stdout); + if (!is_last_column(tb, cl)) + fputc(' ', stdout); + return; + } + + /* NAME=value mode */ + if (tb->flags & SCOLS_FL_EXPORT) { + fprintf(stdout, "%s=", scols_cell_get_data(&cl->header)); + fputs_quoted(data, stdout); + if (!is_last_column(tb, cl)) + fputc(' ', stdout); + return; + } + + if (tb->is_term) { + if (ce && !color) + color = ce->color; + if (ln && !color) + color = ln->color; + if (!color) + color = cl->color; + } + + /* 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 & SCOLS_FL_TRUNC)) { + if (data) + len = mbs_truncate(data, &width); + if (!data || len == (size_t) -1) { + len = 0; + data = NULL; + } + } + if (data) { + if (!(tb->flags & SCOLS_FL_RAW) && (cl->flags & SCOLS_FL_RIGHT)) { + size_t xw = cl->width; + if (color) + fputs(color, stdout); + fprintf(stdout, "%*s", (int) xw, data); + if (color) + fputs(UL_COLOR_RESET, stdout); + if (len < xw) + len = xw; + } + else { + if (color) + fputs(color, stdout); + fputs(data, stdout); + if (color) + fputs(UL_COLOR_RESET, stdout); + } + } + for (i = len; i < width; i++) + fputc(' ', stdout); /* padding */ + + if (!is_last_column(tb, cl)) { + if (len > width && !(cl->flags & SCOLS_FL_TRUNC)) { + fputc('\n', stdout); + for (i = 0; i <= (size_t) cl->seqnum; i++) { + struct libscols_column *x = scols_table_get_column(tb, i); + printf("%*s ", -((int)x->width), " "); + } + } else + fputc(' ', stdout); /* columns separator */ + } + + free(buf); +} + +static char *line_get_ascii_art(struct libscols_table *tb, + struct libscols_line *ln, + char *buf, size_t *bufsz) +{ + const char *art; + size_t len; + + assert(ln); + + if (!ln->parent) + return buf; + + buf = line_get_ascii_art(tb, ln->parent, buf, bufsz); + if (!buf) + return NULL; + + if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch)) + art = " "; + else + art = tb->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 libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + char *buf, size_t bufsz) +{ + const char *data; + struct libscols_symbols *sym; + struct libscols_cell *ce; + char *p = buf; + + assert(tb); + assert(ln); + assert(cl); + assert(cl->seqnum <= tb->ncols); + + memset(buf, 0, bufsz); + + ce = scols_line_get_cell(ln, cl->seqnum); + data = ce ? scols_cell_get_data(ce) : NULL; + if (!data) + return NULL; + + if (!(cl->flags & SCOLS_FL_TREE)) { + strncpy(buf, data, bufsz); + buf[bufsz - 1] = '\0'; + return buf; + } + + /* + * Tree stuff + */ + if (ln->parent) { + p = line_get_ascii_art(tb, ln->parent, buf, &bufsz); + if (!p) + return NULL; + } + + sym = tb->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; +} + +/* + * 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_line(struct libscols_table *tb, + struct libscols_line *ln, char *buf, size_t bufsz) +{ + struct libscols_column *cl; + struct libscols_iter itr; + + assert(ln); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) + print_data(tb, cl, ln, + scols_line_get_cell(ln, cl->seqnum), + line_get_data(tb, ln, cl, buf, bufsz)); + fputc('\n', stdout); +} + +static void print_header(struct libscols_table *tb, char *buf, size_t bufsz) +{ + struct libscols_column *cl; + struct libscols_iter itr; + + assert(tb); + + if (!tb->first_run || + (tb->flags & SCOLS_FL_NOHEADINGS) || + (tb->flags & SCOLS_FL_EXPORT) || + list_empty(&tb->tb_lines)) + return; + + /* set width according to the size of data + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + strncpy(buf, scols_cell_get_data(&cl->header), bufsz); + buf[bufsz - 1] = '\0'; + print_data(tb, cl, NULL, &cl->header, buf); + } + fputc('\n', stdout); +} + +static void print_table(struct libscols_table *tb, char *buf, size_t bufsz) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + assert(tb); + + print_header(tb, buf, bufsz); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + print_line(tb, ln, buf, bufsz); +} + +static void print_tree_line(struct libscols_table *tb, + struct libscols_line *ln, + char *buf, size_t bufsz) +{ + struct list_head *p; + + print_line(tb, ln, buf, bufsz); + + if (list_empty(&ln->ln_branch)) + return; + + /* print all children */ + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + print_tree_line(tb, chld, buf, bufsz); + } +} + +static void print_tree(struct libscols_table *tb, char *buf, size_t bufsz) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + assert(tb); + + print_header(tb, buf, bufsz); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent) + continue; + print_tree_line(tb, ln, buf, bufsz); + } +} + +/* + * This function counts column width. + * + * For the SCOLS_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 libscols_table *tb, + struct libscols_column *cl, + char *buf, + size_t bufsz) +{ + struct libscols_line *ln; + struct libscols_iter itr; + int count = 0; + size_t sum = 0; + + assert(tb); + assert(cl); + + cl->width = 0; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + char *data = line_get_data(tb, 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 & SCOLS_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 (scols_cell_get_data(&cl->header)) + cl->width_min = mbs_safe_width(scols_cell_get_data(&cl->header)); + + /* enlarge to minimal width */ + if (cl->width < cl->width_min && !(cl->flags & SCOLS_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 scols_* voodo... + */ +static void recount_widths(struct libscols_table *tb, char *buf, size_t bufsz) +{ + struct libscols_column *cl; + struct libscols_iter itr; + size_t width = 0; /* output width */ + int trunc_only; + int extremes = 0; + + /* set basic columns width + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + 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) { + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + 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 */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + 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 libscols_column *cl = list_entry( + tb->tb_columns.prev, struct libscols_column, cl_columns); + + if (!(cl->flags & SCOLS_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; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (width <= tb->termwidth) + break; + if (cl->width_hint > 1 && !(cl->flags & SCOLS_FL_TRUNC)) + continue; /* never truncate columns with absolute sizes */ + if (cl->flags & SCOLS_FL_TREE) + continue; /* never truncate the tree */ + if (trunc_only && !(cl->flags & SCOLS_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); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + 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; +} +static size_t strlen_line(struct libscols_line *ln) +{ + size_t i, sz = 0; + + assert(ln); + + for (i = 0; i < ln->ncells; i++) { + struct libscols_cell *ce = scols_line_get_cell(ln, i); + const char *data = ce ? scols_cell_get_data(ce) : NULL; + + sz += data ? strlen(data) : 0; + } + + return sz; +} + +/* + * @tb: table + * + * Prints the table to stdout + */ +int scols_print_table(struct libscols_table *tb) +{ + char *line; + size_t line_sz; + struct libscols_line *ln; + struct libscols_iter itr; + + assert(tb); + 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; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t sz = strlen_line(ln); + if (sz > line_sz) + line_sz = sz; + } + + line_sz++; /* make a space for \0 */ + line = malloc(line_sz); + if (!line) + return -ENOMEM; + + if (tb->first_run && + !((tb->flags & SCOLS_FL_RAW) || (tb->flags & SCOLS_FL_EXPORT))) + recount_widths(tb, line, line_sz); + + if (tb->flags & SCOLS_FL_TREE) + print_tree(tb, line, line_sz); + else + print_table(tb, line, line_sz); + + free(line); + tb->first_run = FALSE; + return 0; +} -- 2.47.2