]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libsmartcols: add table code
authorKarel Zak <kzak@redhat.com>
Wed, 19 Mar 2014 15:33:09 +0000 (16:33 +0100)
committerKarel Zak <kzak@redhat.com>
Thu, 3 Apr 2014 10:29:16 +0000 (12:29 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
libsmartcols/src/Makemodule.am
libsmartcols/src/libsmartcols.h.in
libsmartcols/src/table.c [new file with mode: 0644]
libsmartcols/src/table_print.c [new file with mode: 0644]

index 4c4fa4aa46798f3f7308226326eb8454111c839f..bf218f8f335d11d89a8cfa48349f8d683720ba8a 100644 (file)
@@ -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
index f0291756f489efc669eadbcfed13ae1087da5969..81283fb0e958b77fbb82ffb868892eaec4b0f8fb 100644 (file)
@@ -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 (file)
index 0000000..e703f8f
--- /dev/null
@@ -0,0 +1,507 @@
+/*
+ * table.c - functions handling the data at the table level
+ *
+ * Copyright (C) 2010-2014 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 "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 (file)
index 0000000..694b9c9
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * table.c - functions handling the data at the table level
+ *
+ * Copyright (C) 2010-2014 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 "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;
+}