From d94c519877278124691be5ea3c8dbc729c1962e5 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Wed, 10 Feb 2016 15:25:38 +0100 Subject: [PATCH] libsmartcols: support multi-line cells The initial implementation has been introduced by SCOLS_FL_WRAP columns, but this patch clean ups all and makes things more elegant. Note that use SCOLS_FL_TREE | SCOLS_FL_WRAP for a column is bad idea and I don't think we need to fix it. References: https://github.com/karelzak/util-linux/issues/269 Signed-off-by: Karel Zak --- lib/mbsalign.c | 3 +- libsmartcols/src/column.c | 1 + libsmartcols/src/libsmartcols.h.in | 2 +- libsmartcols/src/smartcolsP.h | 4 + libsmartcols/src/table_print.c | 209 +++++++++++++++++++++++------ 5 files changed, 176 insertions(+), 43 deletions(-) diff --git a/lib/mbsalign.c b/lib/mbsalign.c index a9cf845215..2a8de2f59f 100644 --- a/lib/mbsalign.c +++ b/lib/mbsalign.c @@ -246,6 +246,7 @@ wc_truncate (wchar_t *wc, size_t width) } if (cells + next_cells > width) break; + cells += next_cells; wc++; } @@ -290,7 +291,7 @@ mbs_truncate(char *str, size_t *width) if (sz == (ssize_t) -1) goto done; - wcs = malloc((sz + 1) * sizeof(wchar_t)); + wcs = calloc(1, (sz + 1) * sizeof(wchar_t)); if (!wcs) goto done; diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c index ce589684ef..55c24da221 100644 --- a/libsmartcols/src/column.c +++ b/libsmartcols/src/column.c @@ -70,6 +70,7 @@ void scols_unref_column(struct libscols_column *cl) list_del(&cl->cl_columns); scols_reset_cell(&cl->header); free(cl->color); + free(cl->pending_data_buf); free(cl); } } diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in index b98f406a71..213d50af2d 100644 --- a/libsmartcols/src/libsmartcols.h.in +++ b/libsmartcols/src/libsmartcols.h.in @@ -84,7 +84,7 @@ enum { 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 cells across lines */ + SCOLS_FL_WRAP = (1 << 6), /* wrap long lines to multi-line cells */ }; /* diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h index 7fe6db4671..5add218498 100644 --- a/libsmartcols/src/smartcolsP.h +++ b/libsmartcols/src/smartcolsP.h @@ -84,6 +84,10 @@ struct libscols_column { int is_extreme; char *color; /* default column color */ + char *pending_data; + size_t pending_data_sz; + char *pending_data_buf; + int (*cmpfunc)(struct libscols_cell *, struct libscols_cell *, void *); /* cells comparison function */ diff --git a/libsmartcols/src/table_print.c b/libsmartcols/src/table_print.c index ec9c616509..a726bb9f53 100644 --- a/libsmartcols/src/table_print.c +++ b/libsmartcols/src/table_print.c @@ -194,6 +194,22 @@ static int is_last_column(struct libscols_table *tb, struct libscols_column *cl) #define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") #define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") + +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; +} + /* print padding or asci-art instead of data of @cl */ static void print_empty_cell(struct libscols_table *tb, struct libscols_column *cl, @@ -205,7 +221,7 @@ static void print_empty_cell(struct libscols_table *tb, /* generate tree asci-art rather than padding */ if (ln && scols_column_is_tree(cl)) { if (!ln->parent) { - /* only print symbols->vert if followed by something */ + /* only print symbols->vert if followed by child */ if (!list_empty(&ln->ln_branch)) { fputs(tb->symbols->vert, tb->out); len_pad = mbs_safe_width(tb->symbols->vert); @@ -218,6 +234,8 @@ static void print_empty_cell(struct libscols_table *tb, if (art) { /* whatever the rc, len_pad will be sensible */ line_ascii_art_to_buffer(tb, ln, art); + if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) + buffer_append_data(art, tb->symbols->vert); data = buffer_get_safe_data(art, &len_pad); if (data && len_pad) fputs(data, tb->out); @@ -230,6 +248,25 @@ static void print_empty_cell(struct libscols_table *tb, fputc(' ', tb->out); } + +static const char *get_cell_color(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) /* optional */ +{ + const char *color = NULL; + + if (tb && tb->colors_wanted) { + if (ce && !color) + color = ce->color; + if (ln && !color) + color = ln->color; + if (!color) + color = cl->color; + } + return color; +} + /* 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 @@ -259,6 +296,98 @@ static void print_newline_padding(struct libscols_table *tb, print_empty_cell(tb, scols_table_get_column(tb, i), ln, bufsz); } +/* + * 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) { + 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) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + size_t width = cl->width, bytes; + size_t len = width, i; + char *data; + + if (!cl->pending_data) + return 0; + + DBG(COL, ul_debugobj(cl, "printing pending data")); + + data = strdup(cl->pending_data); + if (!data) + goto err; + bytes = mbs_truncate(data, &len); + if (bytes == (size_t) -1) + goto err; + + step_pending_data(cl, bytes); + + if (color) + fputs(color, tb->out); + fputs(data, tb->out); + if (color) + fputs(UL_COLOR_RESET, tb->out); + free(data); + + for (i = len; i < width; i++) + fputc('x', tb->out); /* padding */ + + if (is_last_column(tb, cl)) + return 0; + + fputs(colsep(tb), tb->out); /* columns separator */ + return 0; +err: + free(data); + return -errno; +} + static int print_data(struct libscols_table *tb, struct libscols_column *cl, struct libscols_line *ln, /* optional */ @@ -309,14 +438,7 @@ static int print_data(struct libscols_table *tb, break; /* continue below */ } - if (tb->colors_wanted) { - if (ce && !color) - color = ce->color; - if (ln && !color) - color = ln->color; - if (!color) - color = cl->color; - } + color = get_cell_color(tb, cl, ln, ce); /* encode, note that 'len' and 'width' are number of cells, not bytes */ data = buffer_get_safe_data(buf, &len); @@ -336,11 +458,21 @@ static int print_data(struct libscols_table *tb, if (len > width && scols_column_is_trunc(cl)) { len = width; bytes = mbs_truncate(data, &len); /* updates 'len' */ + } - if (bytes == (size_t) -1) { - bytes = len = 0; - data = NULL; - } + /* multi-line cell */ + if (len > width && scols_column_is_wrap(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; } if (data) { @@ -353,33 +485,7 @@ static int print_data(struct libscols_table *tb, if (color) fputs(UL_COLOR_RESET, tb->out); len = width; - } else if (len > width && scols_column_is_wrap(cl)) { - char *p = data; - i = 0; - if (color) - fputs(color, tb->out); - - while (*p) { - len = width; - p = strdup(p); - bytes = mbs_truncate(p, &len); - if (bytes == (size_t) -1) { - free(p); - break; - } - fputs(p, tb->out); - free(p); - i += bytes; - p = data + i; - if (*p) - for (size_t j = 0; j < cl->seqnum; j++) - print_empty_cell (tb, scols_table_get_column(tb, j), - ln, buf->bufsz); - } - - if (color) - fputs(UL_COLOR_RESET, tb->out); } else if (color) { char *p = data; size_t art = buffer_get_safe_art_size(buf); @@ -551,7 +657,7 @@ static int print_line(struct libscols_table *tb, struct libscols_line *ln, struct libscols_buffer *buf) { - int rc = 0; + int rc = 0, pending = 0; struct libscols_column *cl; struct libscols_iter itr; @@ -559,15 +665,36 @@ static int print_line(struct libscols_table *tb, DBG(TAB, ul_debugobj(tb, "printing line, line=%p, buff=%p", ln, buf)); + /* 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) + 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; + } + + /* extra lines of the multi-line cells */ + while (rc == 0 && pending) { + pending = 0; + fputs(linesep(tb), tb->out); + 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, buf->bufsz); + } } return 0;