From cfe5e3905201349e9cf3f95d52ff4bd100bde37d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 21:10:49 +0200 Subject: Adding upstream version 2.39.3. Signed-off-by: Daniel Baumann --- libsmartcols/src/Makemodule.am | 62 ++ libsmartcols/src/calculate.c | 608 ++++++++++++ libsmartcols/src/cell.c | 260 ++++++ libsmartcols/src/column.c | 737 +++++++++++++++ libsmartcols/src/grouping.c | 575 ++++++++++++ libsmartcols/src/init.c | 62 ++ libsmartcols/src/iter.c | 74 ++ libsmartcols/src/libsmartcols.h.in | 349 +++++++ libsmartcols/src/libsmartcols.sym | 217 +++++ libsmartcols/src/line.c | 563 ++++++++++++ libsmartcols/src/print-api.c | 220 +++++ libsmartcols/src/print.c | 1243 +++++++++++++++++++++++++ libsmartcols/src/smartcolsP.h | 455 +++++++++ libsmartcols/src/symbols.c | 293 ++++++ libsmartcols/src/table.c | 1783 ++++++++++++++++++++++++++++++++++++ libsmartcols/src/version.c | 62 ++ libsmartcols/src/walk.c | 152 +++ 17 files changed, 7715 insertions(+) create mode 100644 libsmartcols/src/Makemodule.am create mode 100644 libsmartcols/src/calculate.c create mode 100644 libsmartcols/src/cell.c create mode 100644 libsmartcols/src/column.c create mode 100644 libsmartcols/src/grouping.c create mode 100644 libsmartcols/src/init.c create mode 100644 libsmartcols/src/iter.c create mode 100644 libsmartcols/src/libsmartcols.h.in create mode 100644 libsmartcols/src/libsmartcols.sym create mode 100644 libsmartcols/src/line.c create mode 100644 libsmartcols/src/print-api.c create mode 100644 libsmartcols/src/print.c create mode 100644 libsmartcols/src/smartcolsP.h create mode 100644 libsmartcols/src/symbols.c create mode 100644 libsmartcols/src/table.c create mode 100644 libsmartcols/src/version.c create mode 100644 libsmartcols/src/walk.c (limited to 'libsmartcols/src') diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am new file mode 100644 index 0000000..2bb19fd --- /dev/null +++ b/libsmartcols/src/Makemodule.am @@ -0,0 +1,62 @@ + + +# smartcols.h is generated, so it's stored in builddir! +smartcolsincdir = $(includedir)/libsmartcols +nodist_smartcolsinc_HEADERS = libsmartcols/src/libsmartcols.h + +usrlib_exec_LTLIBRARIES += libsmartcols.la +libsmartcols_la_SOURCES= \ + include/list.h \ + \ + libsmartcols/src/smartcolsP.h \ + libsmartcols/src/iter.c \ + libsmartcols/src/symbols.c \ + libsmartcols/src/cell.c \ + libsmartcols/src/column.c \ + libsmartcols/src/line.c \ + libsmartcols/src/table.c \ + libsmartcols/src/print.c \ + libsmartcols/src/print-api.c \ + libsmartcols/src/version.c \ + libsmartcols/src/calculate.c \ + libsmartcols/src/grouping.c \ + libsmartcols/src/walk.c \ + libsmartcols/src/init.c + +libsmartcols_la_LIBADD = $(LDADD) libcommon.la + +libsmartcols_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + -I$(ul_libsmartcols_incdir) \ + -I$(top_srcdir)/libsmartcols/src + +EXTRA_libsmartcols_la_DEPENDENCIES = \ + libsmartcols/src/libsmartcols.sym + +libsmartcols_la_LDFLAGS = $(SOLIB_LDFLAGS) +if HAVE_VSCRIPT +libsmartcols_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libsmartcols/src/libsmartcols.sym +endif +libsmartcols_la_LDFLAGS += -version-info $(LIBSMARTCOLS_VERSION_INFO) + +EXTRA_DIST += \ + libsmartcols/src/libsmartcols.sym + +# move lib from $(usrlib_execdir) to $(libdir) if needed +install-exec-hook-libsmartcols: + if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libsmartcols.so"; then \ + $(MKDIR_P) $(DESTDIR)$(libdir); \ + mv $(DESTDIR)$(usrlib_execdir)/libsmartcols.so.* $(DESTDIR)$(libdir); \ + so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libsmartcols.so); \ + so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \ + (cd $(DESTDIR)$(usrlib_execdir) && \ + rm -f libsmartcols.so && \ + $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libsmartcols.so); \ + fi + +uninstall-hook-libsmartcols: + rm -f $(DESTDIR)$(libdir)/libsmartcols.so* + +INSTALL_EXEC_HOOKS += install-exec-hook-libsmartcols +UNINSTALL_HOOKS += uninstall-hook-libsmartcols diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c new file mode 100644 index 0000000..ad0b15d --- /dev/null +++ b/libsmartcols/src/calculate.c @@ -0,0 +1,608 @@ +#include "smartcolsP.h" +#include "mbsalign.h" + +static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_wstat *st; + + if (scols_column_is_hidden(cl)) { + DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); + return; + } + + st = &cl->wstat; + + DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " + "hint=%d, max=%zu, min=%zu, " + "0x04%x [%s%s%s]", + + cl->header.data, cl->seqnum, cl->width, + cl->width_hint >= 1.0 ? (int) cl->width_hint : + (int) (cl->width_hint * tb->termwidth), + st->width_max, + st->width_min, + cl->flags, + cl->flags & SCOLS_FL_TRUNC ? "trunc" : "", + scols_column_is_right(cl) ? " right" : "", + scols_column_is_noextremes(cl) ? " noextrem" : "")); +} + +static void dbg_columns(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) + dbg_column(tb, cl); +} + +static int count_cell_width(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct ul_buffer *buf) +{ + size_t len; + char *data; + int rc; + struct libscols_cell *ce; + struct libscols_wstat *st; + + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc) + return rc; + + data = ul_buffer_get_data(buf, NULL, NULL); + if (!data) + len = 0; + else if (scols_column_is_customwrap(cl)) + len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); + else if (scols_table_is_noencoding(tb)) + len = mbs_width(data); + else + len = mbs_safe_width(data); + + if (len == (size_t) -1) /* ignore broken multibyte strings */ + len = 0; + + ce = scols_line_get_cell(ln, cl->seqnum); + ce->width = len; + + st = &cl->wstat; + st->width_max = max(len, st->width_max); + + if (scols_column_is_tree(cl)) { + size_t treewidth = ul_buffer_get_safe_pointer_width(buf, SCOLS_BUFPTR_TREEEND); + cl->width_treeart = max(cl->width_treeart, treewidth); + } + + return 0; +} + +static int walk_count_cell_width(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + void *data) +{ + return count_cell_width(tb, ln, cl, (struct ul_buffer *) data); +} + +static double sqrtroot(double num) +{ + double tmp = 0, sq = num / 2; + + while (sq != tmp){ + tmp = sq; + sq = (num / tmp + tmp) / 2; + } + return sq; +} + +static void count_column_deviation(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_wstat *st; + struct libscols_iter itr; + struct libscols_line *ln; + struct libscols_cell *ce; + size_t sum = 0, n = 0, extra = 0; + + st = &cl->wstat; + + if (scols_column_is_tree(cl) && has_groups(tb)) + extra = tb->grpset_size + 1; + + /* count average */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + ce = scols_line_get_cell(ln, cl->seqnum); + + n++; + sum += ce->width + extra; + } + + if (n) + st->width_avg = sum / n; + + /* count deviation */ + if (n > 1) { + double variance; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + double diff; + ce = scols_line_get_cell(ln, cl->seqnum); + + diff = (double) ce->width - st->width_avg; + st->width_sqr_sum += diff * diff; /* aka pow(x, 2) */ + } + + variance = st->width_sqr_sum / (n - 1); + st->width_deviation = sqrtroot(variance); + } + + DBG(COL, ul_debugobj(cl, "%15s avg=%g, deviation=%g", + cl->header.data, + st->width_avg, + st->width_deviation)); +} + +/* + * This function counts column width. + */ +static int count_column_width(struct libscols_table *tb, + struct libscols_column *cl, + struct ul_buffer *buf) +{ + int rc = 0, no_header = 0; + const char *data; + struct libscols_wstat *st; + struct libscols_iter itr; + struct libscols_line *ln; + + assert(tb); + assert(cl); + + st = &cl->wstat; + + cl->width = 0; + memset(st, 0, sizeof(struct libscols_wstat)); + + /* set minimal width according to width_hint */ + if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { + st->width_min = (size_t) (cl->width_hint * tb->termwidth); + if (st->width_min && !is_last_column(cl)) + st->width_min--; + } + + /* set minimal width according to header width */ + data = scols_cell_get_data(&cl->header); + if (data) { + size_t len = scols_table_is_noencoding(tb) ? + mbs_width(data) : mbs_safe_width(data); + + st->width_min = max(st->width_min, len); + } else + no_header = 1; + + if (!st->width_min) + st->width_min = 1; + + /* count width according to cells data */ + if (scols_table_is_tree(tb)) { + /* Count width for tree */ + rc = scols_walk_tree(tb, cl, walk_count_cell_width, (void *) buf); + if (rc) + goto done; + } else { + /* Count width for list */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + rc = count_cell_width(tb, ln, cl, buf); + if (rc) + goto done; + } + } + + if (scols_column_is_tree(cl) && has_groups(tb)) { + /* We don't fill buffer with groups tree ascii art during width + * calculation. The print function only enlarge grpset[] and we + * calculate final width from grpset_size. + */ + size_t gprwidth = tb->grpset_size + 1; + st->width_treeart += gprwidth; + st->width_max += gprwidth; + } + + /* this is default, may be later reduced */ + cl->width = st->width_max; + + /* enlarge to minimal width */ + if (cl->width < st->width_min && !scols_column_is_strict_width(cl)) + cl->width = st->width_min; + + /* use absolute size for large columns */ + else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint + && st->width_min < (size_t) cl->width_hint) + + cl->width = (size_t) cl->width_hint; + + + /* Column without header and data, set minimal size to zero (default is 1) */ + if (st->width_max == 0 && no_header && st->width_min == 1 && cl->width <= 1) + cl->width = st->width_min = 0; + +done: + ON_DBG(COL, dbg_column(tb, cl)); + return rc; +} + +static int cmp_deviation(struct list_head *a, struct list_head *b, + void *data __attribute__((__unused__))) +{ + struct libscols_column *ca = list_entry(a, struct libscols_column, cl_columns); + struct libscols_column *cb = list_entry(b, struct libscols_column, cl_columns); + + double xa = ca->wstat.width_avg + (3*ca->wstat.width_deviation); + double xb = cb->wstat.width_avg + (3*cb->wstat.width_deviation); + + return cmp_numbers(xa, xb); +} + +static int cmp_seqnum(struct list_head *a, struct list_head *b, + void *data __attribute__((__unused__))) +{ + struct libscols_column *ca = list_entry(a, struct libscols_column, cl_columns); + struct libscols_column *cb = list_entry(b, struct libscols_column, cl_columns); + + return cmp_numbers(ca->seqnum, cb->seqnum); +} + +static inline void sort_columns(struct libscols_table *tb, + int (*cmp)(struct list_head *, struct list_head *, void *)) +{ + list_sort(&tb->tb_columns, cmp, NULL); +} + + +/* 68%–95%–99% rule (aka empirical rule) defines relation ship between + * mean (avg) and and standard deviation. + * + * avg + (n * deviation) + * + * n=1 : covers 68% of data + * n=2 : covers 95% of data + * n=3 : covers 99.7% of data + * + */ +static void reduce_to_68(struct libscols_column *cl, size_t wanted) +{ + struct libscols_wstat *st = &cl->wstat; + size_t new; + + if (st->width_deviation < 1.0) + return; + + new = st->width_avg + st->width_deviation; + if (new < st->width_min) + new = st->width_min; + + if (cl->width - new > wanted) + cl->width -= wanted; + else + cl->width = new; +} + +static int reduce_column(struct libscols_table *tb, + struct libscols_column *cl, + size_t *width, + int stage, + int nth) +{ + struct libscols_wstat *st = &cl->wstat; + size_t wanted, org_width, reduce = 1; + int is_trunc = 0; + + if (tb->termwidth >= *width) + return 1; + /* ignore hidden columns */ + if (scols_column_is_hidden(cl)) + return 0; + /* never truncate if already minimal width */ + if (cl->width == cl->wstat.width_min) + return 0; + /* nothing to truncate */ + if (cl->width == 0) + return 0; + /* never truncate the tree */ + if (scols_column_is_tree(cl) && *width <= cl->width_treeart) + return 0; + + org_width = cl->width; + wanted = *width - tb->termwidth; + + is_trunc = scols_column_is_trunc(cl) + || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); + + switch (stage) { + case 0: + /* reduce 1st column if with trunc or extreme flag (the + * columns are sorted by deviation, so 1st is the worst) */ + if (!is_trunc && !scols_column_is_noextremes(cl)) + break; + if (nth != 0) + break; + reduce_to_68(cl, wanted); + break; + + case 1: + /* reduce extreme columns with large width deviation */ + if (st->width_deviation < st->width_avg / 2.0) + break; + /* fallthrough */ + case 2: + /* reduce extreme columns */ + if (!scols_column_is_noextremes(cl)) + break; + reduce_to_68(cl, wanted); + break; + + case 3: + /* reduce columns with trunc flag and relative whint and large width deviation */ + if (st->width_deviation < st->width_avg / 2.0) + break; + /* fallthrough */ + case 4: + /* reduce columns with trunc flag and relative whint */ + if (!is_trunc) + break; + if (cl->width_hint <= 0 || cl->width_hint >= 1) + break; + if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) + break; + reduce_to_68(cl, wanted); + break; + + case 5: + /* reduce all columns with trunc flag large width deviation */ + if (st->width_deviation < st->width_avg / 2.2) + break; + /* fallthrough */ + case 6: + /* reduce all columns with trunc flag */ + if (!is_trunc && !scols_column_is_noextremes(cl)) + break; + if (nth == 0) + /* columns are reduced in "bad first" way, be more + * agresive for the the worst column */ + reduce = 3; + if (cl->width - reduce < st->width_min) + reduce = cl->width - st->width_min; + cl->width -= reduce; + break; + default: + return -1; /* no more stages */ + } + + /* hide zero width columns */ + if (cl->width == 0) + cl->flags |= SCOLS_FL_HIDDEN; + + if (cl->width != org_width) + DBG(COL, ul_debugobj(cl, " [%02zd] %s reduced %zu-->%zu", + cl->seqnum, + cl->header.data, org_width, cl->width)); + + *width -= org_width - cl->width; + return 0; +} + +/* + * This is core of the scols_* voodoo... + */ +int __scols_calculate(struct libscols_table *tb, struct ul_buffer *buf) +{ + struct libscols_column *cl, *last_cl; + struct libscols_iter itr; + size_t width = 0, width_min = 0; /* output width */ + int stage = 0, rc = 0; + int ignore_extremes = 0, group_ncolumns = 0; + size_t colsepsz; + int sorted = 0; + + + DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth)); + tb->is_dummy_print = 1; + + colsepsz = scols_table_is_noencoding(tb) ? + mbs_width(colsep(tb)) : + mbs_safe_width(colsep(tb)); + + if (has_groups(tb)) + group_ncolumns = 1; + + /* set basic columns width + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + int is_last; + + if (scols_column_is_hidden(cl)) + continue; + + /* we print groups chart only for the for the first tree column */ + if (scols_column_is_tree(cl) && group_ncolumns == 1) { + cl->is_groups = 1; + group_ncolumns++; + } + + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + is_last = is_last_column(cl); + + width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ + width_min += cl->wstat.width_min + (is_last ? 0 : colsepsz); + } + + if (!tb->is_term) { + DBG(TAB, ul_debugobj(tb, " non-terminal output")); + goto done; + } + + /* be paranoid */ + if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (width_min > tb->termwidth + && scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + width_min--; + cl->wstat.width_min--; + } + DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); + } + + /* calculate statistics */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + count_column_deviation(tb, cl); + + if (scols_column_is_noextremes(cl)) + ignore_extremes++; + } + + /* remember last column before we sort columns */ + last_cl = list_entry(tb->tb_columns.prev, struct libscols_column, cl_columns); + + /* reduce columns width */ + while (width > tb->termwidth) { + size_t org_width = width; + int rc = 0, n = 0; + + if (!sorted) { + DBG(TAB, ul_debugobj(tb, "sorting by deviation")); + sort_columns(tb, cmp_deviation); + ON_DBG(TAB, dbg_columns(tb)); + sorted = 1; + } + + DBG(TAB, ul_debugobj(tb, "#%d reduce stage (width=%zu, term=%zu)", + stage, width, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + + while (width > tb->termwidth + && rc == 0 + && scols_table_next_column(tb, &itr, &cl) == 0) { + rc = reduce_column(tb, cl, &width, stage, n++); + } + + if (rc != 0) + break; + if (org_width == width) + stage++; + } + + /* enlarge */ + if (width < tb->termwidth) { + if (ignore_extremes) { + if (!sorted) { + sort_columns(tb, cmp_deviation); + sorted = 1; + } + + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t add; + + if (!scols_column_is_noextremes(cl) || scols_column_is_hidden(cl)) + continue; + if (cl->wstat.width_min == 0 && cl->width == 0) + continue; + + add = tb->termwidth - width; + if (add && cl->wstat.width_max && + cl->width + add > cl->wstat.width_max) + add = cl->wstat.width_max - cl->width; + if (!add) + continue; + DBG(TAB, ul_debugobj(tb, " add +%zd (extreme %s)", + add, cl->header.data)); + cl->width += add; + width += add; + + if (width == tb->termwidth) + break; + } + } + + if (width < tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); + + /* try enlarging all columns */ + while (width < tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + DBG(TAB, ul_debugobj(tb, " enlarge (max-out %s)", + cl->header.data)); + cl->width++; + width++; + if (width == tb->termwidth) + break; + } + } + } else if (width < tb->termwidth) { + /* enlarge the last column */ + DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); + + if (!scols_column_is_right(last_cl)) { + last_cl->width += tb->termwidth - width; + width = tb->termwidth; + } + } + } + + + if (sorted) { + sort_columns(tb, cmp_seqnum); + sorted = 0; + } + + /* ignore last column(s) or force last column to be truncated if + * nowrap mode enabled */ + if (tb->no_wrap && width > tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + if (width - cl->width < tb->termwidth) { + size_t r = width - tb->termwidth; + + cl->flags |= SCOLS_FL_TRUNC; + cl->width -= r; + width -= r; + } else { + cl->flags |= SCOLS_FL_HIDDEN; + width -= cl->width + colsepsz; + } + } + } +done: + if (sorted) + sort_columns(tb, cmp_seqnum); + + tb->is_dummy_print = 0; + DBG(TAB, ul_debugobj(tb, "-----final width: %zu (rc=%d)-----", width, rc)); + ON_DBG(TAB, dbg_columns(tb)); + + return rc; +} diff --git a/libsmartcols/src/cell.c b/libsmartcols/src/cell.c new file mode 100644 index 0000000..5b83123 --- /dev/null +++ b/libsmartcols/src/cell.c @@ -0,0 +1,260 @@ +/* + * cell.c - functions for table handling at the cell level + * + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: cell + * @title: Cell + * @short_description: container for your data + * + * An API to access and modify per-cell data and information. Note that cell is + * always part of the line. If you destroy (un-reference) a line than it + * destroys all line cells too. + */ + + +#include +#include +#include +#include + +#include "smartcolsP.h" + +/* + * The cell has no ref-counting, free() and new() functions. All is + * handled by libscols_line. + */ + +/** + * scols_reset_cell: + * @ce: pointer to a struct libscols_cell instance + * + * Frees the cell's internal data and resets its status. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_reset_cell(struct libscols_cell *ce) +{ + if (!ce) + return -EINVAL; + + /*DBG(CELL, ul_debugobj(ce, "reset"));*/ + free(ce->data); + free(ce->color); + memset(ce, 0, sizeof(*ce)); + return 0; +} + +/** + * scols_cell_set_data: + * @ce: a pointer to a struct libscols_cell instance + * @data: data (used for scols_print_table()) + * + * Stores a copy of the @str in @ce, the old data are deallocated by free(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_data(struct libscols_cell *ce, const char *data) +{ + return strdup_to_struct_member(ce, data, data); +} + +/** + * scols_cell_refer_data: + * @ce: a pointer to a struct libscols_cell instance + * @data: data (used for scols_print_table()) + * + * Adds a reference to @str to @ce. The pointer is deallocated by + * scols_reset_cell() or scols_unref_line(). This function is mostly designed + * for situations when the data for the cell are already composed in allocated + * memory (e.g. asprintf()) to avoid extra unnecessary strdup(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_refer_data(struct libscols_cell *ce, char *data) +{ + if (!ce) + return -EINVAL; + free(ce->data); + ce->data = data; + return 0; +} + +/** + * scols_cell_get_data: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: data in @ce or NULL. + */ +const char *scols_cell_get_data(const struct libscols_cell *ce) +{ + return ce ? ce->data : NULL; +} + +/** + * scols_cell_set_userdata: + * @ce: a pointer to a struct libscols_cell instance + * @data: private user data + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_userdata(struct libscols_cell *ce, void *data) +{ + if (!ce) + return -EINVAL; + ce->userdata = data; + return 0; +} + +/** + * scols_cell_get_userdata + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: user data + */ +void *scols_cell_get_userdata(struct libscols_cell *ce) +{ + return ce->userdata; +} + +/** + * scols_cmpstr_cells: + * @a: pointer to cell + * @b: pointer to cell + * @data: unused pointer to private data (defined by API) + * + * Compares cells data by strcoll(). The function is designed for + * scols_column_set_cmpfunc() and scols_sort_table(). + * + * Returns: follows strcoll() return values. + */ +int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, + __attribute__((__unused__)) void *data) +{ + const char *adata, *bdata; + + if (a == b) + return 0; + + adata = scols_cell_get_data(a); + bdata = scols_cell_get_data(b); + + if (adata == NULL && bdata == NULL) + return 0; + if (adata == NULL) + return -1; + if (bdata == NULL) + return 1; + return strcoll(adata, bdata); +} + +/** + * scols_cell_set_color: + * @ce: a pointer to a struct libscols_cell instance + * @color: color name or ESC sequence + * + * Set the color of @ce to @color. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_color(struct libscols_cell *ce, const char *color) +{ + if (color && !color_is_sequence(color)) { + char *seq = color_get_sequence(color); + if (!seq) + return -EINVAL; + free(ce->color); + ce->color = seq; + return 0; + } + return strdup_to_struct_member(ce, color, color); +} + +/** + * scols_cell_get_color: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: the current color of @ce. + */ +const char *scols_cell_get_color(const struct libscols_cell *ce) +{ + return ce->color; +} + +/** + * scols_cell_set_flags: + * @ce: a pointer to a struct libscols_cell instance + * @flags: SCOLS_CELL_FL_* flags + * + * Note that cells in the table are always aligned by column flags. The cell + * flags are used for table title only (now). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_flags(struct libscols_cell *ce, int flags) +{ + if (!ce) + return -EINVAL; + ce->flags = flags; + return 0; +} + +/** + * scols_cell_get_flags: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: the current flags + */ +int scols_cell_get_flags(const struct libscols_cell *ce) +{ + return ce->flags; +} + +/** + * scols_cell_get_alignment: + * @ce: a pointer to a struct libscols_cell instance + * + * Since: 2.30 + * + * Returns: SCOLS_CELL_FL_{RIGHT,CELNTER,LEFT} + */ +int scols_cell_get_alignment(const struct libscols_cell *ce) +{ + if (ce->flags & SCOLS_CELL_FL_RIGHT) + return SCOLS_CELL_FL_RIGHT; + if (ce->flags & SCOLS_CELL_FL_CENTER) + return SCOLS_CELL_FL_CENTER; + + return SCOLS_CELL_FL_LEFT; /* default */ +} + +/** + * scols_cell_copy_content: + * @dest: a pointer to a struct libscols_cell instance + * @src: a pointer to an immutable struct libscols_cell instance + * + * Copy the contents of @src into @dest. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_copy_content(struct libscols_cell *dest, + const struct libscols_cell *src) +{ + int rc; + + rc = scols_cell_set_data(dest, scols_cell_get_data(src)); + if (!rc) + rc = scols_cell_set_color(dest, scols_cell_get_color(src)); + if (!rc) + dest->userdata = src->userdata; + + DBG(CELL, ul_debugobj(src, "copy")); + return rc; +} diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c new file mode 100644 index 0000000..db4c357 --- /dev/null +++ b/libsmartcols/src/column.c @@ -0,0 +1,737 @@ +/* + * column.c - functions for table handling at the column level + * + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: column + * @title: Column + * @short_description: defines output columns formats, headers, etc. + * + * An API to access and modify per-column data and information. + */ + + +#include +#include +#include +#include + +#include "mbsalign.h" +#include "strutils.h" +#include "smartcolsP.h" + +/** + * scols_new_column: + * + * Allocates space for a new column. + * + * Returns: a pointer to a new struct libscols_column instance, NULL in case of an ENOMEM error. + */ +struct libscols_column *scols_new_column(void) +{ + struct libscols_column *cl; + + cl = calloc(1, sizeof(*cl)); + if (!cl) + return NULL; + DBG(COL, ul_debugobj(cl, "alloc")); + cl->refcount = 1; + INIT_LIST_HEAD(&cl->cl_columns); + return cl; +} + +/** + * scols_ref_column: + * @cl: a pointer to a struct libscols_column instance + * + * Increases the refcount of @cl. + */ +void scols_ref_column(struct libscols_column *cl) +{ + if (cl) + cl->refcount++; +} + +/** + * scols_unref_column: + * @cl: a pointer to a struct libscols_column instance + * + * Decreases the refcount of @cl. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_column(struct libscols_column *cl) +{ + if (cl && --cl->refcount <= 0) { + DBG(COL, ul_debugobj(cl, "dealloc")); + list_del(&cl->cl_columns); + scols_reset_cell(&cl->header); + free(cl->color); + free(cl->safechars); + free(cl->pending_data_buf); + free(cl->shellvar); + free(cl); + } +} + +/** + * scols_copy_column: + * @cl: a pointer to a struct libscols_column instance + * + * Creates a new column and copies @cl's data over to it. + * + * Returns: a pointer to a new struct libscols_column instance. + */ +struct libscols_column *scols_copy_column(const struct libscols_column *cl) +{ + struct libscols_column *ret; + + if (!cl) + return NULL; + ret = scols_new_column(); + if (!ret) + return NULL; + + DBG(COL, ul_debugobj(cl, "copy")); + + if (scols_column_set_color(ret, cl->color)) + goto err; + if (scols_cell_copy_content(&ret->header, &cl->header)) + goto err; + + ret->width = cl->width; + ret->width_hint = cl->width_hint; + ret->flags = cl->flags; + ret->is_groups = cl->is_groups; + + memcpy(&ret->wstat, &cl->wstat, sizeof(cl->wstat)); + + return ret; +err: + scols_unref_column(ret); + return NULL; +} + +/** + * scols_column_set_whint: + * @cl: a pointer to a struct libscols_column instance + * @whint: a width hint + * + * Sets the width hint of column @cl to @whint. See scols_table_new_column(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_whint(struct libscols_column *cl, double whint) +{ + if (!cl) + return -EINVAL; + + cl->width_hint = whint; + return 0; +} + +/** + * scols_column_get_whint: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The width hint of column @cl, a negative value in case of an error. + */ +double scols_column_get_whint(const struct libscols_column *cl) +{ + return cl->width_hint; +} + +/** + * scols_column_set_flags: + * @cl: a pointer to a struct libscols_column instance + * @flags: a flag mask + * + * Sets the flags of @cl to @flags. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_flags(struct libscols_column *cl, int flags) +{ + if (!cl) + return -EINVAL; + + if (cl->table) { + if (!(cl->flags & SCOLS_FL_TREE) && (flags & SCOLS_FL_TREE)) + cl->table->ntreecols++; + else if ((cl->flags & SCOLS_FL_TREE) && !(flags & SCOLS_FL_TREE)) + cl->table->ntreecols--; + } + + DBG(COL, ul_debugobj(cl, "setting flags from 0x%04x to 0x%04x", cl->flags, flags)); + cl->flags = flags; + return 0; +} + +/** + * scols_column_set_json_type: + * @cl: a pointer to a struct libscols_column instance + * @type: SCOLS_JSON_* type + * + * Sets the type used for JSON formatting, the default is SCOLS_JSON_STRING. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.33 + */ +int scols_column_set_json_type(struct libscols_column *cl, int type) +{ + if (!cl) + return -EINVAL; + + cl->json_type = type; + return 0; + +} + +/** + * scols_column_get_json_type: + * @cl: a pointer to a struct libscols_column instance + * + * Note that SCOLS_JSON_BOOLEAN interprets NULL, empty strings, '0', 'N' and + * 'n' as "false"; and everything else as "true". + * + * Returns: JSON type used for formatting or a negative value in case of an error. + * + * Since: 2.33 + */ +int scols_column_get_json_type(const struct libscols_column *cl) +{ + return cl ? cl->json_type : -EINVAL; +} + + +/** + * scols_column_get_table: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: pointer to the table where columns is used + */ +struct libscols_table *scols_column_get_table(const struct libscols_column *cl) +{ + return cl->table; +} + +/** + * scols_column_get_flags: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The flag mask of @cl, a negative value in case of an error. + */ +int scols_column_get_flags(const struct libscols_column *cl) +{ + return cl->flags; +} + +/** + * scols_column_get_header: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: A pointer to a struct libscols_cell instance, representing the + * header info of column @cl or NULL in case of an error. + */ +struct libscols_cell *scols_column_get_header(struct libscols_column *cl) +{ + return &cl->header; +} + +/** + * scols_column_set_name: + * @cl: a pointer to a struct libscols_column instance + * @name: column name + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.38 + */ +int scols_column_set_name(struct libscols_column *cl, const char *name) +{ + struct libscols_cell *hr = scols_column_get_header(cl); + + if (!hr) + return -EINVAL; + + free(cl->shellvar); + cl->shellvar = NULL; + + return scols_cell_set_data(hr, name); +} + +/** + * scols_column_get_name: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: A pointer to a column name, which is stored in column header + * + * Since: 2.38 + */ +const char *scols_column_get_name(struct libscols_column *cl) +{ + return scols_cell_get_data(&cl->header); +} + +/** + * scols_column_get_name_as_shellvar + * @cl: a pointer to a struct libscols_column instance + * + * Like scols_column_get_name(), but column name is modified to be compatible with shells + * requirements for variable names. + * + * Since: 2.38 + */ +const char *scols_column_get_name_as_shellvar(struct libscols_column *cl) +{ + if (!cl->shellvar) { + const char *s, *name = scols_column_get_name(cl); + char *p; + size_t sz; + + if (!name || !*name) + return NULL; + + /* "1FOO%" --> "_1FOO_PCT */ + sz = strlen(name) + 1 + 3; + p = cl->shellvar = calloc(1, sz + 1); + if (!cl->shellvar) + return NULL; + + /* convert "1FOO" to "_1FOO" */ + if (!isalpha(*name)) + *p++ = '_'; + + /* replace all "bad" chars with "_" */ + for (s = name; *s; s++) + *p++ = !isalnum(*s) ? '_' : *s; + + if (!*s && *(s - 1) == '%') { + *p++ = 'P'; + *p++ = 'C'; + *p++ = 'T'; + } + } + return cl->shellvar; +} + + +/** + * scols_column_set_color: + * @cl: a pointer to a struct libscols_column instance + * @color: color name or ESC sequence + * + * The default color for data cells and column header. + * + * If you want to set header specific color then use scols_column_get_header() + * and scols_cell_set_color(). + * + * If you want to set data cell specific color the use scols_line_get_cell() + + * scols_cell_set_color(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_color(struct libscols_column *cl, const char *color) +{ + if (color && !color_is_sequence(color)) { + char *seq = color_get_sequence(color); + if (!seq) + return -EINVAL; + free(cl->color); + cl->color = seq; + return 0; + } + return strdup_to_struct_member(cl, color, color); +} + +/** + * scols_column_get_color: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The current color setting of the column @cl. + */ +const char *scols_column_get_color(const struct libscols_column *cl) +{ + return cl->color; +} + +/** + * scols_wrapnl_nextchunk: + * @cl: a pointer to a struct libscols_column instance + * @data: string + * @userdata: callback private data + * + * This is built-in function for scols_column_set_wrapfunc(). This function + * terminates the current chunk by \0 and returns pointer to the begin of + * the next chunk. The chunks are based on \n. + * + * For example for data "AAA\nBBB\nCCC" the next chunk is "BBB". + * + * Returns: next chunk + * + * Since: 2.29 + */ +char *scols_wrapnl_nextchunk(const struct libscols_column *cl __attribute__((unused)), + char *data, + void *userdata __attribute__((unused))) +{ + char *p = data ? strchr(data, '\n') : NULL; + + if (p) { + *p = '\0'; + return p + 1; + } + return NULL; +} + +/** + * scols_wrapnl_chunksize: + * @cl: a pointer to a struct libscols_column instance + * @data: string + * @userdata: callback private data + * + * Analyzes @data and returns size of the largest chunk. The chunks are based + * on \n. For example for data "AAA\nBBB\nCCCC" the largest chunk size is 4. + * + * Note that the size has to be based on number of terminal cells rather than + * bytes to support multu-byte output. + * + * Returns: size of the largest chunk. + * + * Since: 2.29 + */ +size_t scols_wrapnl_chunksize(const struct libscols_column *cl __attribute__((unused)), + const char *data, + void *userdata __attribute__((unused))) +{ + size_t sum = 0; + + while (data && *data) { + const char *p; + size_t sz; + + p = strchr(data, '\n'); + if (p) { + sz = cl->table && scols_table_is_noencoding(cl->table) ? + mbs_nwidth(data, p - data) : + mbs_safe_nwidth(data, p - data, NULL); + p++; + } else { + sz = cl->table && scols_table_is_noencoding(cl->table) ? + mbs_width(data) : + mbs_safe_width(data); + } + sum = max(sum, sz); + data = p; + } + + return sum; +} + +/** + * scols_column_set_cmpfunc: + * @cl: column + * @cmp: pointer to compare function + * @data: private data for cmp function + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *, + struct libscols_cell *, + void *), + void *data) +{ + if (!cl) + return -EINVAL; + + cl->cmpfunc = cmp; + cl->cmpfunc_data = data; + return 0; +} + +/** + * scols_column_set_wrapfunc: + * @cl: a pointer to a struct libscols_column instance + * @wrap_chunksize: function to return size of the largest chink of data + * @wrap_nextchunk: function to return next zero terminated data + * @userdata: optional stuff for callbacks + * + * Extends SCOLS_FL_WRAP and can be used to set custom wrap function. The default + * is to wrap by column size, but you can create functions to wrap for example + * after \n or after words, etc. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_column_set_wrapfunc(struct libscols_column *cl, + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, + void *), + char * (*wrap_nextchunk)(const struct libscols_column *, + char *, + void *), + void *userdata) +{ + if (!cl) + return -EINVAL; + + cl->wrap_nextchunk = wrap_nextchunk; + cl->wrap_chunksize = wrap_chunksize; + cl->wrapfunc_data = userdata; + return 0; +} + +/** + * scols_column_set_safechars: + * @cl: a pointer to a struct libscols_column instance + * @safe: safe characters (e.g. "\n\t") + * + * Use for bytes you don't want to encode on output. This is for example + * necessary if you want to use custom wrap function based on \n, in this case + * you have to set "\n" as a safe char. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_column_set_safechars(struct libscols_column *cl, const char *safe) +{ + return strdup_to_struct_member(cl, safechars, safe); +} + +/** + * scols_column_get_safechars: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: safe chars + * + * Since: 2.29 + */ +const char *scols_column_get_safechars(const struct libscols_column *cl) +{ + return cl->safechars; +} + +/** + * scols_column_get_width: + * @cl: a pointer to a struct libscols_column instance + * + * Important note: the column width is unknown until library starts printing + * (width is calculated before printing). The function is usable for example in + * nextchunk() callback specified by scols_column_set_wrapfunc(). + * + * See also scols_column_get_whint(), it returns wanted size (!= final size). + * + * Returns: column width + * + * Since: 2.29 + */ +size_t scols_column_get_width(const struct libscols_column *cl) +{ + return cl->width; +} + +/** + * scols_column_is_hidden: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag hidden. + * + * Returns: 0 or 1 + * + * Since: 2.27 + */ +int scols_column_is_hidden(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_HIDDEN ? 1 : 0; +} + +/** + * scols_column_is_trunc: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag trunc. + * + * Returns: 0 or 1 + */ +int scols_column_is_trunc(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_TRUNC ? 1 : 0; +} +/** + * scols_column_is_tree: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag tree. + * + * Returns: 0 or 1 + */ +int scols_column_is_tree(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_TREE ? 1 : 0; +} +/** + * scols_column_is_right: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag right. + * + * Returns: 0 or 1 + */ +int scols_column_is_right(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_RIGHT ? 1 : 0; +} +/** + * scols_column_is_strict_width: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag strict_width. + * + * Returns: 0 or 1 + */ +int scols_column_is_strict_width(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_STRICTWIDTH ? 1 : 0; +} +/** + * scols_column_is_noextremes: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag no_extremes. + * + * Returns: 0 or 1 + */ +int scols_column_is_noextremes(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_NOEXTREMES ? 1 : 0; +} +/** + * scols_column_is_wrap: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag wrap. + * + * Returns: 0 or 1 + * + * Since: 2.28 + */ +int scols_column_is_wrap(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_WRAP ? 1 : 0; +} +/** + * scols_column_is_customwrap: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: 0 or 1 + * + * Since: 2.29 + */ +int scols_column_is_customwrap(const struct libscols_column *cl) +{ + return (cl->flags & SCOLS_FL_WRAP) + && cl->wrap_chunksize + && cl->wrap_nextchunk ? 1 : 0; +} + +/** + * scols_column_set_properties: + * @cl: a pointer to a struct libscols_column instance + * @opts: options string + * + * Set properties from string, the string is comma seprated list, like + * "trunc,right,json=number", ... + * + * Returns: 0 on success, <0 on error + * + * Since: 2.39 + */ +int scols_column_set_properties(struct libscols_column *cl, const char *opts) +{ + char *str = (char *) opts; + char *name, *value; + size_t namesz, valuesz; + unsigned int flags = 0; + int rc = 0; + + DBG(COL, ul_debugobj(cl, "apply properties '%s'", opts)); + + while (rc == 0 + && !ul_optstr_next(&str, &name, &namesz, &value, &valuesz)) { + + if (strncmp(name, "trunc", namesz) == 0) + flags |= SCOLS_FL_TRUNC; + + else if (strncmp(name, "tree", namesz) == 0) + flags |= SCOLS_FL_TREE; + + else if (strncmp(name, "right", namesz) == 0) + flags |= SCOLS_FL_RIGHT; + + else if (strncmp(name, "strictwidth", namesz) == 0) + flags |= SCOLS_FL_STRICTWIDTH; + + else if (strncmp(name, "noextremes", namesz) == 0) + flags |= SCOLS_FL_STRICTWIDTH; + + else if (strncmp(name, "hidden", namesz) == 0) + flags |= SCOLS_FL_HIDDEN; + + else if (strncmp(name, "wrap", namesz) == 0) + flags |= SCOLS_FL_WRAP; + + else if (value && strncmp(name, "json", namesz) == 0) { + + if (strncmp(value, "string", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_STRING); + else if (strncmp(value, "number", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + else if (strncmp(value, "array-string", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_STRING); + else if (strncmp(value, "array-number", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_NUMBER); + else if (strncmp(value, "boolean", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); + + } else if (value && strncmp(name, "width", namesz) == 0) { + + char *end = NULL; + double x = strtod(value, &end); + if (errno || str == end) + return -EINVAL; + + rc = scols_column_set_whint(cl, x); + + } else if (value && strncmp(name, "color", namesz) == 0) { + + char *x = strndup(value, valuesz); + if (x) { + scols_column_set_color(cl, x); + free(x); + } + + } else if (value && strncmp(name, "name", namesz) == 0) { + + char *x = strndup(value, valuesz); + if (x) { + scols_column_set_name(cl, x); + free(x); + } + } + } + + if (!rc && flags) + rc = scols_column_set_flags(cl, flags); + + return rc; +} + diff --git a/libsmartcols/src/grouping.c b/libsmartcols/src/grouping.c new file mode 100644 index 0000000..0b27cb2 --- /dev/null +++ b/libsmartcols/src/grouping.c @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2018 Karel Zak + */ +#include "smartcolsP.h" + +/** + * SECTION: grouping + * @title: Grouping + * @short_description: lines grouing + * + * Lines groups manipulation API. The grouping API can be used to create M:N + * relations between lines and on tree-like output it prints extra chart to + * visualize these relations. The group has unlimited number of members and + * group childs. See libsmartcols/sample/grouping* for more details. + */ + +/* Private API */ +void scols_ref_group(struct libscols_group *gr) +{ + if (gr) + gr->refcount++; +} + +void scols_group_remove_children(struct libscols_group *gr) +{ + if (!gr) + return; + + while (!list_empty(&gr->gr_children)) { + struct libscols_line *ln = list_entry(gr->gr_children.next, + struct libscols_line, ln_children); + + DBG(GROUP, ul_debugobj(gr, "remove child")); + list_del_init(&ln->ln_children); + scols_ref_group(ln->parent_group); + ln->parent_group = NULL; + scols_unref_line(ln); + } +} + +void scols_group_remove_members(struct libscols_group *gr) +{ + if (!gr) + return; + + while (!list_empty(&gr->gr_members)) { + struct libscols_line *ln = list_entry(gr->gr_members.next, + struct libscols_line, ln_groups); + + DBG(GROUP, ul_debugobj(gr, "remove member [%p]", ln)); + list_del_init(&ln->ln_groups); + + scols_unref_group(ln->group); + ln->group->nmembers++; + ln->group = NULL; + + scols_unref_line(ln); + } +} + +/* note group has to be already without members to deallocate */ +void scols_unref_group(struct libscols_group *gr) +{ + if (gr && --gr->refcount <= 0) { + DBG(GROUP, ul_debugobj(gr, "dealloc")); + scols_group_remove_children(gr); + list_del(&gr->gr_groups); + free(gr); + return; + } +} + + +static void groups_fix_members_order(struct libscols_line *ln) +{ + struct libscols_iter itr; + struct libscols_line *child; + + if (ln->group) { + INIT_LIST_HEAD(&ln->ln_groups); + list_add_tail(&ln->ln_groups, &ln->group->gr_members); + DBG(GROUP, ul_debugobj(ln->group, "fixing member line=%p [%zu/%zu]", + ln, ln->group->nmembers, + list_count_entries(&ln->group->gr_members))); + } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_child(ln, &itr, &child) == 0) + groups_fix_members_order(child); + + /* + * We modify gr_members list, so is_last_group_member() does not have + * to provide reliable answer, we need to verify by list_count_entries(). + */ + if (ln->group + && is_last_group_member(ln) + && ln->group->nmembers == list_count_entries(&ln->group->gr_members)) { + + DBG(GROUP, ul_debugobj(ln->group, "fixing childs")); + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &itr, &child) == 0) + groups_fix_members_order(child); + } +} + +void scols_groups_fix_members_order(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_line *ln; + struct libscols_group *gr; + + /* remove all from groups lists */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + while (!list_empty(&gr->gr_members)) { + struct libscols_line *line = list_entry(gr->gr_members.next, + struct libscols_line, ln_groups); + list_del_init(&line->ln_groups); + } + } + + /* add again to the groups list in order we walk in tree */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + groups_fix_members_order(ln); + } + + /* If group child is member of another group * + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + struct libscols_iter xitr; + struct libscols_line *child; + + scols_reset_iter(&xitr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &xitr, &child) == 0) + groups_fix_members_order(child); + } + */ +} + +static inline const char *group_state_to_string(int state) +{ + static const char *grpstates[] = { + [SCOLS_GSTATE_NONE] = "none", + [SCOLS_GSTATE_FIRST_MEMBER] = "1st-member", + [SCOLS_GSTATE_MIDDLE_MEMBER] = "middle-member", + [SCOLS_GSTATE_LAST_MEMBER] = "last-member", + [SCOLS_GSTATE_MIDDLE_CHILD] = "middle-child", + [SCOLS_GSTATE_LAST_CHILD] = "last-child", + [SCOLS_GSTATE_CONT_MEMBERS] = "continue-members", + [SCOLS_GSTATE_CONT_CHILDREN] = "continue-children" + }; + + assert(state >= 0); + assert((size_t) state < ARRAY_SIZE(grpstates)); + + return grpstates[state]; +} +/* +static void grpset_debug(struct libscols_table *tb, struct libscols_line *ln) +{ + size_t i; + + for (i = 0; i < tb->grpset_size; i++) { + if (tb->grpset[i]) { + struct libscols_group *gr = tb->grpset[i]; + + if (ln) + DBG(LINE, ul_debugobj(ln, "grpset[%zu]: %p %s", i, + gr, group_state_to_string(gr->state))); + else + DBG(LINE, ul_debug("grpset[%zu]: %p %s", i, + gr, group_state_to_string(gr->state))); + } else if (ln) { + DBG(LINE, ul_debugobj(ln, "grpset[%zu]: free", i)); + } else + DBG(LINE, ul_debug("grpset[%zu]: free", i)); + } +} +*/ +static int group_state_for_line(struct libscols_group *gr, struct libscols_line *ln) +{ + if (gr->state == SCOLS_GSTATE_NONE && + (ln->group != gr || !is_first_group_member(ln))) + /* + * NONE is possible to translate to FIRST_MEMBER only, and only if + * line group matches with the current group. + */ + return SCOLS_GSTATE_NONE; + + if (ln->group != gr && ln->parent_group != gr) { + /* Not our line, continue */ + if (gr->state == SCOLS_GSTATE_FIRST_MEMBER || + gr->state == SCOLS_GSTATE_MIDDLE_MEMBER || + gr->state == SCOLS_GSTATE_CONT_MEMBERS) + return SCOLS_GSTATE_CONT_MEMBERS; + + if (gr->state == SCOLS_GSTATE_LAST_MEMBER || + gr->state == SCOLS_GSTATE_MIDDLE_CHILD || + gr->state == SCOLS_GSTATE_CONT_CHILDREN) + return SCOLS_GSTATE_CONT_CHILDREN; + + } else if (ln->group == gr && is_first_group_member(ln)) { + return SCOLS_GSTATE_FIRST_MEMBER; + + } else if (ln->group == gr && is_last_group_member(ln)) { + return SCOLS_GSTATE_LAST_MEMBER; + + } else if (ln->group == gr && is_group_member(ln)) { + return SCOLS_GSTATE_MIDDLE_MEMBER; + + } else if (ln->parent_group == gr && is_last_group_child(ln)) { + return SCOLS_GSTATE_LAST_CHILD; + + } else if (ln->parent_group == gr && is_group_child(ln)) { + return SCOLS_GSTATE_MIDDLE_CHILD; + } + + return SCOLS_GSTATE_NONE; +} + +/* + * apply new @state to the chunk (addressed by @xx) of grpset used for the group (@gr) + */ +static void grpset_apply_group_state(struct libscols_group **xx, int state, struct libscols_group *gr) +{ + size_t i; + + DBG(GROUP, ul_debugobj(gr, " applying state to grpset")); + + /* gr->state holds the old state, @state is the new state + */ + for (i = 0; i < SCOLS_GRPSET_CHUNKSIZ; i++) + xx[i] = state == SCOLS_GSTATE_NONE ? NULL : gr; + + gr->state = state; +} + +static struct libscols_group **grpset_locate_freespace(struct libscols_table *tb, int chunks, int prepend) +{ + size_t i, avail = 0; + struct libscols_group **tmp, **first = NULL; + const size_t wanted = chunks * SCOLS_GRPSET_CHUNKSIZ; + + if (!tb->grpset_size) + prepend = 0; + /* + DBG(TAB, ul_debugobj(tb, "orig grpset:")); + grpset_debug(tb, NULL); + */ + if (prepend) { + for (i = tb->grpset_size - 1; ; i--) { + if (tb->grpset[i] == NULL) { + first = &tb->grpset[i]; + avail++; + } else + avail = 0; + if (avail == wanted) + goto done; + if (i == 0) + break; + } + } else { + for (i = 0; i < tb->grpset_size; i++) { + if (tb->grpset[i] == NULL) { + if (avail == 0) + first = &tb->grpset[i]; + avail++; + } else + avail = 0; + if (avail == wanted) + goto done; + } + } + + DBG(TAB, ul_debugobj(tb, " realocate grpset [sz: old=%zu, new=%zu, new_chunks=%d]", + tb->grpset_size, tb->grpset_size + wanted, chunks)); + + tmp = realloc(tb->grpset, (tb->grpset_size + wanted) * sizeof(struct libscols_group *)); + if (!tmp) + return NULL; + + tb->grpset = tmp; + + if (prepend) { + DBG(TAB, ul_debugobj(tb, " prepending free space")); + char *dest = (char *) tb->grpset; + + memmove( dest + (wanted * sizeof(struct libscols_group *)), + tb->grpset, + tb->grpset_size * sizeof(struct libscols_group *)); + first = tb->grpset; + } else { + first = tb->grpset + tb->grpset_size; + } + + memset(first, 0, wanted * sizeof(struct libscols_group *)); + tb->grpset_size += wanted; + +done: + /* + DBG(TAB, ul_debugobj(tb, "new grpset:")); + grpset_debug(tb, NULL); + */ + return first; +} + +static struct libscols_group **grpset_locate_group(struct libscols_table *tb, struct libscols_group *gr) +{ + size_t i; + + for (i = 0; i < tb->grpset_size; i++) { + if (gr == tb->grpset[i]) + return &tb->grpset[i]; + } + + return NULL; +} + + +static int grpset_update(struct libscols_table *tb, struct libscols_line *ln, struct libscols_group *gr) +{ + struct libscols_group **xx; + int state; + + DBG(LINE, ul_debugobj(ln, " group [%p] grpset update [grpset size=%zu]", gr, tb->grpset_size)); + + /* new state, note that gr->state still holds the original state */ + state = group_state_for_line(gr, ln); + DBG(LINE, ul_debugobj(ln, " state %s --> %s", + group_state_to_string(gr->state), + group_state_to_string(state))); + + if (state == SCOLS_GSTATE_FIRST_MEMBER && gr->state != SCOLS_GSTATE_NONE) { + DBG(LINE, ul_debugobj(ln, "wrong group initialization (%s)", group_state_to_string(gr->state))); + abort(); + } + if (state != SCOLS_GSTATE_NONE && gr->state == SCOLS_GSTATE_LAST_CHILD) { + DBG(LINE, ul_debugobj(ln, "wrong group termination (%s)", group_state_to_string(gr->state))); + abort(); + } + if (gr->state == SCOLS_GSTATE_LAST_MEMBER && + !(state == SCOLS_GSTATE_LAST_CHILD || + state == SCOLS_GSTATE_CONT_CHILDREN || + state == SCOLS_GSTATE_MIDDLE_CHILD || + state == SCOLS_GSTATE_NONE)) { + DBG(LINE, ul_debugobj(ln, "wrong group member->child order")); + abort(); + } + + /* should not happen; probably wrong line... */ + if (gr->state == SCOLS_GSTATE_NONE && state == SCOLS_GSTATE_NONE) + return 0; + + /* locate place in grpset where we draw the group */ + if (!tb->grpset || gr->state == SCOLS_GSTATE_NONE) + xx = grpset_locate_freespace(tb, 1, 1); + else + xx = grpset_locate_group(tb, gr); + if (!xx) { + DBG(LINE, ul_debugobj(ln, "failed to locate group or reallocate grpset")); + return -ENOMEM; + } + + grpset_apply_group_state(xx, state, gr); + /*ON_DBG(LINE, grpset_debug(tb, ln));*/ + return 0; +} + +static int grpset_update_active_groups(struct libscols_table *tb, struct libscols_line *ln) +{ + int rc = 0; + size_t i; + struct libscols_group *last = NULL; + + DBG(LINE, ul_debugobj(ln, " update for active groups")); + + for (i = 0; i < tb->grpset_size; i++) { + struct libscols_group *gr = tb->grpset[i]; + + if (!gr || last == gr) + continue; + last = gr; + rc = grpset_update(tb, ln, gr); + if (rc) + break; + } + + DBG(LINE, ul_debugobj(ln, " <- active groups updated [rc=%d]", rc)); + return rc; +} + +int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln) +{ + int rc = 0; + + DBG(LINE, ul_debugobj(ln, " grpset update [line: group=%p, parent_group=%p", + ln->group, ln->parent_group)); + + rc = grpset_update_active_groups(tb, ln); + if (!rc && ln->group && ln->group->state == SCOLS_GSTATE_NONE) { + DBG(LINE, ul_debugobj(ln, " introduce a new group")); + rc = grpset_update(tb, ln, ln->group); + } + return rc; +} + +void scols_groups_reset_state(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_group *gr; + + DBG(TAB, ul_debugobj(tb, "reset groups states")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + DBG(GROUP, ul_debugobj(gr, " reset to NONE")); + gr->state = SCOLS_GSTATE_NONE; + } + + if (tb->grpset) { + DBG(TAB, ul_debugobj(tb, " zeroize grpset")); + memset(tb->grpset, 0, tb->grpset_size * sizeof(struct libscols_group *)); + } + tb->ngrpchlds_pending = 0; +} + +static void add_member(struct libscols_group *gr, struct libscols_line *ln) +{ + DBG(GROUP, ul_debugobj(gr, "add member %p", ln)); + + ln->group = gr; + gr->nmembers++; + scols_ref_group(gr); + + INIT_LIST_HEAD(&ln->ln_groups); + list_add_tail(&ln->ln_groups, &gr->gr_members); + scols_ref_line(ln); +} + +/* + * Returns first group which is ready to print group children. + * + * This function scans grpset[] in backward order and returns first group + * with SCOLS_GSTATE_CONT_CHILDREN or SCOLS_GSTATE_LAST_MEMBER state. + */ +struct libscols_group *scols_grpset_get_printable_children(struct libscols_table *tb) +{ + size_t i; + + for (i = tb->grpset_size; i > 0; i -= SCOLS_GRPSET_CHUNKSIZ) { + struct libscols_group *gr = tb->grpset[i-1]; + + if (gr == NULL) + continue; + if (gr->state == SCOLS_GSTATE_CONT_CHILDREN || + gr->state == SCOLS_GSTATE_LAST_MEMBER) + return gr; + } + + return NULL; +} + + +/** + * scols_table_group_lines: + * @tb: a pointer to a struct libscols_table instance + * @ln: new group member + * @member: group member + * @id: group identifier (unused, not implemented yet), use zero. + * + * This function add line @ln to group of lines represented by @member. If the + * group is not yet defined (@member is not member of any group) than a new one + * is allocated. + * + * The @ln maybe a NULL -- in this case only a new group is allocated if not + * defined yet. + * + * Note that the same line cannot be member of more groups (not implemented + * yet). The child of any group can be member of another group. + * + * The @id is not used for now, use 0. The plan is to use it to support + * multi-group membership in future. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_table_group_lines( struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_line *member, + __attribute__((__unused__)) int id) +{ + struct libscols_group *gr = NULL; + + if (!tb || !member) { + DBG(GROUP, ul_debugobj(gr, "failed group lines (no table or member)")); + return -EINVAL; + } + if (ln) { + if (ln->group && !member->group) { + DBG(GROUP, ul_debugobj(gr, "failed group lines (new group, line member of another)")); + return -EINVAL; + } + if (ln->group && member->group && ln->group != member->group) { + DBG(GROUP, ul_debugobj(gr, "failed group lines (groups mismatch bwteen member and line")); + return -EINVAL; + } + } + + gr = member->group; + + /* create a new group */ + if (!gr) { + gr = calloc(1, sizeof(*gr)); + if (!gr) + return -ENOMEM; + DBG(GROUP, ul_debugobj(gr, "alloc")); + gr->refcount = 1; + INIT_LIST_HEAD(&gr->gr_members); + INIT_LIST_HEAD(&gr->gr_children); + INIT_LIST_HEAD(&gr->gr_groups); + + /* add group to the table */ + list_add_tail(&gr->gr_groups, &tb->tb_groups); + + /* add the first member */ + add_member(gr, member); + } + + /* add to group */ + if (ln && !ln->group) + add_member(gr, ln); + + return 0; +} + +/** + * scols_line_link_group: + * @ln: line instance + * @member: group member + * @id: group identifier (unused, not implemented yet)) + * + * Define @ln as child of group represented by group @member. The line @ln + * cannot be child of any other line. It's possible to create group->child or + * parent->child relationship, but no both for the same line (child). + * + * The @id is not used for now, use 0. The plan is to use it to support + * multi-group membership in future. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, + __attribute__((__unused__)) int id) +{ + if (!ln || !member || !member->group || ln->parent) + return -EINVAL; + + if (!list_empty(&ln->ln_children)) + return -EINVAL; + + DBG(GROUP, ul_debugobj(member->group, "add child")); + + list_add_tail(&ln->ln_children, &member->group->gr_children); + scols_ref_line(ln); + + ln->parent_group = member->group; + scols_ref_group(member->group); + + return 0; +} diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c new file mode 100644 index 0000000..dfd7510 --- /dev/null +++ b/libsmartcols/src/init.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: init + * @title: Library initialization + * @short_description: initialize debugging + * + * The library debug stuff. + */ + +#include + +#include "smartcolsP.h" + +UL_DEBUG_DEFINE_MASK(libsmartcols); +UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) = +{ + { "all", SCOLS_DEBUG_ALL, "info about all subsystems" }, + { "buff", SCOLS_DEBUG_BUFF, "output buffer utils" }, + { "cell", SCOLS_DEBUG_CELL, "table cell utils" }, + { "col", SCOLS_DEBUG_COL, "cols utils" }, + { "help", SCOLS_DEBUG_HELP, "this help" }, + { "group", SCOLS_DEBUG_GROUP, "lines grouping utils" }, + { "line", SCOLS_DEBUG_LINE, "table line utils" }, + { "tab", SCOLS_DEBUG_TAB, "table utils" }, + { NULL, 0, NULL } +}; + +/** + * scols_init_debug: + * @mask: debug mask (0xffff to enable full debugging) + * + * If the @mask is not specified, then this function reads + * the LIBSMARTCOLS_DEBUG environment variable to get the mask. + * + * Already initialized debugging stuff cannot be changed. Calling + * this function twice has no effect. + */ +void scols_init_debug(int mask) +{ + if (libsmartcols_debug_mask) + return; + + __UL_INIT_DEBUG_FROM_ENV(libsmartcols, SCOLS_DEBUG_, mask, LIBSMARTCOLS_DEBUG); + + if (libsmartcols_debug_mask != SCOLS_DEBUG_INIT + && libsmartcols_debug_mask != (SCOLS_DEBUG_HELP|SCOLS_DEBUG_INIT)) { + const char *ver = NULL; + + scols_get_library_version(&ver); + + DBG(INIT, ul_debug("library debug mask: 0x%04x", libsmartcols_debug_mask)); + DBG(INIT, ul_debug("library version: %s", ver)); + } + ON_DBG(HELP, ul_debug_print_masks("LIBSMARTCOLS_DEBUG", + UL_DEBUG_MASKNAMES(libsmartcols))); +} diff --git a/libsmartcols/src/iter.c b/libsmartcols/src/iter.c new file mode 100644 index 0000000..91cc080 --- /dev/null +++ b/libsmartcols/src/iter.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009-2014 Karel Zak + * Copyright (C) 2014 Ondrej Oprala + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: iter + * @title: Iterator + * @short_description: unified iterator + * + * The iterator keeps the direction and the last position + * for access to the internal library tables/lists. + */ + +#include +#include + +#include "smartcolsP.h" + +/** + * scols_new_iter: + * @direction: SCOLS_INTER_{FOR,BACK}WARD direction + * + * Returns: newly allocated generic libmount iterator. + */ +struct libscols_iter *scols_new_iter(int direction) +{ + struct libscols_iter *itr = calloc(1, sizeof(*itr)); + if (!itr) + return NULL; + itr->direction = direction; + return itr; +} + +/** + * scols_free_iter: + * @itr: iterator pointer + * + * Deallocates the iterator. + */ +void scols_free_iter(struct libscols_iter *itr) +{ + free(itr); +} + +/** + * scols_reset_iter: + * @itr: iterator pointer + * @direction: SCOLS_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged + * + * Resets the iterator. + */ +void scols_reset_iter(struct libscols_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +/** + * scols_iter_get_direction: + * @itr: iterator pointer + * + * Returns: SCOLS_INTER_{FOR,BACK}WARD + */ +int scols_iter_get_direction(const struct libscols_iter *itr) +{ + return itr->direction; +} diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in new file mode 100644 index 0000000..f5820e9 --- /dev/null +++ b/libsmartcols/src/libsmartcols.h.in @@ -0,0 +1,349 @@ +/* + * Prints table or tree. + * + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef _LIBSMARTCOLS_H +#define _LIBSMARTCOLS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * LIBSMARTCOLS_VERSION: + * + * Library version string + */ +#define LIBSMARTCOLS_VERSION "@LIBSMARTCOLS_VERSION@" + +/** + * libscols_iter: + * + * Generic iterator + */ +struct libscols_iter; + +/** + * libscols_symbols: + * + * Symbol groups for printing tree hierarchies + */ +struct libscols_symbols; + +/** + * libscols_cell: + * + * A cell - the smallest library object + */ +struct libscols_cell; + +/** + * libscols_line: + * + * A line - an array of cells + */ +struct libscols_line; + +/** + * libscols_table: + * + * A table - The most abstract object, encapsulating lines, columns, symbols and cells + */ +struct libscols_table; + +/** + * libscols_column: + * + * A column - defines the number of columns and column names + */ +struct libscols_column; + +/* iter.c */ +enum { + + SCOLS_ITER_FORWARD = 0, + SCOLS_ITER_BACKWARD +}; + +/* + * Column flags + */ +enum { + SCOLS_FL_TRUNC = (1 << 0), /* truncate fields data if necessary */ + SCOLS_FL_TREE = (1 << 1), /* use tree "ascii art" */ + SCOLS_FL_RIGHT = (1 << 2), /* align to the right */ + SCOLS_FL_STRICTWIDTH = (1 << 3), /* don't reduce width if column is empty */ + SCOLS_FL_NOEXTREMES = (1 << 4), /* ignore extreme fields when count column width*/ + SCOLS_FL_HIDDEN = (1 << 5), /* maintain data, but don't print */ + SCOLS_FL_WRAP = (1 << 6) /* wrap long lines to multi-line cells */ +}; + +/* + * Column JSON types + */ +enum { + SCOLS_JSON_STRING = 0, /* default */ + SCOLS_JSON_NUMBER = 1, + SCOLS_JSON_BOOLEAN = 2, + SCOLS_JSON_ARRAY_STRING = 3, /* e.g. for multi-line (SCOLS_FL_WRAP) cells */ + SCOLS_JSON_ARRAY_NUMBER = 4, + SCOLS_JSON_BOOLEAN_OPTIONAL = 5, +}; + +/* + * Cell flags, see scols_cell_set_flags() before use + */ +enum { + /* alignment evaluated in order: right,center,left */ + SCOLS_CELL_FL_LEFT = 0, + SCOLS_CELL_FL_CENTER = (1 << 0), + SCOLS_CELL_FL_RIGHT = (1 << 1) +}; + +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); +extern int scols_iter_get_direction(const struct libscols_iter *itr); + +/* init.c */ +extern void scols_init_debug(int mask); + +/* version.c */ +extern int scols_parse_version_string(const char *ver_string); +extern int scols_get_library_version(const char **ver_string); + +/* symbols.c */ +extern struct libscols_symbols *scols_new_symbols(void); +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 *sy); +extern int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_right(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str); + +extern int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str); + +/* cell.c */ +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 *data); +extern int scols_cell_refer_data(struct libscols_cell *ce, char *data); +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); + +extern int scols_cell_set_flags(struct libscols_cell *ce, int flags); +extern int scols_cell_get_flags(const struct libscols_cell *ce); +extern int scols_cell_get_alignment(const struct libscols_cell *ce); + +extern void *scols_cell_get_userdata(struct libscols_cell *ce); +extern int scols_cell_set_userdata(struct libscols_cell *ce, void *data); + +extern int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, void *data); +/* column.c */ +extern int scols_column_is_tree(const struct libscols_column *cl); +extern int scols_column_is_trunc(const struct libscols_column *cl); +extern int scols_column_is_right(const struct libscols_column *cl); +extern int scols_column_is_strict_width(const struct libscols_column *cl); +extern int scols_column_is_hidden(const struct libscols_column *cl); +extern int scols_column_is_noextremes(const struct libscols_column *cl); +extern int scols_column_is_wrap(const struct libscols_column *cl); +extern int scols_column_is_customwrap(const struct libscols_column *cl); + +extern size_t scols_column_get_width(const struct libscols_column *cl); + +extern int scols_column_set_safechars(struct libscols_column *cl, const char *safe); +extern const char *scols_column_get_safechars(const struct libscols_column *cl); + +extern int scols_column_set_json_type(struct libscols_column *cl, int type); +extern int scols_column_get_json_type(const struct libscols_column *cl); + +extern int scols_column_set_flags(struct libscols_column *cl, int flags); +extern int scols_column_get_flags(const struct libscols_column *cl); +extern struct libscols_column *scols_new_column(void); +extern void scols_ref_column(struct libscols_column *cl); +extern void scols_unref_column(struct libscols_column *cl); +extern struct libscols_column *scols_copy_column(const struct libscols_column *cl); +extern int scols_column_set_whint(struct libscols_column *cl, double whint); +extern double scols_column_get_whint(const struct libscols_column *cl); +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(const struct libscols_column *cl); +extern struct libscols_table *scols_column_get_table(const struct libscols_column *cl); + +extern int scols_column_set_name(struct libscols_column *cl, const char *name); +extern const char *scols_column_get_name(struct libscols_column *cl); +extern const char *scols_column_get_name_as_shellvar(struct libscols_column *cl); + +extern int scols_column_set_properties(struct libscols_column *cl, const char *opts); + +extern int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *a, + struct libscols_cell *b, void *), + void *data); + +extern int scols_column_set_wrapfunc(struct libscols_column *cl, + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, void *), + char * (*wrap_nextchunk)(const struct libscols_column *, + char *, void *), + void *userdata); + +extern char *scols_wrapnl_nextchunk(const struct libscols_column *cl, char *data, void *userdata); +extern size_t scols_wrapnl_chunksize(const struct libscols_column *cl, const char *data, void *userdata); + +/* 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 int scols_line_has_children(struct libscols_line *ln); +extern int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent); +extern int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, struct libscols_line **chld); +extern struct libscols_line *scols_line_get_parent(const struct libscols_line *ln); +extern int scols_line_set_color(struct libscols_line *ln, const char *color); +extern const char *scols_line_get_color(const struct libscols_line *ln); +extern size_t scols_line_get_ncells(const struct libscols_line *ln); +extern struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, size_t n); +extern struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl); +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 int scols_line_set_column_data(struct libscols_line *ln, struct libscols_column *cl, const char *data); +extern const char *scols_line_get_column_data(struct libscols_line *ln, struct libscols_column *cl); +extern int scols_line_refer_column_data(struct libscols_line *ln, struct libscols_column *cl, char *data); +extern struct libscols_line *scols_copy_line(const struct libscols_line *ln); + +/* table */ +extern int scols_table_colors_wanted(const struct libscols_table *tb); +extern int scols_table_set_name(struct libscols_table *tb, const char *name); +extern const char *scols_table_get_name(const struct libscols_table *tb); +extern struct libscols_cell *scols_table_get_title(struct libscols_table *tb); +extern int scols_table_is_raw(const struct libscols_table *tb); +extern int scols_table_is_ascii(const struct libscols_table *tb); +extern int scols_table_is_json(const struct libscols_table *tb); +extern int scols_table_is_noheadings(const struct libscols_table *tb); +extern int scols_table_is_header_repeat(const struct libscols_table *tb); +extern int scols_table_is_empty(const struct libscols_table *tb); +extern int scols_table_is_export(const struct libscols_table *tb); +extern int scols_table_is_shellvar(const struct libscols_table *tb); +extern int scols_table_is_maxout(const struct libscols_table *tb); +extern int scols_table_is_minout(const struct libscols_table *tb); +extern int scols_table_is_nowrap(const struct libscols_table *tb); +extern int scols_table_is_nolinesep(const struct libscols_table *tb); +extern int scols_table_is_tree(const struct libscols_table *tb); +extern int scols_table_is_noencoding(const struct libscols_table *tb); + +extern int scols_table_enable_colors(struct libscols_table *tb, int enable); +extern int scols_table_enable_raw(struct libscols_table *tb, int enable); +extern int scols_table_enable_ascii(struct libscols_table *tb, int enable); +extern int scols_table_enable_json(struct libscols_table *tb, int enable); +extern int scols_table_enable_noheadings(struct libscols_table *tb, int enable); +extern int scols_table_enable_header_repeat(struct libscols_table *tb, int enable); +extern int scols_table_enable_export(struct libscols_table *tb, int enable); +extern int scols_table_enable_shellvar(struct libscols_table *tb, int enable); +extern int scols_table_enable_maxout(struct libscols_table *tb, int enable); +extern int scols_table_enable_minout(struct libscols_table *tb, int enable); +extern int scols_table_enable_nowrap(struct libscols_table *tb, int enable); +extern int scols_table_enable_nolinesep(struct libscols_table *tb, int enable); +extern int scols_table_enable_noencoding(struct libscols_table *tb, int enable); + +extern int scols_table_set_column_separator(struct libscols_table *tb, const char *sep); +extern int scols_table_set_line_separator(struct libscols_table *tb, const char *sep); + +extern struct libscols_table *scols_new_table(void); +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 int scols_table_move_column(struct libscols_table *tb, struct libscols_column *pre, struct libscols_column *cl); +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_set_columns_iter(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column *cl); +extern const char *scols_table_get_column_separator(const struct libscols_table *tb); +extern const char *scols_table_get_line_separator(const struct libscols_table *tb); +extern size_t scols_table_get_ncols(const struct libscols_table *tb); +extern size_t scols_table_get_nlines(const struct libscols_table *tb); +extern struct libscols_column *scols_table_get_column(struct libscols_table *tb, size_t n); +struct libscols_column *scols_table_get_column_by_name(struct libscols_table *tb, const char *name); +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); +extern int scols_table_set_default_symbols(struct libscols_table *tb); +extern struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb); + +extern int scols_table_set_stream(struct libscols_table *tb, FILE *stream); +extern FILE *scols_table_get_stream(const struct libscols_table *tb); +extern int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce); + +extern int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_sort_table_by_tree(struct libscols_table *tb); +/* + * + */ +enum { + SCOLS_TERMFORCE_AUTO = 0, + SCOLS_TERMFORCE_NEVER, + SCOLS_TERMFORCE_ALWAYS +}; +extern int scols_table_set_termforce(struct libscols_table *tb, int force); +extern int scols_table_get_termforce(const struct libscols_table *tb); +extern int scols_table_set_termwidth(struct libscols_table *tb, size_t width); +extern size_t scols_table_get_termwidth(const struct libscols_table *tb); +extern int scols_table_set_termheight(struct libscols_table *tb, size_t height); +extern size_t scols_table_get_termheight(const struct libscols_table *tb); + + +/* table_print.c */ +extern int scols_print_table(struct libscols_table *tb); +extern int scols_print_table_to_string(struct libscols_table *tb, char **data); + +extern int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end); +extern int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data); + +/* grouping.c */ +int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, int id); +int scols_table_group_lines(struct libscols_table *tb, struct libscols_line *ln, + struct libscols_line *member, int id); +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSMARTCOLS_H */ diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym new file mode 100644 index 0000000..4499908 --- /dev/null +++ b/libsmartcols/src/libsmartcols.sym @@ -0,0 +1,217 @@ +/* + * symbols since util-linux 2.25 + * + * Copyright (C) 2014-2016 Karel Zak + */ +SMARTCOLS_2.25 { +global: + scols_cell_copy_content; + scols_cell_get_color; + scols_cell_get_data; + scols_cell_get_userdata; + scols_cell_refer_data; + scols_cell_set_color; + scols_cell_set_data; + scols_cell_set_userdata; + scols_cmpstr_cells; + scols_column_get_color; + scols_column_get_flags; + scols_column_get_header; + scols_column_get_whint; + scols_column_is_noextremes; + scols_column_is_right; + scols_column_is_strict_width; + scols_column_is_tree; + scols_column_is_trunc; + scols_column_set_cmpfunc; + scols_column_set_color; + scols_column_set_flags; + scols_column_set_whint; + scols_copy_column; + scols_copy_line; + scols_copy_symbols; + scols_copy_table; + scols_free_iter; + scols_get_library_version; + scols_init_debug; + scols_iter_get_direction; + scols_line_add_child; + scols_line_alloc_cells; + scols_line_free_cells; + scols_line_get_cell; + scols_line_get_color; + scols_line_get_column_cell; + scols_line_get_ncells; + scols_line_get_parent; + scols_line_get_userdata; + scols_line_has_children; + scols_line_next_child; + scols_line_refer_data; + scols_line_remove_child; + scols_line_set_color; + scols_line_set_data; + scols_line_set_userdata; + scols_new_column; + scols_new_iter; + scols_new_line; + scols_new_symbols; + scols_new_table; + scols_parse_version_string; + scols_print_table; + scols_print_table_to_string; + scols_ref_column; + scols_ref_line; + scols_ref_symbols; + scols_ref_table; + scols_reset_cell; + scols_reset_iter; + scols_sort_table; + scols_symbols_set_branch; + scols_symbols_set_right; + scols_symbols_set_vertical; + scols_table_add_column; + scols_table_add_line; + scols_table_colors_wanted; + scols_table_enable_ascii; + scols_table_enable_colors; + scols_table_enable_export; + scols_table_enable_maxout; + scols_table_enable_noheadings; + scols_table_enable_raw; + scols_table_get_column; + scols_table_get_column_separator; + scols_table_get_line; + scols_table_get_line_separator; + scols_table_get_ncols; + scols_table_get_nlines; + scols_table_get_stream; + scols_table_is_ascii; + scols_table_is_empty; + scols_table_is_export; + scols_table_is_maxout; + scols_table_is_noheadings; + scols_table_is_raw; + scols_table_is_tree; + scols_table_new_column; + scols_table_new_line; + scols_table_next_column; + scols_table_next_line; + scols_table_reduce_termwidth; + scols_table_remove_column; + scols_table_remove_columns; + scols_table_remove_line; + scols_table_remove_lines; + scols_table_set_column_separator; + scols_table_set_line_separator; + scols_table_set_stream; + scols_table_set_symbols; + scols_unref_column; + scols_unref_line; + scols_unref_symbols; + scols_unref_table; +local: + *; +}; + +SMARTCOLS_2.27 { +global: + scols_column_is_hidden; + scols_table_enable_json; + scols_table_is_json; + scols_table_set_name; +} SMARTCOLS_2.25; + +SMARTCOLS_2.28 { +global: + scols_column_is_wrap; + scols_line_refer_column_data; + scols_line_set_column_data; + scols_symbols_set_title_padding; + scols_table_enable_nowrap; + scols_table_get_title; + scols_cell_get_flags; + scols_cell_set_flags; + scols_table_print_range; + scols_table_print_range_to_string; + scols_table_enable_nolinesep; +} SMARTCOLS_2.27; + +SMARTCOLS_2.29 { +global: + scols_column_get_safechars; + scols_column_get_table; + scols_column_get_width; + scols_column_is_customwrap; + scols_column_set_safechars; + scols_column_set_wrapfunc; + scols_symbols_set_cell_padding; + scols_table_get_name; + scols_table_get_symbols; + scols_table_get_termforce; + scols_table_get_termwidth; + scols_table_is_nolinesep; + scols_table_is_nowrap; + scols_table_set_default_symbols; + scols_table_set_termforce; + scols_table_set_termwidth; + scols_wrapnl_chunksize; + scols_wrapnl_nextchunk; +} SMARTCOLS_2.28; + + +SMARTCOLS_2.30 { +global: + scols_cell_get_alignment; + scols_table_move_column; + scols_sort_table_by_tree; + scols_line_is_ancestor; +} SMARTCOLS_2.29; + + +SMARTCOLS_2.31 { + scols_table_set_termheight; + scols_table_get_termheight; + scols_table_is_header_repeat; + scols_table_enable_header_repeat; + scols_table_enable_noencoding; + scols_table_is_noencoding; +} SMARTCOLS_2.30; + + +SMARTCOLS_2.33 { + scols_column_set_json_type; + scols_column_get_json_type; +} SMARTCOLS_2.31; + +SMARTCOLS_2.34 { + scols_table_group_lines; + scols_line_link_group; + scols_symbols_set_group_vertical; + scols_symbols_set_group_horizontal; + scols_symbols_set_group_first_member; + scols_symbols_set_group_last_member; + scols_symbols_set_group_middle_member; + scols_symbols_set_group_last_child; + scols_symbols_set_group_middle_child; +} SMARTCOLS_2.33; + +SMARTCOLS_2.35 { + scols_table_enable_minout; + scols_table_is_minout; + scols_table_set_columns_iter; +} SMARTCOLS_2.34; + +SMARTCOLS_2.38 { + scols_line_get_column_data; + scols_column_set_name; + scols_column_get_name; + scols_column_get_name_as_shellvar; + scols_table_is_shellvar; + scols_table_enable_shellvar; +} SMARTCOLS_2.35; + + +SMARTCOLS_2.39 { + scols_column_set_properties; + scols_table_get_column_by_name; +} SMARTCOLS_2.38; diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c new file mode 100644 index 0000000..cab99c5 --- /dev/null +++ b/libsmartcols/src/line.c @@ -0,0 +1,563 @@ +/* + * line.c - functions for table handling at the line level + * + * Copyright (C) 2014 Karel Zak + * Copyright (C) 2014 Ondrej Oprala + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: line + * @title: Line + * @short_description: cells container, also keeps tree (parent->child) information + * + * An API to access and modify per-line data and information. + */ + + +#include +#include +#include +#include + +#include "smartcolsP.h" + +/** + * scols_new_line: + * + * Note that the line is allocated without cells, the cells will be allocated + * later when you add the line to the table. If you want to use the line + * without table then you have to explicitly allocate the cells by + * scols_line_alloc_cells(). + * + * Returns: a pointer to a new struct libscols_line instance. + */ +struct libscols_line *scols_new_line(void) +{ + struct libscols_line *ln; + + ln = calloc(1, sizeof(*ln)); + if (!ln) + return NULL; + + DBG(LINE, ul_debugobj(ln, "alloc")); + ln->refcount = 1; + INIT_LIST_HEAD(&ln->ln_lines); + INIT_LIST_HEAD(&ln->ln_children); + INIT_LIST_HEAD(&ln->ln_branch); + INIT_LIST_HEAD(&ln->ln_groups); + return ln; +} + +/** + * scols_ref_line: + * @ln: a pointer to a struct libscols_line instance + * + * Increases the refcount of @ln. + */ +void scols_ref_line(struct libscols_line *ln) +{ + if (ln) + ln->refcount++; +} + +/** + * scols_unref_line: + * @ln: a pointer to a struct libscols_line instance + * + * Decreases the refcount of @ln. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_line(struct libscols_line *ln) +{ + if (ln && --ln->refcount <= 0) { + DBG(CELL, ul_debugobj(ln, "dealloc")); + list_del(&ln->ln_lines); + list_del(&ln->ln_children); + list_del(&ln->ln_groups); + scols_unref_group(ln->group); + scols_line_free_cells(ln); + free(ln->color); + free(ln); + return; + } +} + +/** + * scols_line_free_cells: + * @ln: a pointer to a struct libscols_line instance + * + * Frees the allocated cells referenced to by @ln. + */ +void scols_line_free_cells(struct libscols_line *ln) +{ + size_t i; + + if (!ln || !ln->cells) + return; + + DBG(LINE, ul_debugobj(ln, "free cells")); + + for (i = 0; i < ln->ncells; i++) + scols_reset_cell(&ln->cells[i]); + + free(ln->cells); + ln->ncells = 0; + ln->cells = NULL; +} + +/** + * scols_line_alloc_cells: + * @ln: a pointer to a struct libscols_line instance + * @n: the number of elements + * + * Allocates space for @n cells. This function is optional, + * and libsmartcols automatically allocates necessary cells + * according to number of columns in the table when you add + * the line to the table. See scols_table_add_line(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_alloc_cells(struct libscols_line *ln, size_t n) +{ + struct libscols_cell *ce; + + if (!ln) + return -EINVAL; + if (ln->ncells == n) + return 0; + + if (!n) { + scols_line_free_cells(ln); + return 0; + } + + DBG(LINE, ul_debugobj(ln, "alloc %zu cells", n)); + + ce = realloc(ln->cells, n * sizeof(struct libscols_cell)); + if (!ce) + return -errno; + + if (n > ln->ncells) + memset(ce + ln->ncells, 0, + (n - ln->ncells) * sizeof(struct libscols_cell)); + + ln->cells = ce; + ln->ncells = n; + return 0; +} + +int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn) +{ + struct libscols_cell ce; + + if (!ln || newn >= ln->ncells || oldn >= ln->ncells) + return -EINVAL; + if (oldn == newn) + return 0; + + DBG(LINE, ul_debugobj(ln, "move cells[%zu] -> cells[%zu]", oldn, newn)); + + /* remember data from old position */ + memcpy(&ce, &ln->cells[oldn], sizeof(struct libscols_cell)); + + /* remove old position (move data behind oldn to oldn) */ + if (oldn + 1 < ln->ncells) + memmove(ln->cells + oldn, ln->cells + oldn + 1, + (ln->ncells - oldn - 1) * sizeof(struct libscols_cell)); + + /* create a space for new position */ + if (newn + 1 < ln->ncells) + memmove(ln->cells + newn + 1, ln->cells + newn, + (ln->ncells - newn - 1) * sizeof(struct libscols_cell)); + + /* copy original data to new position */ + memcpy(&ln->cells[newn], &ce, sizeof(struct libscols_cell)); + return 0; +} + +/** + * scols_line_set_userdata: + * @ln: a pointer to a struct libscols_line instance + * @data: user data + * + * Binds @data to @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_userdata(struct libscols_line *ln, void *data) +{ + if (!ln) + return -EINVAL; + ln->userdata = data; + return 0; +} + +/** + * scols_line_get_userdata: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: user data + */ +void *scols_line_get_userdata(struct libscols_line *ln) +{ + return ln->userdata; +} + +/** + * scols_line_remove_child: + * @ln: a pointer to a struct libscols_line instance + * @child: a pointer to a struct libscols_line instance + * + * Removes @child as a child of @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child) +{ + if (!ln || !child) + return -EINVAL; + + DBG(LINE, ul_debugobj(ln, "remove child")); + + list_del_init(&child->ln_children); + child->parent = NULL; + scols_unref_line(child); + + scols_unref_line(ln); + return 0; +} + +/** + * scols_line_add_child: + * @ln: a pointer to a struct libscols_line instance + * @child: a pointer to a struct libscols_line instance + * + * Sets @child as a child of @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child) +{ + if (!ln || !child) + return -EINVAL; + + DBG(LINE, ul_debugobj(ln, "add child")); + scols_ref_line(child); + scols_ref_line(ln); + + /* unref old<->parent */ + if (child->parent) + scols_line_remove_child(child->parent, child); + + /* new reference from parent to child */ + list_add_tail(&child->ln_children, &ln->ln_branch); + + /* new reference from child to parent */ + child->parent = ln; + return 0; +} + +/** + * scols_line_get_parent: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: a pointer to @ln's parent, NULL in case it has no parent or if there was an error. + */ +struct libscols_line *scols_line_get_parent(const struct libscols_line *ln) +{ + return ln ? ln->parent : NULL; +} + +/** + * scols_line_has_children: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: 1 if @ln has any children, otherwise 0. + */ +int scols_line_has_children(struct libscols_line *ln) +{ + return ln ? !list_empty(&ln->ln_branch) : 0; +} + +/** + * scols_line_next_child: + * @ln: a pointer to a struct libscols_line instance + * @itr: a pointer to a struct libscols_iter instance + * @chld: a pointer to a pointer to a struct libscols_line instance + * + * Finds the next child and returns a pointer to it via @chld. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld) +{ + int rc = 1; + + if (!ln || !itr || !chld) + return -EINVAL; + *chld = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &ln->ln_branch); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children); + rc = 0; + } + + return rc; +} + +/* private API */ +int scols_line_next_group_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld) +{ + int rc = 1; + + if (!ln || !itr || !chld || !ln->group) + return -EINVAL; + *chld = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &ln->group->gr_children); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children); + rc = 0; + } + + return rc; +} + +/** + * scols_line_is_ancestor: + * @ln: line + * @parent: potential parent + * + * The function is designed to detect circular dependencies between @ln and + * @parent. It checks if @ln is not any (grand) parent in the @parent's tree. + * + * Since: 2.30 + * + * Returns: 0 or 1 + */ +int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent) +{ + while (parent) { + if (parent == ln) + return 1; + parent = scols_line_get_parent(parent); + }; + return 0; +} + +/** + * scols_line_set_color: + * @ln: a pointer to a struct libscols_line instance + * @color: color name or ESC sequence + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_color(struct libscols_line *ln, const char *color) +{ + if (color && !color_is_sequence(color)) { + char *seq = color_get_sequence(color); + if (!seq) + return -EINVAL; + free(ln->color); + ln->color = seq; + return 0; + } + return strdup_to_struct_member(ln, color, color); +} + +/** + * scols_line_get_color: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: @ln's color string, NULL in case of an error. + */ +const char *scols_line_get_color(const struct libscols_line *ln) +{ + return ln->color; +} + +/** + * scols_line_get_ncells: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: number of cells + */ +size_t scols_line_get_ncells(const struct libscols_line *ln) +{ + return ln->ncells; +} + +/** + * scols_line_get_cell: + * @ln: a pointer to a struct libscols_line instance + * @n: cell number to retrieve + * + * Returns: the @n-th cell in @ln, NULL in case of an error. + */ +struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, + size_t n) +{ + if (!ln || n >= ln->ncells) + return NULL; + return &ln->cells[n]; +} + +/** + * scols_line_get_column_cell: + * @ln: a pointer to a struct libscols_line instance + * @cl: pointer to cell + * + * Like scols_line_get_cell() by cell is referenced by column. + * + * Returns: the @n-th cell in @ln, NULL in case of an error. + */ +struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl) +{ + if (!ln || !cl) + return NULL; + + return scols_line_get_cell(ln, cl->seqnum); +} + +/** + * scols_line_set_data: + * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell, whose data is to be set + * @data: actual data to set + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, n); + + if (!ce) + return -EINVAL; + return scols_cell_set_data(ce, data); +} + +/** + * scols_line_set_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be set + * @data: actual data to set + * + * The same as scols_line_set_data() but cell is referenced by column object. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_line_set_column_data(struct libscols_line *ln, + struct libscols_column *cl, + const char *data) +{ + return scols_line_set_data(ln, cl->seqnum, data); +} + +/** + * scols_line_get_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be get + * + * See also scols_cell_get_data() + * + * Returns: cell data or NULL. + * + * Since: 2.38 + */ +const char *scols_line_get_column_data(struct libscols_line *ln, + struct libscols_column *cl) +{ + struct libscols_cell *cell = scols_line_get_column_cell(ln, cl); + + return cell ? scols_cell_get_data(cell) : NULL; +} + + +/** + * scols_line_refer_data: + * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell which will refer to @data + * @data: actual data to refer to + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, n); + + if (!ce) + return -EINVAL; + return scols_cell_refer_data(ce, data); +} + +/** + * scols_line_refer_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be set + * @data: actual data to refer to + * + * The same as scols_line_refer_data() but cell is referenced by column object. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_line_refer_column_data(struct libscols_line *ln, + struct libscols_column *cl, + char *data) +{ + return scols_line_refer_data(ln, cl->seqnum, data); +} + +/** + * scols_copy_line: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: A newly allocated copy of @ln, NULL in case of an error. + */ +struct libscols_line *scols_copy_line(const struct libscols_line *ln) +{ + struct libscols_line *ret; + size_t i; + + if (!ln) + return NULL; + + ret = scols_new_line(); + if (!ret) + return NULL; + if (scols_line_set_color(ret, ln->color)) + goto err; + if (scols_line_alloc_cells(ret, ln->ncells)) + goto err; + + ret->userdata = ln->userdata; + ret->ncells = ln->ncells; + ret->seqnum = ln->seqnum; + + DBG(LINE, ul_debugobj(ln, "copy")); + + for (i = 0; i < ret->ncells; ++i) { + if (scols_cell_copy_content(&ret->cells[i], &ln->cells[i])) + goto err; + } + + return ret; +err: + scols_unref_line(ret); + return NULL; +} diff --git a/libsmartcols/src/print-api.c b/libsmartcols/src/print-api.c new file mode 100644 index 0000000..52b2664 --- /dev/null +++ b/libsmartcols/src/print-api.c @@ -0,0 +1,220 @@ +#include "smartcolsP.h" + +/** + * scola_table_print_range: + * @tb: table + * @start: first printed line or NULL to print from the begin of the table + * @end: last printed line or NULL to print all from start. + * + * If the start is the first line in the table than prints table header too. + * The header is printed only once. This does not work for trees. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end) +{ + struct ul_buffer buf = UL_INIT_BUFFER; + struct libscols_iter itr; + int rc; + + if (scols_table_is_tree(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range from API")); + + rc = __scols_initialize_printing(tb, &buf); + if (rc) + return rc; + + if (start) { + itr.direction = SCOLS_ITER_FORWARD; + itr.head = &tb->tb_lines; + itr.p = &start->ln_lines; + } else + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + if (!start || itr.p == tb->tb_lines.next) { + rc = __scols_print_header(tb, &buf); + if (rc) + goto done; + } + + rc = __scols_print_range(tb, &buf, &itr, end); +done: + __scols_cleanup_printing(tb, &buf); + return rc; +} + +/** + * scols_table_print_range_to_string: + * @tb: table + * @start: first printed line or NULL to print from the beginning of the table + * @end: last printed line or NULL to print all from start. + * @data: pointer to the beginning of a memory area to print to + * + * The same as scols_table_print_range(), but prints to @data instead of + * stream. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = scols_table_print_range(tb, start, end); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_table_print_range_to_string( + struct libscols_table *tb __attribute__((__unused__)), + struct libscols_line *start __attribute__((__unused__)), + struct libscols_line *end __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif + +static int do_print_table(struct libscols_table *tb, int *is_empty) +{ + int rc = 0; + struct ul_buffer buf = UL_INIT_BUFFER; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing")); + if (is_empty) + *is_empty = 0; + + if (list_empty(&tb->tb_columns)) { + DBG(TAB, ul_debugobj(tb, "error -- no columns")); + return -EINVAL; + } + if (list_empty(&tb->tb_lines)) { + DBG(TAB, ul_debugobj(tb, "ignore -- no lines")); + if (scols_table_is_json(tb)) { + ul_jsonwrt_init(&tb->json, tb->out, 0); + ul_jsonwrt_root_open(&tb->json); + ul_jsonwrt_array_open(&tb->json, tb->name ? tb->name : ""); + ul_jsonwrt_array_close(&tb->json); + ul_jsonwrt_root_close(&tb->json); + } else if (is_empty) + *is_empty = 1; + return 0; + } + + tb->header_printed = 0; + rc = __scols_initialize_printing(tb, &buf); + if (rc) + return rc; + + if (scols_table_is_json(tb)) { + ul_jsonwrt_root_open(&tb->json); + ul_jsonwrt_array_open(&tb->json, tb->name ? tb->name : ""); + } + + if (tb->format == SCOLS_FMT_HUMAN) + __scols_print_title(tb); + + rc = __scols_print_header(tb, &buf); + if (rc) + goto done; + + if (scols_table_is_tree(tb)) + rc = __scols_print_tree(tb, &buf); + else + rc = __scols_print_table(tb, &buf); + + if (scols_table_is_json(tb)) { + ul_jsonwrt_array_close(&tb->json); + ul_jsonwrt_root_close(&tb->json); + } +done: + __scols_cleanup_printing(tb, &buf); + return rc; +} + +/** + * scols_print_table: + * @tb: table + * + * Prints the table to the output stream and terminate by \n. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_print_table(struct libscols_table *tb) +{ + int empty = 0; + int rc = do_print_table(tb, &empty); + + if (rc == 0 && !empty && !scols_table_is_json(tb)) + fputc('\n', tb->out); + return rc; +} + +/** + * scols_print_table_to_string: + * @tb: table + * @data: pointer to the beginning of a memory area to print to + * + * Prints the table to @data. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_print_table_to_string(struct libscols_table *tb, char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = do_print_table(tb, NULL); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_print_table_to_string( + struct libscols_table *tb __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c new file mode 100644 index 0000000..6a7e6da --- /dev/null +++ b/libsmartcols/src/print.c @@ -0,0 +1,1243 @@ + /* print.c - functions to print table + * + * Copyright (C) 2010-2014 Karel Zak + * Copyright (C) 2016 Igor Gnatenko + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table_print + * @title: Table print + * @short_description: output functions + * + * Table output API. + */ + +#include +#include +#include +#include +#include + +#include "mbsalign.h" +#include "carefulputc.h" +#include "smartcolsP.h" + +/* Fallback for symbols + * + * Note that by default library define all the symbols, but in case user does + * not define all symbols or if we extended the symbols struct then we need + * fallback to be more robust and backwardly compatible. + */ +#define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ") +#define branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-") +#define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ") +#define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-") + +#define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|") +#define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-") +#define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->") +#define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->") +#define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->") +#define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-") +#define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-") + +#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \ + ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " ")) + +#define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used) + +static int is_next_columns_empty( + struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln) +{ + struct libscols_iter itr; + + if (!tb || !cl) + return 0; + if (is_last_column(cl)) + return 1; + if (!ln) + return 0; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + scols_table_set_columns_iter(tb, &itr, cl); + + /* skip current column */ + scols_table_next_column(tb, &itr, &cl); + + while (scols_table_next_column(tb, &itr, &cl) == 0) { + struct libscols_cell *ce; + const char *data = NULL; + + if (scols_column_is_hidden(cl)) + continue; + if (scols_column_is_tree(cl)) + return 0; + + ce = scols_line_get_cell(ln, cl->seqnum); + if (ce) + data = scols_cell_get_data(ce); + if (data && *data) + return 0; + } + return 1; +} + +/* returns pointer to the end of used data */ +static int tree_ascii_art_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct ul_buffer *buf) +{ + const char *art; + int rc; + + assert(ln); + assert(buf); + + if (!ln->parent) + return 0; + + rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); + if (rc) + return rc; + + if (is_last_child(ln)) + art = " "; + else + art = vertical_symbol(tb); + + return ul_buffer_append_string(buf, art); +} + +static int grpset_is_empty( struct libscols_table *tb, + size_t idx, + size_t *rest) +{ + size_t i; + + for (i = idx; i < tb->grpset_size; i++) { + if (tb->grpset[i] == NULL) { + if (rest) + (*rest)++; + } else + return 0; + } + return 1; +} + +static int groups_ascii_art_to_buffer( struct libscols_table *tb, + struct libscols_line *ln, + struct ul_buffer *buf, + int empty) +{ + int filled = 0; + size_t i, rest = 0; + const char *filler = cellpadding_symbol(tb); + + if (!has_groups(tb)) + return 0; + + DBG(LINE, ul_debugobj(ln, "printing groups chart")); + + if (tb->is_dummy_print) + return 0; /* allocate grpset[] only */ + + for (i = 0; i < tb->grpset_size; i += SCOLS_GRPSET_CHUNKSIZ) { + struct libscols_group *gr = tb->grpset[i]; + + if (!gr) { + ul_buffer_append_ntimes(buf, SCOLS_GRPSET_CHUNKSIZ, cellpadding_symbol(tb)); + continue; + } + + /* + * Empty cells (multi-line entries, etc.), print vertical symbols only + * to show that the group continues. + */ + if (empty) { + switch (gr->state) { + case SCOLS_GSTATE_FIRST_MEMBER: + case SCOLS_GSTATE_MIDDLE_MEMBER: + case SCOLS_GSTATE_CONT_MEMBERS: + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_ntimes(buf, 2, filler); + break; + + case SCOLS_GSTATE_LAST_MEMBER: + case SCOLS_GSTATE_MIDDLE_CHILD: + case SCOLS_GSTATE_CONT_CHILDREN: + ul_buffer_append_string(buf, filler); + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_string(buf, filler); + break; + case SCOLS_GSTATE_LAST_CHILD: + ul_buffer_append_ntimes(buf, 3, filler); + break; + } + continue; + } + + /* + * Regular cell + */ + switch (gr->state) { + case SCOLS_GSTATE_FIRST_MEMBER: + ul_buffer_append_string(buf, grp_m_first_symbol(tb)); + break; + case SCOLS_GSTATE_MIDDLE_MEMBER: + ul_buffer_append_string(buf, grp_m_middle_symbol(tb)); + break; + case SCOLS_GSTATE_LAST_MEMBER: + ul_buffer_append_string(buf, grp_m_last_symbol(tb)); + break; + case SCOLS_GSTATE_CONT_MEMBERS: + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_ntimes(buf, 2, filler); + break; + case SCOLS_GSTATE_MIDDLE_CHILD: + ul_buffer_append_string(buf, filler); + ul_buffer_append_string(buf, grp_c_middle_symbol(tb)); + if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) { + ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); + filled = 1; + } + filler = grp_horizontal_symbol(tb); + break; + case SCOLS_GSTATE_LAST_CHILD: + ul_buffer_append_string(buf, cellpadding_symbol(tb)); + ul_buffer_append_string(buf, grp_c_last_symbol(tb)); + if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) { + ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); + filled = 1; + } + filler = grp_horizontal_symbol(tb); + break; + case SCOLS_GSTATE_CONT_CHILDREN: + ul_buffer_append_string(buf, filler); + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_string(buf, filler); + break; + } + + if (filled) + break; + } + + if (!filled) + ul_buffer_append_string(buf, filler); + return 0; +} + +static int has_pending_data(struct libscols_table *tb) +{ + struct libscols_column *cl; + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) + return 1; + } + return 0; +} + +static void fputs_color_reset(struct libscols_table *tb) +{ + if (tb->cur_color) { + fputs(UL_COLOR_RESET, tb->out); + tb->cur_color = NULL; + } +} + +static void fputs_color(struct libscols_table *tb, const char *color) +{ + if (tb->cur_color) + fputs_color_reset(tb); + + tb->cur_color = color; + if (color) + fputs(color, tb->out); +} + +static const char *get_cell_color(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, + struct libscols_cell *ce) +{ + const char *color = NULL; + + if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) + return NULL; + if (ce) + color = ce->color; + if (!color && (!ln || !ln->color) && cl) + color = cl->color; + return color; +} + +/* switch from line color to cell/column color */ +static void fputs_color_cell_open(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, + struct libscols_cell *ce) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + + if (color) + fputs_color(tb, color); +} + +/* switch from cell/column color to line color or reset */ +static void fputs_color_cell_close(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, + struct libscols_cell *ce) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + + if (color) + fputs_color(tb, ln ? ln->color : NULL); +} + +/* switch to line color */ +static void fputs_color_line_open(struct libscols_table *tb, + struct libscols_line *ln) +{ + if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) + return; + fputs_color(tb, ln ? ln->color : NULL); +} + +/* switch off all colors */ +static void fputs_color_line_close(struct libscols_table *tb) +{ + if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) + return; + fputs_color_reset(tb); +} + +/* print padding or ASCII-art instead of data of @cl */ +static void print_empty_cell(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, + size_t bufsz) +{ + size_t len_pad = 0; /* in screen cells as opposed to bytes */ + + DBG(COL, ul_debugobj(cl, " printing empty cell")); + + fputs_color_cell_open(tb, cl, ln, ce); + + /* generate tree/group ASCII-art rather than padding + */ + if (ln && scols_column_is_tree(cl)) { + struct ul_buffer art = UL_INIT_BUFFER; + char *data; + + if (ul_buffer_alloc_data(&art, bufsz) != 0) + goto done; + + if (cl->is_groups) + groups_ascii_art_to_buffer(tb, ln, &art, 1); + + tree_ascii_art_to_buffer(tb, ln, &art); + + if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) + ul_buffer_append_string(&art, vertical_symbol(tb)); + + if (scols_table_is_noencoding(tb)) + data = ul_buffer_get_data(&art, NULL, &len_pad); + else + data = ul_buffer_get_safe_data(&art, NULL, &len_pad, NULL); + + if (data && len_pad) + fputs(data, tb->out); + ul_buffer_free_data(&art); + } + +done: + /* minout -- don't fill */ + if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { + fputs_color_cell_close(tb, cl, ln, ce); + return; + } + + /* default -- fill except last column */ + if (!scols_table_is_maxout(tb) && is_last_column(cl)) { + fputs_color_cell_close(tb, cl, ln, ce); + return; + } + + /* fill rest of cell with space */ + for(; len_pad < cl->width; ++len_pad) + fputs(cellpadding_symbol(tb), tb->out); + + fputs_color_cell_close(tb, cl, ln, ce); + + if (!is_last_column(cl)) + fputs(colsep(tb), tb->out); +} + + + +/* Fill the start of a line with padding (or with tree ascii-art). + * + * This is necessary after a long non-truncated column, as this requires the + * next column to be printed on the next line. For example (see 'DDD'): + * + * aaa bbb ccc ddd eee + * AAA BBB CCCCCCC + * DDD EEE + * ^^^^^^^^^^^^ + * new line padding + */ +static void print_newline_padding(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, + size_t bufsz) +{ + size_t i; + + assert(tb); + assert(cl); + + DBG(LINE, ul_debugobj(ln, "printing newline padding")); + + fputs(linesep(tb), tb->out); /* line break */ + tb->termlines_used++; + + fputs_color_line_open(tb, ln); + + /* fill cells after line break */ + for (i = 0; i <= (size_t) cl->seqnum; i++) + print_empty_cell(tb, scols_table_get_column(tb, i), ln, ce, bufsz); + + fputs_color_line_close(tb); +} + +/* + * Pending data + * + * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is + * printed as usually and output is truncated to match column width. + * + * The rest of the long text is printed on next extra line(s). The extra lines + * don't exist in the table (not represented by libscols_line). The data for + * the extra lines are stored in libscols_column->pending_data_buf and the + * function print_line() adds extra lines until the buffer is not empty in all + * columns. + */ + +/* set data that will be printed by extra lines */ +static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz) +{ + char *p = NULL; + + if (data && *data) { + DBG(COL, ul_debugobj(cl, "setting pending data")); + assert(sz); + p = strdup(data); + if (!p) + return -ENOMEM; + } + + free(cl->pending_data_buf); + cl->pending_data_buf = p; + cl->pending_data_sz = sz; + cl->pending_data = cl->pending_data_buf; + return 0; +} + +/* the next extra line has been printed, move pending data cursor */ +static int step_pending_data(struct libscols_column *cl, size_t bytes) +{ + DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes)); + + if (bytes >= cl->pending_data_sz) + return set_pending_data(cl, NULL, 0); + + cl->pending_data += bytes; + cl->pending_data_sz -= bytes; + return 0; +} + +/* print next pending data for the column @cl */ +static int print_pending_data( + struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) +{ + size_t width = cl->width, bytes; + size_t len = width, i; + char *data; + char *nextchunk = NULL; + + if (!cl->pending_data) + return 0; + if (!width) + return -EINVAL; + + DBG(COL, ul_debugobj(cl, "printing pending data")); + + data = strdup(cl->pending_data); + if (!data) + goto err; + + if (scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + bytes = nextchunk - data; + + len = scols_table_is_noencoding(tb) ? + mbs_nwidth(data, bytes) : + mbs_safe_nwidth(data, bytes, NULL); + } else + bytes = mbs_truncate(data, &len); + + if (bytes == (size_t) -1) + goto err; + + if (bytes) + step_pending_data(cl, bytes); + + fputs_color_cell_open(tb, cl, ln, ce); + + fputs(data, tb->out); + free(data); + + /* minout -- don't fill */ + if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* default -- fill except last column */ + if (!scols_table_is_maxout(tb) && is_last_column(cl)) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* fill rest of cell with space */ + for(i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + + fputs_color_cell_close(tb, cl, ln, ce); + + if (!is_last_column(cl)) + fputs(colsep(tb), tb->out); + + return 0; +err: + free(data); + return -errno; +} + +static void print_json_data(struct libscols_table *tb, + struct libscols_column *cl, + const char *name, + char *data) +{ + switch (cl->json_type) { + case SCOLS_JSON_STRING: + /* name: "aaa" */ + ul_jsonwrt_value_s(&tb->json, name, data); + break; + case SCOLS_JSON_NUMBER: + /* name: 123 */ + ul_jsonwrt_value_raw(&tb->json, name, data); + break; + case SCOLS_JSON_BOOLEAN: + case SCOLS_JSON_BOOLEAN_OPTIONAL: + /* name: true|false|null */ + if (cl->json_type == SCOLS_JSON_BOOLEAN_OPTIONAL && (!*data || !strcmp(data, "-"))) { + ul_jsonwrt_value_null(&tb->json, name); + } else { + ul_jsonwrt_value_boolean(&tb->json, name, + !*data ? 0 : + *data == '0' ? 0 : + *data == 'N' || *data == 'n' ? 0 : 1); + } + break; + case SCOLS_JSON_ARRAY_STRING: + case SCOLS_JSON_ARRAY_NUMBER: + /* name: [ "aaa", "bbb", "ccc" ] */ + ul_jsonwrt_array_open(&tb->json, name); + + if (!scols_column_is_customwrap(cl)) + ul_jsonwrt_value_s(&tb->json, NULL, data); + else do { + char *next = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data); + + if (cl->json_type == SCOLS_JSON_ARRAY_STRING) + ul_jsonwrt_value_s(&tb->json, NULL, data); + else + ul_jsonwrt_value_raw(&tb->json, NULL, data); + data = next; + } while (data); + + ul_jsonwrt_array_close(&tb->json); + break; + } +} + +static int print_data(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, /* optional */ + struct ul_buffer *buf) +{ + size_t len = 0, i, width, bytes; + char *data, *nextchunk; + const char *name = NULL; + int is_last; + + assert(tb); + assert(cl); + + data = ul_buffer_get_data(buf, NULL, NULL); + if (!data) + data = ""; + + if (tb->format != SCOLS_FMT_HUMAN) { + name = scols_table_is_shellvar(tb) ? + scols_column_get_name_as_shellvar(cl) : + scols_column_get_name(cl); + } + + is_last = is_last_column(cl); + + if (is_last && scols_table_is_json(tb) && + scols_table_is_tree(tb) && has_children(ln)) + /* "children": [] is the real last value */ + is_last = 0; + + switch (tb->format) { + case SCOLS_FMT_RAW: + fputs_nonblank(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_EXPORT: + fputs(name ?: "", tb->out); + fputc('=', tb->out); + fputs_quoted(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_JSON: + print_json_data(tb, cl, name, data); + return 0; + + case SCOLS_FMT_HUMAN: + break; /* continue below */ + } + + /* Encode. Note that 'len' and 'width' are number of cells, not bytes. + */ + if (scols_table_is_noencoding(tb)) + data = ul_buffer_get_data(buf, &bytes, &len); + else + data = ul_buffer_get_safe_data(buf, &bytes, &len, scols_column_get_safechars(cl)); + + if (!data) + data = ""; + width = cl->width; + + /* custom multi-line cell based */ + if (*data && scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + set_pending_data(cl, nextchunk, bytes - (nextchunk - data)); + bytes = nextchunk - data; + + len = scols_table_is_noencoding(tb) ? + mbs_nwidth(data, bytes) : + mbs_safe_nwidth(data, bytes, NULL); + } + + if (is_last + && len < width + && !scols_table_is_maxout(tb) + && !scols_column_is_right(cl)) + width = len; + + /* truncate data */ + if (len > width && scols_column_is_trunc(cl)) { + len = width; + bytes = mbs_truncate(data, &len); /* updates 'len' */ + } + + /* standard multi-line cell */ + if (len > width && scols_column_is_wrap(cl) + && !scols_column_is_customwrap(cl)) { + set_pending_data(cl, data, bytes); + + len = width; + bytes = mbs_truncate(data, &len); + if (bytes != (size_t) -1 && bytes > 0) + step_pending_data(cl, bytes); + } + + if (bytes == (size_t) -1) { + bytes = len = 0; + data = NULL; + } + + fputs_color_cell_open(tb, cl, ln, ce); + + if (data && *data) { + if (scols_column_is_right(cl)) { + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + len = width; + } + fputs(data, tb->out); + + } + + /* minout -- don't fill */ + if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* default -- fill except last column */ + if (!scols_table_is_maxout(tb) && is_last) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* fill rest of cell with space */ + for(i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + + fputs_color_cell_close(tb, cl, ln, ce); + + if (len > width && !scols_column_is_trunc(cl)) { + DBG(COL, ul_debugobj(cl, "*** data len=%zu > column width=%zu", len, width)); + print_newline_padding(tb, cl, ln, ce, ul_buffer_get_bufsiz(buf)); /* next column starts on next line */ + + } else if (!is_last) + fputs(colsep(tb), tb->out); /* columns separator */ + + return 0; +} + +int __cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct ul_buffer *buf) +{ + const char *data; + struct libscols_cell *ce; + int rc = 0; + + assert(tb); + assert(ln); + assert(cl); + assert(buf); + assert(cl->seqnum <= tb->ncols); + + ul_buffer_reset_data(buf); + + ce = scols_line_get_cell(ln, cl->seqnum); + data = ce ? scols_cell_get_data(ce) : NULL; + + if (!scols_column_is_tree(cl)) + return data ? ul_buffer_append_string(buf, data) : 0; + + /* + * Group stuff + */ + if (!scols_table_is_json(tb) && cl->is_groups) + rc = groups_ascii_art_to_buffer(tb, ln, buf, 0); + + /* + * Tree stuff + */ + if (!rc && ln->parent && !scols_table_is_json(tb)) { + rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); + + if (!rc && is_last_child(ln)) + rc = ul_buffer_append_string(buf, right_symbol(tb)); + else if (!rc) + rc = ul_buffer_append_string(buf, branch_symbol(tb)); + } + + if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb)) + ul_buffer_save_pointer(buf, SCOLS_BUFPTR_TREEEND); + + if (!rc && data) + rc = ul_buffer_append_string(buf, data); + return rc; +} + +/* + * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and + * control and non-printable characters can be encoded in the \x?? encoding. + */ +static int print_line(struct libscols_table *tb, + struct libscols_line *ln, + struct ul_buffer *buf) +{ + int rc = 0, pending = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(ln); + + DBG(LINE, ul_debugobj(ln, " printing line")); + + fputs_color_line_open(tb, ln); + + /* regular line */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc == 0) + rc = print_data(tb, cl, ln, + scols_line_get_cell(ln, cl->seqnum), + buf); + if (rc == 0 && cl->pending_data) + pending = 1; + } + fputs_color_line_close(tb); + + /* extra lines of the multi-line cells */ + while (rc == 0 && pending) { + DBG(LINE, ul_debugobj(ln, "printing pending data")); + pending = 0; + fputs(linesep(tb), tb->out); + fputs_color_line_open(tb, ln); + tb->termlines_used++; + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) { + rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum)); + if (rc == 0 && cl->pending_data) + pending = 1; + } else + print_empty_cell(tb, cl, ln, NULL, ul_buffer_get_bufsiz(buf)); + } + fputs_color_line_close(tb); + } + + return 0; +} + +int __scols_print_title(struct libscols_table *tb) +{ + int rc; + mbs_align_t align; + size_t width, len = 0, bufsz, titlesz; + char *title = NULL, *buf = NULL; + + assert(tb); + + if (!tb->title.data) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing title")); + + /* encode data */ + if (tb->no_encode) { + len = bufsz = strlen(tb->title.data) + 1; + buf = strdup(tb->title.data); + if (!buf) { + rc = -ENOMEM; + goto done; + } + } else { + bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1; + if (bufsz == 1) { + DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore")); + return 0; + } + buf = malloc(bufsz); + if (!buf) { + rc = -ENOMEM; + goto done; + } + + if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) || + !len || len == (size_t) -1) { + rc = -EINVAL; + goto done; + } + } + + /* truncate and align */ + width = tb->is_term ? tb->termwidth : 80; + titlesz = width + bufsz; + + title = malloc(titlesz); + if (!title) { + rc = -EINVAL; + goto done; + } + + switch (scols_cell_get_alignment(&tb->title)) { + case SCOLS_CELL_FL_RIGHT: + align = MBS_ALIGN_RIGHT; + break; + case SCOLS_CELL_FL_CENTER: + align = MBS_ALIGN_CENTER; + break; + case SCOLS_CELL_FL_LEFT: + default: + align = MBS_ALIGN_LEFT; + /* + * Don't print extra blank chars after the title if on left + * (that's same as we use for the last column in the table). + */ + if (len < width + && !scols_table_is_maxout(tb) + && isblank(*titlepadding_symbol(tb))) + width = len; + break; + + } + + /* copy from buf to title and align to width with title_padding */ + rc = mbsalign_with_padding(buf, title, titlesz, + &width, align, + 0, (int) *titlepadding_symbol(tb)); + + if (rc == -1) { + rc = -EINVAL; + goto done; + } + + + if (tb->colors_wanted) + fputs_color(tb, tb->title.color); + + fputs(title, tb->out); + + if (tb->colors_wanted) + fputs_color_reset(tb); + + fputc('\n', tb->out); + rc = 0; +done: + free(buf); + free(title); + DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc)); + return rc; +} + +int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf) +{ + int rc = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(tb); + + if ((tb->header_printed == 1 && tb->header_repeat == 0) || + scols_table_is_noheadings(tb) || + scols_table_is_export(tb) || + scols_table_is_json(tb) || + list_empty(&tb->tb_lines)) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing header")); + + /* set the width according to the size of the data */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + + ul_buffer_reset_data(buf); + + if (cl->is_groups + && scols_table_is_tree(tb) && scols_column_is_tree(cl)) { + size_t i; + for (i = 0; i < tb->grpset_size + 1; i++) { + rc = ul_buffer_append_data(buf, " ", 1); + if (rc) + break; + } + } + if (!rc) + rc = ul_buffer_append_string(buf, + scols_table_is_shellvar(tb) ? + scols_column_get_name_as_shellvar(cl) : + scols_column_get_name(cl)); + if (!rc) + rc = print_data(tb, cl, NULL, &cl->header, buf); + } + + if (rc == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + tb->header_printed = 1; + tb->header_next = tb->termlines_used + tb->termheight; + if (tb->header_repeat) + DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]", + tb->header_next, tb->termlines_used, rc)); + return rc; +} + + +int __scols_print_range(struct libscols_table *tb, + struct ul_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end) +{ + int rc = 0; + struct libscols_line *ln; + + assert(tb); + DBG(TAB, ul_debugobj(tb, "printing range")); + + while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { + + int last = scols_iter_is_last(itr); + + if (scols_table_is_json(tb)) + ul_jsonwrt_object_open(&tb->json, NULL); + + rc = print_line(tb, ln, buf); + + if (scols_table_is_json(tb)) + ul_jsonwrt_object_close(&tb->json); + else if (last == 0 && tb->no_linesep == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + if (end && ln == end) + break; + + if (!last && want_repeat_header(tb)) + __scols_print_header(tb, buf); + } + + return rc; + +} + +int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf) +{ + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + return __scols_print_range(tb, buf, &itr, NULL); +} + +/* scols_walk_tree() callback to print tree line */ +static int print_tree_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl __attribute__((__unused__)), + void *data) +{ + struct ul_buffer *buf = (struct ul_buffer *) data; + int rc; + + DBG(LINE, ul_debugobj(ln, " printing tree line")); + + if (scols_table_is_json(tb)) + ul_jsonwrt_object_open(&tb->json, NULL); + + rc = print_line(tb, ln, buf); + if (rc) + return rc; + + if (has_children(ln)) { + if (scols_table_is_json(tb)) + ul_jsonwrt_array_open(&tb->json, "children"); + else { + /* between parent and child is separator */ + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + } else { + int last; + + /* terminate all open last children for JSON */ + if (scols_table_is_json(tb)) { + do { + last = (is_child(ln) && is_last_child(ln)) || + (is_tree_root(ln) && is_last_tree_root(tb, ln)); + + ul_jsonwrt_object_close(&tb->json); + if (last && is_child(ln)) + ul_jsonwrt_array_close(&tb->json); + ln = ln->parent; + } while(ln && last); + + } else if (tb->no_linesep == 0) { + int last_in_tree = scols_walk_is_last(tb, ln); + + if (last_in_tree == 0) { + /* standard output */ + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + } + } + + return 0; +} + +int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf) +{ + assert(tb); + DBG(TAB, ul_debugobj(tb, "----printing-tree-----")); + + return scols_walk_tree(tb, NULL, print_tree_line, (void *) buf); +} + +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; +} + +void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf) +{ + if (!tb) + return; + + ul_buffer_free_data(buf); + + if (tb->priv_symbols) { + scols_table_set_symbols(tb, NULL); + tb->priv_symbols = 0; + } +} + +int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf) +{ + size_t bufsz, extra_bufsz = 0; + struct libscols_line *ln; + struct libscols_iter itr; + int rc; + + DBG(TAB, ul_debugobj(tb, "initialize printing")); + + if (!tb->symbols) { + rc = scols_table_set_default_symbols(tb); + if (rc) + goto err; + tb->priv_symbols = 1; + } else + tb->priv_symbols = 0; + + if (tb->format == SCOLS_FMT_HUMAN) + tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 : + tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 : + isatty(STDOUT_FILENO); + + if (tb->is_term) { + size_t width = (size_t) scols_table_get_termwidth(tb); + + if (tb->termreduce > 0 && tb->termreduce < width) { + width -= tb->termreduce; + scols_table_set_termwidth(tb, width); + } + bufsz = width; + } else + bufsz = BUFSIZ; + + if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) + tb->header_repeat = 0; + + /* + * Estimate extra space necessary for tree, JSON or another output + * decoration. + */ + if (scols_table_is_tree(tb)) + extra_bufsz += tb->nlines * strlen(vertical_symbol(tb)); + + switch (tb->format) { + case SCOLS_FMT_RAW: + extra_bufsz += tb->ncols; /* separator between columns */ + break; + case SCOLS_FMT_JSON: + ul_jsonwrt_init(&tb->json, tb->out, 0); + extra_bufsz += tb->nlines * 3; /* indentation */ + /* fallthrough */ + case SCOLS_FMT_EXPORT: + { + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + + if (scols_column_get_name(cl)) + extra_bufsz += strlen(scols_column_get_name(cl)); /* data */ + extra_bufsz += 2; /* separators */ + } + break; + } + case SCOLS_FMT_HUMAN: + break; + } + + /* + * Enlarge buffer if necessary, the buffer should be large enough to + * store line data and tree ascii art (or another decoration). + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t sz; + + sz = strlen_line(ln) + extra_bufsz; + if (sz > bufsz) + bufsz = sz; + } + + /* pre-allocate space for data */ + rc = ul_buffer_alloc_data(buf, bufsz + 1); /* data + space for \0 */ + if (rc) + goto err; + + /* + * Make sure groups members are in the same orders as the tree + */ + if (has_groups(tb) && scols_table_is_tree(tb)) + scols_groups_fix_members_order(tb); + + if (tb->format == SCOLS_FMT_HUMAN) { + rc = __scols_calculate(tb, buf); + if (rc != 0) + goto err; + } + + return 0; +err: + __scols_cleanup_printing(tb, buf); + return rc; +} + diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h new file mode 100644 index 0000000..8a7ee9b --- /dev/null +++ b/libsmartcols/src/smartcolsP.h @@ -0,0 +1,455 @@ +/* + * smartcolsP.h - private library header file + * + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2014 Karel Zak + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#ifndef _LIBSMARTCOLS_PRIVATE_H +#define _LIBSMARTCOLS_PRIVATE_H + +#include "c.h" +#include "list.h" +#include "strutils.h" +#include "color-names.h" +#include "jsonwrt.h" +#include "debug.h" +#include "buffer.h" + +#include "libsmartcols.h" + +/* + * Debug + */ +#define SCOLS_DEBUG_HELP (1 << 0) +#define SCOLS_DEBUG_INIT (1 << 1) +#define SCOLS_DEBUG_CELL (1 << 2) +#define SCOLS_DEBUG_LINE (1 << 3) +#define SCOLS_DEBUG_TAB (1 << 4) +#define SCOLS_DEBUG_COL (1 << 5) +#define SCOLS_DEBUG_BUFF (1 << 6) +#define SCOLS_DEBUG_GROUP (1 << 7) +#define SCOLS_DEBUG_ALL 0xFFFF + +UL_DEBUG_DECLARE_MASK(libsmartcols); +#define DBG(m, x) __UL_DBG(libsmartcols, SCOLS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(libsmartcols, SCOLS_DEBUG_, m, x) +#define DBG_FLUSH __UL_DBG_FLUSH(libsmartcols, SCOLS_DEBUG_) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libsmartcols) +#include "debugobj.h" + +#define SCOLS_BUFPTR_TREEEND 0 + +/* + * Generic iterator + */ +struct libscols_iter { + struct list_head *p; /* current position */ + struct list_head *head; /* start position */ + int direction; /* SCOLS_ITER_{FOR,BACK}WARD */ +}; + +/* + * Tree symbols + */ +struct libscols_symbols { + int refcount; + + char *tree_branch; + char *tree_vert; + char *tree_right; + + char *group_vert; + char *group_horz; + char *group_first_member; + char *group_last_member; + char *group_middle_member; + char *group_last_child; + char *group_middle_child; + + char *title_padding; + char *cell_padding; +}; + +/* + * Table cells + */ +struct libscols_cell { + char *data; + char *color; + void *userdata; + int flags; + size_t width; +}; + +extern int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn); + +struct libscols_wstat { + size_t width_min; + size_t width_max; + double width_avg; + double width_sqr_sum; + double width_deviation; + + size_t width_treeart; +}; + +/* + * Table column + */ +struct libscols_column { + int refcount; /* reference counter */ + size_t seqnum; /* column index */ + + size_t width; /* expected column width */ + size_t width_treeart; + double width_hint; /* hint (N < 1 is in percent of termwidth) */ + + struct libscols_wstat wstat; /* private __scols_calculate() data */ + + int json_type; /* SCOLS_JSON_* */ + + int flags; + char *color; /* default column color */ + char *safechars; /* do not encode this bytes */ + + char *pending_data; + size_t pending_data_sz; + char *pending_data_buf; + + int (*cmpfunc)(struct libscols_cell *, + struct libscols_cell *, + void *); /* cells comparison function */ + void *cmpfunc_data; + + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, void *); + char *(*wrap_nextchunk)(const struct libscols_column *, + char *, void *); + void *wrapfunc_data; + + + struct libscols_cell header; /* column name with color etc. */ + char *shellvar; /* raw colum name in shell compatible format */ + + struct list_head cl_columns; /* member of table->tb_columns */ + + struct libscols_table *table; + + unsigned int is_groups : 1; /* print group chart */ + +}; + +#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") +#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") + +enum { + SCOLS_GSTATE_NONE = 0, /* not activate yet */ + SCOLS_GSTATE_FIRST_MEMBER, + SCOLS_GSTATE_MIDDLE_MEMBER, + SCOLS_GSTATE_LAST_MEMBER, + SCOLS_GSTATE_MIDDLE_CHILD, + SCOLS_GSTATE_LAST_CHILD, + SCOLS_GSTATE_CONT_MEMBERS, + SCOLS_GSTATE_CONT_CHILDREN +}; + +/* + * Every group needs at least 3 columns + */ +#define SCOLS_GRPSET_CHUNKSIZ 3 + +struct libscols_group { + int refcount; + + size_t nmembers; + + struct list_head gr_members; /* head of line->ln_group */ + struct list_head gr_children; /* head of line->ln_children */ + struct list_head gr_groups; /* member of table->tb_groups */ + + int state; /* SCOLS_GSTATE_* */ +}; + +/* + * Table line + */ +struct libscols_line { + int refcount; + size_t seqnum; + + void *userdata; + char *color; /* default line color */ + + struct libscols_cell *cells; /* array with data */ + size_t ncells; /* number of cells */ + + struct list_head ln_lines; /* member of table->tb_lines */ + struct list_head ln_branch; /* head of line->ln_children */ + struct list_head ln_children; /* member of line->ln_children or group->gr_children */ + struct list_head ln_groups; /* member of group->gr_groups */ + + struct libscols_line *parent; + struct libscols_group *parent_group; /* for group childs */ + struct libscols_group *group; /* for group members */ +}; + +enum { + SCOLS_FMT_HUMAN = 0, /* default, human readable */ + SCOLS_FMT_RAW, /* space separated */ + SCOLS_FMT_EXPORT, /* COLNAME="data" ... */ + SCOLS_FMT_JSON /* http://en.wikipedia.org/wiki/JSON */ +}; + +/* + * The table + */ +struct libscols_table { + int refcount; + char *name; /* optional table name (for JSON) */ + size_t ncols; /* number of columns */ + size_t ntreecols; /* number of columns with SCOLS_FL_TREE */ + size_t nlines; /* number of lines */ + size_t termwidth; /* terminal width (number of columns) */ + size_t termheight; /* terminal height (number of lines) */ + size_t termreduce; /* extra blank space */ + int termforce; /* SCOLS_TERMFORCE_* */ + FILE *out; /* output stream */ + + char *colsep; /* column separator */ + char *linesep; /* line separator */ + + struct list_head tb_columns; /* list of columns, items: column->cl_columns */ + struct list_head tb_lines; /* list of lines; items: line->ln_lines */ + + struct list_head tb_groups; /* all defined groups */ + struct libscols_group **grpset; + size_t grpset_size; + + size_t ngrpchlds_pending; /* groups with not yet printed children */ + struct libscols_line *walk_last_tree_root; /* last root, used by scols_walk_() */ + + struct libscols_column *dflt_sort_column; /* default sort column, set by scols_sort_table() */ + + struct libscols_symbols *symbols; + struct libscols_cell title; /* optional table title (for humans) */ + + struct ul_jsonwrt json; /* JSON formatting */ + + int format; /* SCOLS_FMT_* */ + + size_t termlines_used; /* printed line counter */ + size_t header_next; /* where repeat header */ + + const char *cur_color; /* current active color when printing */ + + /* flags */ + unsigned int ascii :1, /* don't use unicode */ + colors_wanted :1, /* enable colors */ + is_term :1, /* isatty() */ + padding_debug :1, /* output visible padding chars */ + is_dummy_print :1, /* printing used for width calculation only */ + is_shellvar :1, /* shell compatible column names */ + maxout :1, /* maximize output */ + minout :1, /* minimize output (mutually exclusive to maxout) */ + header_repeat :1, /* print header after libscols_table->termheight */ + header_printed :1, /* header already printed */ + priv_symbols :1, /* default private symbols */ + walk_last_done :1, /* last tree root walked */ + no_headings :1, /* don't print header */ + no_encode :1, /* don't care about control and non-printable chars */ + no_linesep :1, /* don't print line separator */ + no_wrap :1; /* never wrap lines */ +}; + +#define IS_ITER_FORWARD(_i) ((_i)->direction == SCOLS_ITER_FORWARD) +#define IS_ITER_BACKWARD(_i) ((_i)->direction == SCOLS_ITER_BACKWARD) + +#define SCOLS_ITER_INIT(itr, list) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (list)->next : (list)->prev; \ + (itr)->head = (list); \ + } while(0) + +#define SCOLS_ITER_ITERATE(itr, res, restype, member) \ + do { \ + res = list_entry((itr)->p, restype, member); \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (itr)->p->next : (itr)->p->prev; \ + } while(0) + + +static inline int scols_iter_is_last(const struct libscols_iter *itr) +{ + if (!itr || !itr->head || !itr->p) + return 0; + + return itr->p == itr->head; +} + +/* + * line.c + */ +int scols_line_next_group_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld); + + +/* + * table.c + */ +int scols_table_next_group(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_group **gr); + +/* + * grouping.c + */ +void scols_ref_group(struct libscols_group *gr); +void scols_group_remove_children(struct libscols_group *gr); +void scols_group_remove_members(struct libscols_group *gr); +void scols_unref_group(struct libscols_group *gr); +void scols_groups_fix_members_order(struct libscols_table *tb); +int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln); +void scols_groups_reset_state(struct libscols_table *tb); +struct libscols_group *scols_grpset_get_printable_children(struct libscols_table *tb); + +/* + * walk.c + */ +extern int scols_walk_tree(struct libscols_table *tb, + struct libscols_column *cl, + int (*callback)(struct libscols_table *, + struct libscols_line *, + struct libscols_column *, + void *), + void *data); +extern int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln); + +/* + * calculate.c + */ +extern int __scols_calculate(struct libscols_table *tb, struct ul_buffer *buf); + +/* + * print.c + */ +extern int __cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct ul_buffer *buf); + +void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_title(struct libscols_table *tb); +int __scols_print_range(struct libscols_table *tb, + struct ul_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end); + +static inline int is_tree_root(struct libscols_line *ln) +{ + return ln && !ln->parent && !ln->parent_group; +} + +static inline int is_last_tree_root(struct libscols_table *tb, struct libscols_line *ln) +{ + if (!ln || !tb || tb->walk_last_tree_root != ln) + return 0; + + return 1; +} + +static inline int is_child(struct libscols_line *ln) +{ + return ln && ln->parent; +} + +static inline int is_last_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent) + return 0; + + return list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch); +} + +static inline int is_first_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent) + return 0; + + return list_entry_is_first(&ln->ln_children, &ln->parent->ln_branch); +} + + +static inline int is_last_column(struct libscols_column *cl) +{ + struct libscols_column *next; + + if (list_entry_is_last(&cl->cl_columns, &cl->table->tb_columns)) + return 1; + + next = list_entry(cl->cl_columns.next, struct libscols_column, cl_columns); + if (next && scols_column_is_hidden(next) && is_last_column(next)) + return 1; + return 0; +} + +static inline int is_last_group_member(struct libscols_line *ln) +{ + if (!ln || !ln->group) + return 0; + + return list_entry_is_last(&ln->ln_groups, &ln->group->gr_members); +} + +static inline int is_first_group_member(struct libscols_line *ln) +{ + if (!ln || !ln->group) + return 0; + + return list_entry_is_first(&ln->ln_groups, &ln->group->gr_members); +} + +static inline int is_group_member(struct libscols_line *ln) +{ + return ln && ln->group; +} + +static inline int is_last_group_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent_group) + return 0; + + return list_entry_is_last(&ln->ln_children, &ln->parent_group->gr_children); +} + +static inline int is_group_child(struct libscols_line *ln) +{ + return ln && ln->parent_group; +} + +static inline int has_groups(struct libscols_table *tb) +{ + return tb && !list_empty(&tb->tb_groups); +} + +static inline int has_children(struct libscols_line *ln) +{ + return ln && !list_empty(&ln->ln_branch); +} + +static inline int has_group_children(struct libscols_line *ln) +{ + return ln && ln->group && !list_empty(&ln->group->gr_children); +} + +#endif /* _LIBSMARTCOLS_PRIVATE_H */ diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c new file mode 100644 index 0000000..2fadfc7 --- /dev/null +++ b/libsmartcols/src/symbols.c @@ -0,0 +1,293 @@ +/* + * symbols.c - routines for symbol handling + * + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2016 Igor Gnatenko + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: symbols + * @title: Symbols + * @short_description: can be used to overwrite default output chars (for ascii art) + * + * An API to access and modify data and information per symbol/symbol group. + */ + + +#include +#include +#include + +#include "smartcolsP.h" + +/** + * scols_new_symbols: + * + * Returns: a pointer to a newly allocated struct libscols_symbols instance. + */ +struct libscols_symbols *scols_new_symbols(void) +{ + struct libscols_symbols *sy = calloc(1, sizeof(struct libscols_symbols)); + + if (!sy) + return NULL; + sy->refcount = 1; + return sy; +} + +/** + * scols_ref_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Increases the refcount of @sy. + */ +void scols_ref_symbols(struct libscols_symbols *sy) +{ + if (sy) + sy->refcount++; +} + +/** + * scols_unref_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Decreases the refcount of @sy. + */ +void scols_unref_symbols(struct libscols_symbols *sy) +{ + if (sy && --sy->refcount <= 0) { + free(sy->tree_branch); + free(sy->tree_vert); + free(sy->tree_right); + free(sy->group_last_member); + free(sy->group_middle_member); + free(sy->group_first_member); + free(sy->group_vert); + free(sy->group_horz); + free(sy->group_last_child); + free(sy->group_middle_child); + free(sy->title_padding); + free(sy->cell_padding); + free(sy); + } +} + +/** + * scols_symbols_set_branch: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the branch part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, tree_branch, str); +} + +/** + * scols_symbols_set_vertical: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertical part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, tree_vert, str); +} + +/** + * scols_symbols_set_right: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the right part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_right(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, tree_right, str); +} + +/** + * scols_symbols_set_title_padding: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the symbols which fill title output + * + * The current implementation uses only the first byte from the padding string. + * A multibyte chars are not supported yet. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, title_padding, str); +} + +/** + * scols_symbols_set_cell_padding: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the symbols which fill cells + * + * The padding char has to take up just one cell on the terminal. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, cell_padding, str); +} + + +/** + * scols_symbols_set_group_vertical: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertival line + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_vert, str); +} + +/** + * scols_symbols_set_group_horizontal: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the horizontal line + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_horz, str); +} + +/** + * scols_symbols_set_group_first_member: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent first member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_first_member, str); +} + +/** + * scols_symbols_set_group_last_member: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_last_member, str); +} + +/** + * scols_symbols_set_group_middle: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent middle member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_middle_member, str); +} + +/** + * scols_symbols_set_group_last_child: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last child + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_last_child, str); +} + +/** + * scols_symbols_set_group_middle_child: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last child + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_middle_child, str); +} + +/** + * scols_copy_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Returns: a newly allocated copy of the @sy symbol group or NULL in case of an error. + */ +struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy) +{ + struct libscols_symbols *ret; + int rc; + + assert(sy); + if (!sy) + return NULL; + + ret = scols_new_symbols(); + if (!ret) + return NULL; + + rc = scols_symbols_set_branch(ret, sy->tree_branch); + if (!rc) + rc = scols_symbols_set_vertical(ret, sy->tree_vert); + if (!rc) + rc = scols_symbols_set_right(ret, sy->tree_right); + if (!rc) + rc = scols_symbols_set_group_vertical(ret, sy->group_vert); + if (!rc) + rc = scols_symbols_set_group_horizontal(ret, sy->group_horz); + if (!rc) + rc = scols_symbols_set_group_first_member(ret, sy->group_first_member); + if (!rc) + rc = scols_symbols_set_group_last_member(ret, sy->group_last_member); + if (!rc) + rc = scols_symbols_set_group_middle_member(ret, sy->group_middle_member); + if (!rc) + rc = scols_symbols_set_group_middle_child(ret, sy->group_middle_child); + if (!rc) + rc = scols_symbols_set_group_last_child(ret, sy->group_last_child); + if (!rc) + rc = scols_symbols_set_title_padding(ret, sy->title_padding); + if (!rc) + rc = scols_symbols_set_cell_padding(ret, sy->cell_padding); + if (!rc) + return ret; + + scols_unref_symbols(ret); + return NULL; +} diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c new file mode 100644 index 0000000..8449c4f --- /dev/null +++ b/libsmartcols/src/table.c @@ -0,0 +1,1783 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak + * Copyright (C) 2014 Ondrej Oprala + * Copyright (C) 2016 Igor Gnatenko + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table + * @title: Table + * @short_description: container for rows and columns + * + * Table data manipulation API. + */ + + +#include +#include +#include +#include +#include + +#include "nls.h" +#include "ttyutils.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 '- */ + +#define UTF_V3 "\342\224\206" /* U+2506 Triple Dash Vertical | */ +#define UTF_H3 "\342\224\210" /* U+2504 Triple Dash Horizontal - */ +#define UTF_DR "\342\224\214" /* U+250C Down and Right ,- */ +#define UTF_DH "\342\224\254" /* U+252C Down and Horizontal |' */ + +#define UTF_TR "\342\226\266" /* U+25B6 Black Right-Pointing Triangle > */ +#endif /* !HAVE_WIDECHAR */ + +#define is_last_column(_tb, _cl) \ + list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) + + +static void check_padding_debug(struct libscols_table *tb) +{ + const char *str; + + assert(libsmartcols_debug_mask); /* debug has to be already initialized! */ + + str = getenv("LIBSMARTCOLS_DEBUG_PADDING"); + if (!str || (strcmp(str, "on") != 0 && strcmp(str, "1") != 0)) + return; + + DBG(INIT, ul_debugobj(tb, "padding debug: ENABLE")); + tb->padding_debug = 1; +} + +/** + * scols_new_table: + * + * Returns: A newly allocated table. + */ +struct libscols_table *scols_new_table(void) +{ + struct libscols_table *tb; + int c, l; + + tb = calloc(1, sizeof(struct libscols_table)); + if (!tb) + return NULL; + + tb->refcount = 1; + tb->out = stdout; + + get_terminal_dimension(&c, &l); + tb->termwidth = c > 0 ? c : 80; + tb->termheight = l > 0 ? l : 24; + + INIT_LIST_HEAD(&tb->tb_lines); + INIT_LIST_HEAD(&tb->tb_columns); + INIT_LIST_HEAD(&tb->tb_groups); + + DBG(TAB, ul_debugobj(tb, "alloc")); + ON_DBG(INIT, check_padding_debug(tb)); + + return tb; +} + +/** + * scols_ref_table: + * @tb: a pointer to a struct libscols_table instance + * + * Increases the refcount of @tb. + */ +void scols_ref_table(struct libscols_table *tb) +{ + if (tb) + tb->refcount++; +} + +static void scols_table_remove_groups(struct libscols_table *tb) +{ + while (!list_empty(&tb->tb_groups)) { + struct libscols_group *gr = list_entry(tb->tb_groups.next, + struct libscols_group, gr_groups); + scols_group_remove_children(gr); + scols_group_remove_members(gr); + scols_unref_group(gr); + } +} + +/** + * scols_unref_table: + * @tb: a pointer to a struct libscols_table instance + * + * Decreases the refcount of @tb. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_table(struct libscols_table *tb) +{ + if (tb && (--tb->refcount <= 0)) { + DBG(TAB, ul_debugobj(tb, "dealloc <-")); + scols_table_remove_groups(tb); + scols_table_remove_lines(tb); + scols_table_remove_columns(tb); + scols_unref_symbols(tb->symbols); + scols_reset_cell(&tb->title); + free(tb->grpset); + free(tb->linesep); + free(tb->colsep); + free(tb->name); + free(tb); + DBG(TAB, ul_debug("<- done")); + } +} + +/* Private API */ +int scols_table_next_group(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_group **gr) +{ + int rc = 1; + + if (!tb || !itr || !gr) + return -EINVAL; + *gr = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_groups); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *gr, struct libscols_group, gr_groups); + rc = 0; + } + + return rc; +} + +/** + * scols_table_set_name: + * @tb: a pointer to a struct libscols_table instance + * @name: a name + * + * The table name is used for example for JSON top level object name. + * + * Returns: 0, a negative number in case of an error. + * + * Since: 2.27 + */ +int scols_table_set_name(struct libscols_table *tb, const char *name) +{ + return strdup_to_struct_member(tb, name, name); +} + +/** + * scols_table_get_name: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: The current name setting of the table @tb + * + * Since: 2.29 + */ +const char *scols_table_get_name(const struct libscols_table *tb) +{ + return tb->name; +} + +/** + * scols_table_get_title: + * @tb: a pointer to a struct libscols_table instance + * + * The returned pointer is possible to modify by cell functions. Note that + * title output alignment on non-tty is hardcoded to 80 output chars. For the + * regular terminal it's based on terminal width. + * + * Returns: Title of the table, or NULL in case of blank title. + * + * Since: 2.28 + */ +struct libscols_cell *scols_table_get_title(struct libscols_table *tb) +{ + return &tb->title; +} + +/** + * scols_table_add_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * + * Adds @cl to @tb's column list. The column cannot be shared between more + * tables. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_iter itr; + struct libscols_line *ln; + int rc = 0; + + if (!tb || !cl || cl->table) + return -EINVAL; + + if (!list_empty(&cl->cl_columns)) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols++; + + DBG(TAB, ul_debugobj(tb, "add column")); + list_add_tail(&cl->cl_columns, &tb->tb_columns); + cl->seqnum = tb->ncols++; + cl->table = tb; + scols_ref_column(cl); + + if (list_empty(&tb->tb_lines)) + return 0; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + /* Realloc line cell arrays + */ + while (scols_table_next_line(tb, &itr, &ln) == 0) { + rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + break; + } + + return rc; +} + +/** + * scols_table_remove_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * + * Removes @cl from @tb. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_remove_column(struct libscols_table *tb, + struct libscols_column *cl) +{ + if (!tb || !cl || !list_empty(&tb->tb_lines)) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols--; + if (tb->dflt_sort_column == cl) + tb->dflt_sort_column = NULL; + + DBG(TAB, ul_debugobj(tb, "remove column")); + list_del_init(&cl->cl_columns); + tb->ncols--; + cl->table = NULL; + scols_unref_column(cl); + return 0; +} + +/** + * scols_table_remove_columns: + * @tb: a pointer to a struct libscols_table instance + * + * Removes all of @tb's columns. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_remove_columns(struct libscols_table *tb) +{ + if (!tb || !list_empty(&tb->tb_lines)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove all columns")); + 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; +} + +/** + * scols_table_move_column: + * @tb: table + * @pre: column before the column + * @cl: column to move + * + * Move the @cl behind @pre. If the @pre is NULL then the @col is the first + * column in the table. + * + * Since: 2.30 + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_move_column(struct libscols_table *tb, + struct libscols_column *pre, + struct libscols_column *cl) +{ + struct list_head *head; + struct libscols_iter itr; + struct libscols_column *p; + struct libscols_line *ln; + size_t n = 0, oldseq; + + if (!tb || !cl) + return -EINVAL; + + if (pre && pre->seqnum + 1 == cl->seqnum) + return 0; + if (pre == NULL && cl->seqnum == 0) + return 0; + + DBG(TAB, ul_debugobj(tb, "move column %zu behind %zu", + cl->seqnum, pre? pre->seqnum : 0)); + + list_del_init(&cl->cl_columns); /* remove from old position */ + + head = pre ? &pre->cl_columns : &tb->tb_columns; + list_add(&cl->cl_columns, head); /* add to the new place */ + + oldseq = cl->seqnum; + + /* fix seq. numbers */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &p) == 0) + p->seqnum = n++; + + /* move data in lines */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + scols_line_move_cells(ln, cl->seqnum, oldseq); + return 0; +} + +/** + * scols_table_new_column: + * @tb: table + * @name: column header + * @whint: column width hint (absolute width: N > 1; relative width: 0 < N < 1) + * @flags: flags integer + * + * 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: + * + * @whint: 0 < N < 1 : relative width, percent of terminal width + * + * @whint: N >= 1 : absolute width, empty column will be truncated to + * the column header width if no specified STRICTWIDTH flag + * + * Note that if table has disabled "maxout" flag (disabled by default) than + * relative width is used as a hint only. It's possible that column will be + * narrow if the specified size is too large for column data. + * + * + * If the width of all columns is greater than terminal width then library + * tries to reduce width of the individual columns. It's done in three stages: + * + * #1 reduce columns with SCOLS_FL_TRUNC flag and with relative width if the + * width is greater than width defined by @whint (@whint * terminal_width) + * + * #2 reduce all columns with SCOLS_FL_TRUNC flag + * + * #3 reduce all columns with relative width + * + * The next stage is always used if the previous stage is unsuccessful. Note + * that SCOLS_FL_WRAP is interpreted as SCOLS_FL_TRUNC when calculate column + * width (if custom wrap function is not specified), but the final text is not + * truncated, but wrapped to multi-line cell. + * + * + * The column is necessary to address 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; + + if (!tb) + return NULL; + + DBG(TAB, ul_debugobj(tb, "new column name=%s, whint=%g, flags=0x%04x", + name, whint, flags)); + cl = scols_new_column(); + if (!cl) + return NULL; + + if (name && scols_column_set_name(cl, name)) + goto err; + scols_column_set_whint(cl, whint); + scols_column_set_flags(cl, flags); + + 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; +} + +/** + * scols_table_next_column: + * @tb: a pointer to a struct libscols_table instance + * @itr: a pointer to a struct libscols_iter instance + * @cl: a pointer to a pointer to a struct libscols_column instance + * + * Returns the next column of @tb via @cl. + * + * Returns: 0, a negative value in case of an error. + */ +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; +} + +/** + * scols_table_set_columns_iter: + * @tb: tab pointer + * @itr: iterator + * @cl: tab entry + * + * Sets @iter to the position of @cl in the file @tb. + * + * Returns: 0 on success, negative number in case of error. + * + * Since: 2.35 + */ +int scols_table_set_columns_iter( + struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_column *cl) +{ + if (!tb || !itr || !cl) + return -EINVAL; + + if (cl->table != tb) + return -EINVAL; + + SCOLS_ITER_INIT(itr, &tb->tb_columns); + itr->p = &cl->cl_columns; + + return 0; +} + +/** + * scols_table_get_ncols: + * @tb: table + * + * Returns: the ncols table member. + */ +size_t scols_table_get_ncols(const struct libscols_table *tb) +{ + return tb->ncols; +} + +/** + * scols_table_get_nlines: + * @tb: table + * + * Returns: the nlines table member. + */ +size_t scols_table_get_nlines(const struct libscols_table *tb) +{ + return tb->nlines; +} + +/** + * scols_table_set_stream: + * @tb: table + * @stream: output stream + * + * Sets the output stream for table @tb. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_set_stream(struct libscols_table *tb, FILE *stream) +{ + assert(tb); + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "setting alternative stream")); + tb->out = stream; + return 0; +} + +/** + * scols_table_get_stream: + * @tb: table + * + * Gets the output stream for table @tb. + * + * Returns: stream pointer, NULL in case of an error or an unset stream. + */ +FILE *scols_table_get_stream(const struct libscols_table *tb) +{ + return tb->out; +} + +/** + * scols_table_reduce_termwidth: + * @tb: table + * @reduce: width + * + * If necessary then libsmartcols use all terminal width, the @reduce setting + * provides extra space (for example for borders in ncurses applications). + * + * The @reduce must be smaller than terminal width, otherwise it's silently + * ignored. The reduction is not applied when STDOUT_FILENO is not terminal. + * + * Note that after output initialization (scols_table_print_* calls) the width + * will be reduced, this behavior affects subsequenced scols_table_get_termwidth() + * calls. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "reduce terminal width: %zu", reduce)); + tb->termreduce = reduce; + return 0; +} + +/** + * scols_table_get_column: + * @tb: table + * @n: 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; + + 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; +} + +/** + * scols_table_get_column_ny_name + * @tb: table + * @name: column name + * + * Returns: pointer to column or NULL + * + * Since: 2.39 + */ +struct libscols_column *scols_table_get_column_by_name( + struct libscols_table *tb, const char *name) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + if (!tb || !name) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + const char *cn = scols_column_get_name(cl); + + if (cn && strcmp(cn, name) == 0) + return cl; + } + return NULL; +} + + +/** + * scols_table_add_line: + * @tb: table + * @ln: line + * + * Note that this function calls scols_line_alloc_cells() if number + * of the cells in the line is too small for @tb. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) +{ + if (!tb || !ln) + return -EINVAL; + + if (!list_empty(&ln->ln_lines)) + return -EINVAL; + + if (tb->ncols > ln->ncells) { + int rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + return rc; + } + + DBG(TAB, ul_debugobj(tb, "add line")); + list_add_tail(&ln->ln_lines, &tb->tb_lines); + ln->seqnum = tb->nlines++; + scols_ref_line(ln); + return 0; +} + +/** + * scols_table_remove_line: + * @tb: table + * @ln: line + * + * Note that this function does not destroy the parent<->child relationship between lines. + * You have to call scols_line_remove_child() + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_remove_line(struct libscols_table *tb, + struct libscols_line *ln) +{ + if (!tb || !ln) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove line")); + list_del_init(&ln->ln_lines); + tb->nlines--; + scols_unref_line(ln); + return 0; +} + +/** + * scols_table_remove_lines: + * @tb: table + * + * This empties the table and also destroys all the parent<->child relationships. + */ +void scols_table_remove_lines(struct libscols_table *tb) +{ + if (!tb) + return; + + DBG(TAB, ul_debugobj(tb, "remove all lines")); + 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); + } +} + +/** + * scols_table_next_line: + * @tb: a pointer to a struct libscols_table instance + * @itr: a pointer to a struct libscols_iter instance + * @ln: a pointer to a pointer to a struct libscols_line instance + * + * Finds the next line and returns a pointer to it via @ln. + * + * Returns: 0, a negative value in case of an error. + */ +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; +} + +/** + * scols_table_new_line: + * @tb: table + * @parent: parental line or NULL + * + * This is shortcut for + * + * ln = scols_new_line(); + * scols_table_add_line(tb, ln); + * scols_line_add_child(parent, ln); + * + * + * Returns: newly allocate line + */ +struct libscols_line *scols_table_new_line(struct libscols_table *tb, + struct libscols_line *parent) +{ + struct libscols_line *ln; + + if (!tb) + 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; +} + +/** + * scols_table_get_line: + * @tb: table + * @n: column number (0..N) + * + * Returns: a line or NULL + */ +struct libscols_line *scols_table_get_line(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_line *ln; + + 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; +} + +/** + * scols_copy_table: + * @tb: table + * + * Creates a new independent table copy, except struct libscols_symbols that + * are shared between the tables. + * + * Returns: a newly allocated copy of @tb + */ +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; + + if (!tb) + return NULL; + ret = scols_new_table(); + if (!ret) + return NULL; + + DBG(TAB, ul_debugobj(tb, "copy")); + + if (tb->symbols) + scols_table_set_symbols(ret, tb->symbols); + + /* 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); + } + + /* separators */ + if (scols_table_set_column_separator(ret, tb->colsep) || + scols_table_set_line_separator(ret, tb->linesep)) + goto err; + + return ret; +err: + scols_unref_table(ret); + return NULL; +} + +/** + * scols_table_set_default_symbols: + * @tb: table + * + * The library check the current environment to select ASCII or UTF8 symbols. + * This default behavior could be controlled by scols_table_enable_ascii(). + * + * Use scols_table_set_symbols() to unset symbols or use your own setting. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_default_symbols(struct libscols_table *tb) +{ + struct libscols_symbols *sy; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "setting default symbols")); + + sy = scols_new_symbols(); + if (!sy) + return -ENOMEM; + +#if defined(HAVE_WIDECHAR) + if (!scols_table_is_ascii(tb) && + !strcmp(nl_langinfo(CODESET), "UTF-8")) { + /* tree chart */ + scols_symbols_set_branch(sy, UTF_VR UTF_H); + scols_symbols_set_vertical(sy, UTF_V " "); + scols_symbols_set_right(sy, UTF_UR UTF_H); + /* groups chart */ + scols_symbols_set_group_horizontal(sy, UTF_H3); + scols_symbols_set_group_vertical(sy, UTF_V3); + + scols_symbols_set_group_first_member(sy, UTF_DR UTF_H3 UTF_TR); + scols_symbols_set_group_last_member(sy, UTF_UR UTF_DH UTF_TR); + scols_symbols_set_group_middle_member(sy, UTF_VR UTF_H3 UTF_TR); + scols_symbols_set_group_last_child(sy, UTF_UR UTF_H3); + scols_symbols_set_group_middle_child(sy, UTF_VR UTF_H3); + } else +#endif + { + /* tree chart */ + scols_symbols_set_branch(sy, "|-"); + scols_symbols_set_vertical(sy, "| "); + scols_symbols_set_right(sy, "`-"); + /* groups chart */ + scols_symbols_set_group_horizontal(sy, "-"); + scols_symbols_set_group_vertical(sy, "|"); + + scols_symbols_set_group_first_member(sy, ",->"); + scols_symbols_set_group_last_member(sy, "'->"); + scols_symbols_set_group_middle_member(sy, "|->"); + scols_symbols_set_group_last_child(sy, "`-"); + scols_symbols_set_group_middle_child(sy, "|-"); + } + scols_symbols_set_title_padding(sy, " "); + scols_symbols_set_cell_padding(sy, " "); + + rc = scols_table_set_symbols(tb, sy); + scols_unref_symbols(sy); + return rc; +} + + +/** + * scols_table_set_symbols: + * @tb: table + * @sy: symbols or NULL + * + * Add a reference to @sy from the table. The symbols are used by library to + * draw tree output. If no symbols are used for the table then library creates + * default temporary symbols to draw output by scols_table_set_default_symbols(). + * + * If @sy is NULL then remove reference from the currently used symbols. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_symbols(struct libscols_table *tb, + struct libscols_symbols *sy) +{ + if (!tb) + return -EINVAL; + + /* remove old */ + if (tb->symbols) { + DBG(TAB, ul_debugobj(tb, "remove symbols reference")); + scols_unref_symbols(tb->symbols); + tb->symbols = NULL; + } + + /* set new */ + if (sy) { /* ref user defined */ + DBG(TAB, ul_debugobj(tb, "set symbols")); + tb->symbols = sy; + scols_ref_symbols(sy); + } + return 0; +} + +/** + * scols_table_get_symbols: + * @tb: table + * + * Returns: pointer to symbols table. + * + * Since: 2.29 + */ +struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb) +{ + return tb->symbols; +} + +/** + * scols_table_enable_nolinesep: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable line separator printing. This is useful if you want to + * re-printing the same line more than once (e.g. progress bar). Don't use it + * if you're not sure. + * + * Note that for the last line in the table the separator is disabled at all. + * The library differentiate between table terminator and line terminator + * (although for standard output \n byte is used in both cases). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_nolinesep(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "nolinesep: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_linesep = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_nolinesep: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if line separator printing is disabled. + * + * Since: 2.29 + */ +int scols_table_is_nolinesep(const struct libscols_table *tb) +{ + return tb->no_linesep; +} + +/** + * scols_table_enable_colors: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable colors. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_colors(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "colors: %s", enable ? "ENABLE" : "DISABLE")); + tb->colors_wanted = enable; + return 0; +} + +/** + * scols_table_enable_raw: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable raw output format. The parsable output formats + * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_raw(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "raw: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_RAW; + else if (tb->format == SCOLS_FMT_RAW) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_json: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable JSON output format. The parsable output formats + * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.27 + */ +int scols_table_enable_json(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "json: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_JSON; + else if (tb->format == SCOLS_FMT_JSON) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_export: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable export output format (COLUMNAME="value" ...). + * The parsable output formats (export and raw) are mutually exclusive. + * + * See also scols_table_enable_shellvar(). Note that in version 2.37 (and only + * in this version) scols_table_enable_shellvar() functionality has been + * automatically enabled for "export" format. This behavior has been reverted + * in version 2.38 due to backward compatibility issues. Now it's necessary to + * explicitly call scols_table_enable_shellvar(). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_export(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "export: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_EXPORT; + else if (tb->format == SCOLS_FMT_EXPORT) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_shellvar: + * @tb: table + * @enable: 1 or 0 + * + * Force library to print column names to be compatible with shell requirements + * to variable names. For example "1FOO%" will be printed as "_1FOO_PCT". + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.38 + */ +int scols_table_enable_shellvar(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "shellvar: %s", enable ? "ENABLE" : "DISABLE")); + tb->is_shellvar = enable ? 1 : 0; + return 0; +} + + +/** + * scols_table_enable_ascii: + * @tb: table + * @enable: 1 or 0 + * + * The ASCII-only output is relevant for tree-like outputs. The library + * checks if the current environment is UTF8 compatible by default. This + * function overrides this check and force the library to use ASCII chars + * for the tree. + * + * If a custom libcols_symbols are specified (see scols_table_set_symbols() + * then ASCII flag setting is ignored. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_ascii(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "ascii: %s", enable ? "ENABLE" : "DISABLE")); + tb->ascii = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_noheadings: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable header line. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_noheadings(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "noheading: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_headings = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_header_repeat: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable header line repeat. The header line is printed only once by + * default. Note that the flag will be silently ignored and disabled if the + * output is not on terminal or output format is JSON, raw, etc. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.31 + */ +int scols_table_enable_header_repeat(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "header-repeat: %s", enable ? "ENABLE" : "DISABLE")); + tb->header_repeat = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_maxout: + * @tb: table + * @enable: 1 or 0 + * + * The extra space after last column is ignored by default. The output + * maximization add padding for all columns. + * + * This setting is mutually exclusive to scols_table_enable_minout(). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_maxout(struct libscols_table *tb, int enable) +{ + if (!tb || tb->minout) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "maxout: %s", enable ? "ENABLE" : "DISABLE")); + tb->maxout = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_minout: + * @tb: table + * @enable: 1 or 0 + * + * Force library to terminate line after last column with data. The extra + * padding is not added to the empty cells at the end of the line. The default is fill + * tailing empty cells except the last line cell. + * + * This setting is mutually exclusive to scols_table_enable_maxout(). + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.35 + */ +int scols_table_enable_minout(struct libscols_table *tb, int enable) +{ + if (!tb || tb->maxout) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "minout: %s", enable ? "ENABLE" : "DISABLE")); + tb->minout = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_nowrap: + * @tb: table + * @enable: 1 or 0 + * + * Never continue on next line, remove last column(s) when too large, truncate last column. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.28 + */ +int scols_table_enable_nowrap(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "nowrap: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_wrap = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_nowrap: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if nowrap is enabled. + * + * Since: 2.29 + */ +int scols_table_is_nowrap(const struct libscols_table *tb) +{ + return tb->no_wrap; +} + +/** + * scols_table_enable_noencoding: + * @tb: table + * @enable: 1 or 0 + * + * The library encode non-printable and control chars by \xHEX by default. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.31 + */ +int scols_table_enable_noencoding(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "encoding: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_encode = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_noencoding: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if encoding is disabled. + * + * Since: 2.31 + */ +int scols_table_is_noencoding(const struct libscols_table *tb) +{ + return tb->no_encode; +} + +/** + * scols_table_colors_wanted: + * @tb: table + * + * Returns: 1 if colors are enabled. + */ +int scols_table_colors_wanted(const struct libscols_table *tb) +{ + return tb->colors_wanted; +} + +/** + * scols_table_is_empty: + * @tb: table + * + * Returns: 1 if the table is empty. + */ +int scols_table_is_empty(const struct libscols_table *tb) +{ + return !tb->nlines; +} + +/** + * scols_table_is_ascii: + * @tb: table + * + * Returns: 1 if ASCII tree is enabled. + */ +int scols_table_is_ascii(const struct libscols_table *tb) +{ + return tb->ascii; +} + +/** + * scols_table_is_noheadings: + * @tb: table + * + * Returns: 1 if header output is disabled. + */ +int scols_table_is_noheadings(const struct libscols_table *tb) +{ + return tb->no_headings; +} + +/** + * scols_table_is_header_repeat + * @tb: table + * + * Returns: 1 if header repeat is enabled. + * + * Since: 2.31 + */ +int scols_table_is_header_repeat(const struct libscols_table *tb) +{ + return tb->header_repeat; +} + +/** + * scols_table_is_export: + * @tb: table + * + * Returns: 1 if export output format is enabled. + */ +int scols_table_is_export(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_EXPORT; +} + +/** + * scols_table_is_shellvar: + * @tb: table + * + * Returns: 1 if column names has to be compatible with shell requirements + * to variable names + * + * Since: 2.38 + */ +int scols_table_is_shellvar(const struct libscols_table *tb) +{ + return tb->is_shellvar; +} + +/** + * scols_table_is_raw: + * @tb: table + * + * Returns: 1 if raw output format is enabled. + */ +int scols_table_is_raw(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_RAW; +} + +/** + * scols_table_is_json: + * @tb: table + * + * Returns: 1 if JSON output format is enabled. + * + * Since: 2.27 + */ +int scols_table_is_json(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_JSON; +} + +/** + * scols_table_is_maxout + * @tb: table + * + * Returns: 1 if output maximization is enabled or 0 + */ +int scols_table_is_maxout(const struct libscols_table *tb) +{ + return tb->maxout; +} + +/** + * scols_table_is_minout + * @tb: table + * + * Returns: 1 if output minimization is enabled or 0 + * + * Since: 2.35 + */ +int scols_table_is_minout(const struct libscols_table *tb) +{ + return tb->minout; +} + +/** + * scols_table_is_tree: + * @tb: table + * + * Returns: returns 1 tree-like output is expected. + */ +int scols_table_is_tree(const struct libscols_table *tb) +{ + return tb->ntreecols > 0; +} + +/** + * scols_table_set_column_separator: + * @tb: table + * @sep: separator + * + * Sets the column separator of @tb to @sep. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_column_separator(struct libscols_table *tb, const char *sep) +{ + return strdup_to_struct_member(tb, colsep, sep); +} + +/** + * scols_table_set_line_separator: + * @tb: table + * @sep: separator + * + * Sets the line separator of @tb to @sep. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_line_separator(struct libscols_table *tb, const char *sep) +{ + return strdup_to_struct_member(tb, linesep, sep); +} + +/** + * scols_table_get_column_separator: + * @tb: table + * + * Returns: @tb column separator, NULL in case of an error + */ +const char *scols_table_get_column_separator(const struct libscols_table *tb) +{ + return tb->colsep; +} + +/** + * scols_table_get_line_separator: + * @tb: table + * + * Returns: @tb line separator, NULL in case of an error + */ +const char *scols_table_get_line_separator(const struct libscols_table *tb) +{ + return tb->linesep; +} +/* for lines in the struct libscols_line->ln_lines list */ +static int cells_cmp_wrapper_lines(struct list_head *a, struct list_head *b, void *data) +{ + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; + struct libscols_cell *ca, *cb; + + assert(a); + assert(b); + assert(cl); + + ra = list_entry(a, struct libscols_line, ln_lines); + rb = list_entry(b, struct libscols_line, ln_lines); + ca = scols_line_get_cell(ra, cl->seqnum); + cb = scols_line_get_cell(rb, cl->seqnum); + + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); +} + +/* for lines in the struct libscols_line->ln_children list */ +static int cells_cmp_wrapper_children(struct list_head *a, struct list_head *b, void *data) +{ + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; + struct libscols_cell *ca, *cb; + + assert(a); + assert(b); + assert(cl); + + ra = list_entry(a, struct libscols_line, ln_children); + rb = list_entry(b, struct libscols_line, ln_children); + ca = scols_line_get_cell(ra, cl->seqnum); + cb = scols_line_get_cell(rb, cl->seqnum); + + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); +} + + +static int sort_line_children(struct libscols_line *ln, struct libscols_column *cl) +{ + struct list_head *p; + + if (!list_empty(&ln->ln_branch)) { + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } + + list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl); + } + + if (is_first_group_member(ln)) { + list_for_each(p, &ln->group->gr_children) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } + + list_sort(&ln->group->gr_children, cells_cmp_wrapper_children, cl); + } + + return 0; +} + +static int __scols_sort_tree(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + if (!tb || !cl || !cl->cmpfunc) + return -EINVAL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + sort_line_children(ln, cl); + return 0; +} + +/** + * scols_sort_table: + * @tb: table + * @cl: order by this column or NULL + * + * Orders the table by the column. See also scols_column_set_cmpfunc(). If the + * tree output is enabled then children in the tree are recursively sorted too. + * + * The column @cl is saved as the default sort column to the @tb and the next time + * is possible to call scols_sort_table(tb, NULL). The saved column is also used by + * scols_sort_table_by_tree(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl) +{ + if (!tb) + return -EINVAL; + if (!cl) + cl = tb->dflt_sort_column; + if (!cl || !cl->cmpfunc) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table by %zu column", cl->seqnum)); + list_sort(&tb->tb_lines, cells_cmp_wrapper_lines, cl); + + if (scols_table_is_tree(tb)) + __scols_sort_tree(tb, cl); + + if (cl && cl != tb->dflt_sort_column) + tb->dflt_sort_column = cl; + + return 0; +} + +/* + * Move all @ln's children after @ln in the table. + */ +static struct libscols_line *move_line_and_children(struct libscols_line *ln, struct libscols_line *pre) +{ + if (pre) { + list_del_init(&ln->ln_lines); /* remove from old position */ + list_add(&ln->ln_lines, &pre->ln_lines); /* add to the new place (after @pre) */ + } + pre = ln; + + if (!list_empty(&ln->ln_branch)) { + struct list_head *p; + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + pre = move_line_and_children(chld, pre); + } + } + + return pre; +} + +/** + * scols_sort_table_by_tree: + * @tb: table + * + * Reorders lines in the table by parent->child relation. Note that order of + * the lines in the table is independent on the tree hierarchy by default. + * + * The children of the lines are sorted according to the default sort column + * if scols_sort_table() has been previously called. + * + * Since: 2.30 + * + * Returns: 0, a negative value in case of an error. + */ +int scols_sort_table_by_tree(struct libscols_table *tb) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table by tree")); + + if (tb->dflt_sort_column) + __scols_sort_tree(tb, tb->dflt_sort_column); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + move_line_and_children(ln, NULL); + + return 0; +} + + +/** + * scols_table_set_termforce: + * @tb: table + * @force: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} + * + * Forces library to use stdout as terminal, non-terminal or use automatic + * detection (default). + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_termforce(struct libscols_table *tb, int force) +{ + if (!tb) + return -EINVAL; + tb->termforce = force; + return 0; +} + +/** + * scols_table_get_termforce: + * @tb: table + * + * Returns: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} or a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_get_termforce(const struct libscols_table *tb) +{ + return tb->termforce; +} + +/** + * scols_table_set_termwidth + * @tb: table + * @width: terminal width + * + * The library automatically detects terminal width or defaults to 80 chars if + * detections is unsuccessful. This function override this behaviour. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_termwidth(struct libscols_table *tb, size_t width) +{ + DBG(TAB, ul_debugobj(tb, "set terminatl width: %zu", width)); + tb->termwidth = width; + return 0; +} + +/** + * scols_table_get_termwidth + * @tb: table + * + * Returns: terminal width. + */ +size_t scols_table_get_termwidth(const struct libscols_table *tb) +{ + return tb->termwidth; +} + +/** + * scols_table_set_termheight + * @tb: table + * @height: terminal height (number of lines) + * + * The library automatically detects terminal height or defaults to 24 lines if + * detections is unsuccessful. This function override this behaviour. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.31 + */ +int scols_table_set_termheight(struct libscols_table *tb, size_t height) +{ + DBG(TAB, ul_debugobj(tb, "set terminatl height: %zu", height)); + tb->termheight = height; + return 0; +} + +/** + * scols_table_get_termheight + * @tb: table + * + * Returns: terminal height (number of lines). + * + * Since: 2.31 + */ +size_t scols_table_get_termheight(const struct libscols_table *tb) +{ + return tb->termheight; +} diff --git a/libsmartcols/src/version.c b/libsmartcols/src/version.c new file mode 100644 index 0000000..e592ccc --- /dev/null +++ b/libsmartcols/src/version.c @@ -0,0 +1,62 @@ +/* + * version.c - Return the version of the library + * + * Copyright (C) 2014 Karel Zak + * + * See COPYING.libmount for the License of this software. + */ + +/** + * SECTION: version-utils + * @title: Version functions + * @short_description: functions to get the library version. + * + * Note that library version is not the same thing as SONAME version. The + * libsmarcols uses symbols versioning and SONAME is not modified for releases. + * + * The library version and symbols version follow util-linux package versioning. + */ + +#include + +#include "smartcolsP.h" + +static const char *lib_version = LIBSMARTCOLS_VERSION; + +/** + * scols_parse_version_string: + * @ver_string: version string (e.g "2.18.0") + * + * Returns: release version code. + */ +int scols_parse_version_string(const char *ver_string) +{ + const char *cp; + int version = 0; + + assert(ver_string); + + for (cp = ver_string; *cp; cp++) { + if (*cp == '.') + continue; + if (!isdigit(*cp)) + break; + version = (version * 10) + (*cp - '0'); + } + return version; +} + +/** + * scols_get_library_version: + * @ver_string: return pointer to the static library version string if not NULL + * + * Returns: release version number. + */ +int scols_get_library_version(const char **ver_string) +{ + if (ver_string) + *ver_string = lib_version; + + return scols_parse_version_string(lib_version); +} + diff --git a/libsmartcols/src/walk.c b/libsmartcols/src/walk.c new file mode 100644 index 0000000..0b51fed --- /dev/null +++ b/libsmartcols/src/walk.c @@ -0,0 +1,152 @@ +#include "smartcolsP.h" + +static int walk_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + int (*callback)(struct libscols_table *, + struct libscols_line *, + struct libscols_column *, + void *), + void *data) +{ + int rc = 0; + +/* DBG(LINE, ul_debugobj(ln, " wall line")); */ + + /* we list group children in __scols_print_tree() after tree root node */ + if (is_group_member(ln) && is_last_group_member(ln) && has_group_children(ln)) + tb->ngrpchlds_pending++; + + if (has_groups(tb)) + rc = scols_groups_update_grpset(tb, ln); + if (rc == 0) + rc = callback(tb, ln, cl, data); + + /* children */ + if (rc == 0 && has_children(ln)) { + struct list_head *p; + +/* DBG(LINE, ul_debugobj(ln, " children walk"));*/ + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = list_entry(p, + struct libscols_line, ln_children); + + rc = walk_line(tb, chld, cl, callback, data); + if (rc) + break; + } + } + +/* DBG(LINE, ul_debugobj(ln, "<- walk line done [rc=%d]", rc)); */ + return rc; +} + +/* last line in the tree? */ +int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln) +{ + if (tb->walk_last_done == 0) + return 0; + if (tb->ngrpchlds_pending > 0) + return 0; + if (has_children(ln)) + return 0; + if (is_tree_root(ln) && !is_last_tree_root(tb, ln)) + return 0; + if (is_group_member(ln) && (!is_last_group_member(ln) || has_group_children(ln))) + return 0; + if (is_child(ln)) { + struct libscols_line *parent = ln->parent; + + if (!is_last_child(ln)) + return 0; + while (parent) { + if (is_child(parent) && !is_last_child(parent)) + return 0; + if (!parent->parent) + break; + parent = parent->parent; + } + if (is_tree_root(parent) && !is_last_tree_root(tb, parent)) + return 0; + } + if (is_group_child(ln) && !is_last_group_child(ln)) + return 0; + + DBG(LINE, ul_debugobj(ln, "last in table")); + return 1; +} + +int scols_walk_tree(struct libscols_table *tb, + struct libscols_column *cl, + int (*callback)(struct libscols_table *, + struct libscols_line *, + struct libscols_column *, + void *), + void *data) +{ + int rc = 0; + struct libscols_line *ln; + struct libscols_iter itr; + + assert(tb); +/* DBG(TAB, ul_debugobj(tb, ">> walk start"));*/ + + /* init */ + tb->ngrpchlds_pending = 0; + tb->walk_last_tree_root = NULL; + tb->walk_last_done = 0; + + if (has_groups(tb)) + scols_groups_reset_state(tb); + + /* set pointer to last tree root */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (!tb->walk_last_tree_root) + tb->walk_last_tree_root = ln; + if (is_child(ln) || is_group_child(ln)) + continue; + tb->walk_last_tree_root = ln; + } + + /* walk */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + + if (tb->walk_last_tree_root == ln) + tb->walk_last_done = 1; + rc = walk_line(tb, ln, cl, callback, data); + + /* walk group's children */ + while (rc == 0 && tb->ngrpchlds_pending) { + struct libscols_group *gr = scols_grpset_get_printable_children(tb); + struct list_head *p; + + DBG(LINE, ul_debugobj(ln, " walk group children [pending=%zu]", tb->ngrpchlds_pending)); + if (!gr) { + DBG(LINE, ul_debugobj(ln, " *** ngrpchlds_pending counter invalid")); + tb->ngrpchlds_pending = 0; + break; + } + + tb->ngrpchlds_pending--; + + list_for_each(p, &gr->gr_children) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + + rc = walk_line(tb, chld, cl, callback, data); + if (rc) + break; + } + } + } + + tb->ngrpchlds_pending = 0; + tb->walk_last_done = 0; +/* DBG(TAB, ul_debugobj(tb, "<< walk end [rc=%d]", rc));*/ + return rc; +} -- cgit v1.2.3