summaryrefslogtreecommitdiffstats
path: root/libsmartcols/src/print.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsmartcols/src/print.c')
-rw-r--r--libsmartcols/src/print.c1243
1 files changed, 1243 insertions, 0 deletions
diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c
new file mode 100644
index 0000000..906df7e
--- /dev/null
+++ b/libsmartcols/src/print.c
@@ -0,0 +1,1243 @@
+ /* print.c - functions to print table
+ *
+ * Copyright (C) 2010-2014 Karel Zak <kzak@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_print
+ * @title: Table print
+ * @short_description: output functions
+ *
+ * Table output API.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <ctype.h>
+
+#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;
+}
+