summaryrefslogtreecommitdiffstats
path: root/libsmartcols/src/table.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libsmartcols/src/table.c1542
1 files changed, 1542 insertions, 0 deletions
diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c
new file mode 100644
index 0000000..3914c88
--- /dev/null
+++ b/libsmartcols/src/table.c
@@ -0,0 +1,1542 @@
+/*
+ * table.c - functions handling the data at the table level
+ *
+ * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com>
+ *
+ * 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <ctype.h>
+
+#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 */
+#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 enabled! */
+
+ 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);
+
+ 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++;
+}
+
+/**
+ * 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_lines(tb);
+ scols_table_remove_columns(tb);
+ scols_unref_symbols(tb->symbols);
+ scols_reset_cell(&tb->title);
+ free(tb->linesep);
+ free(tb->colsep);
+ free(tb->name);
+ free(tb);
+ }
+}
+
+/**
+ * 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 (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--;
+
+ 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;
+ struct libscols_cell *hr;
+
+ if (!tb)
+ return NULL;
+
+ DBG(TAB, ul_debugobj(tb, "new column name=%s, whint=%g, flags=%d",
+ name, whint, flags));
+ cl = scols_new_column();
+ if (!cl)
+ return NULL;
+
+ /* set column name */
+ hr = scols_column_get_header(cl);
+ if (!hr)
+ goto err;
+ if (scols_cell_set_data(hr, name))
+ goto err;
+
+ scols_column_set_whint(cl, whint);
+ scols_column_set_flags(cl, flags);
+
+ if (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_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_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 (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")) {
+ 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);
+ } else
+#endif
+ {
+ scols_symbols_set_branch(sy, "|-");
+ scols_symbols_set_vertical(sy, "| ");
+ scols_symbols_set_right(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.
+ *
+ * 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_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 use the extra space for all columns.
+ *
+ * Returns: 0 on success, negative number in case of an error.
+ */
+int scols_table_enable_maxout(struct libscols_table *tb, int enable)
+{
+ if (!tb)
+ return -EINVAL;
+ DBG(TAB, ul_debugobj(tb, "maxout: %s", enable ? "ENABLE" : "DISABLE"));
+ tb->maxout = 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_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_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))
+ return 0;
+
+ 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);
+ return 0;
+}
+
+/**
+ * scols_sort_table:
+ * @tb: table
+ * @cl: order by this column
+ *
+ * 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.
+ *
+ * Returns: 0, a negative value in case of an error.
+ */
+int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl)
+{
+ if (!tb || !cl || !cl->cmpfunc)
+ return -EINVAL;
+
+ DBG(TAB, ul_debugobj(tb, "sorting table"));
+ list_sort(&tb->tb_lines, cells_cmp_wrapper_lines, cl);
+
+ if (scols_table_is_tree(tb)) {
+ struct libscols_line *ln;
+ struct libscols_iter itr;
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0)
+ sort_line_children(ln, cl);
+ }
+
+ return 0;
+}
+
+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 (behind @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.
+ *
+ * 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"));
+
+ scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+ while (scols_table_next_line(tb, &itr, &ln) == 0) {
+ if (ln->parent)
+ continue;
+
+ 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;
+}