From: Karel Zak Date: Thu, 12 Oct 2023 20:38:43 +0000 (+0200) Subject: libsmartcols: multi-line cells refactoring X-Git-Tag: v2.40-rc1~193 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e222a23f110e4859655fcb5b9f7e3ad5d7f03e01;p=thirdparty%2Futil-linux.git libsmartcols: multi-line cells refactoring * move data wrapping code to column.c * do data wrapping on one place when copy cell data to buffer * use table cursor in affected functions * calculate tree ASCII-art to wrapped data * mark wrap_chunksize() callback as deprecated; library calculates the size itself from real data Signed-off-by: Karel Zak --- diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c index cb7e020386..ebb61344db 100644 --- a/libsmartcols/src/calculate.c +++ b/libsmartcols/src/calculate.c @@ -42,41 +42,38 @@ static int count_cell_width(struct libscols_table *tb, struct libscols_column *cl, struct ul_buffer *buf) { - size_t len; - char *data; - int rc; + size_t len = 0; + int rc = 0; struct libscols_cell *ce; - struct libscols_wstat *st; + char *data; - rc = __cell_to_buffer(tb, ln, cl, buf); - if (rc) - return rc; + ce = scols_line_get_cell(ln, cl->seqnum); + scols_table_set_cursor(tb, ln, cl, ce); + rc = __cursor_to_buffer(tb, buf, 1); + if (rc) + goto done; 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); + goto done; + + len = scols_table_is_noencoding(tb) ? + mbs_width(data) : + 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; + ce->width = len; + cl->wstat.width_max = max(len, cl->wstat.width_max); +done: + scols_table_reset_cursor(tb); + return rc; } static int walk_count_cell_width(struct libscols_table *tb, diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c index db4c3572df..f9c6a61f2c 100644 --- a/libsmartcols/src/column.c +++ b/libsmartcols/src/column.c @@ -73,7 +73,7 @@ void scols_unref_column(struct libscols_column *cl) scols_reset_cell(&cl->header); free(cl->color); free(cl->safechars); - free(cl->pending_data_buf); + free(cl->wrap_data); free(cl->shellvar); free(cl); } @@ -402,6 +402,8 @@ char *scols_wrapnl_nextchunk(const struct libscols_column *cl __attribute__((unu * Note that the size has to be based on number of terminal cells rather than * bytes to support multu-byte output. * + * Deprecated since 2.40. + * * Returns: size of the largest chunk. * * Since: 2.29 @@ -459,7 +461,7 @@ int scols_column_set_cmpfunc(struct libscols_column *cl, /** * 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_chunksize: function to return size of the largest chink of data (deprecated) * @wrap_nextchunk: function to return next zero terminated data * @userdata: optional stuff for callbacks * @@ -467,6 +469,9 @@ int scols_column_set_cmpfunc(struct libscols_column *cl, * is to wrap by column size, but you can create functions to wrap for example * after \n or after words, etc. * + * Note that since 2.40 the @wrap_chunksize is unnecessary. The library calculates + * the size itself. + * * Returns: 0, a negative value in case of an error. * * Since: 2.29 @@ -474,7 +479,7 @@ int scols_column_set_cmpfunc(struct libscols_column *cl, int scols_column_set_wrapfunc(struct libscols_column *cl, size_t (*wrap_chunksize)(const struct libscols_column *, const char *, - void *), + void *) __attribute__((__unused__)), char * (*wrap_nextchunk)(const struct libscols_column *, char *, void *), @@ -484,7 +489,6 @@ int scols_column_set_wrapfunc(struct libscols_column *cl, return -EINVAL; cl->wrap_nextchunk = wrap_nextchunk; - cl->wrap_chunksize = wrap_chunksize; cl->wrapfunc_data = userdata; return 0; } @@ -639,7 +643,6 @@ int scols_column_is_wrap(const struct libscols_column *cl) int scols_column_is_customwrap(const struct libscols_column *cl) { return (cl->flags & SCOLS_FL_WRAP) - && cl->wrap_chunksize && cl->wrap_nextchunk ? 1 : 0; } @@ -735,3 +738,122 @@ int scols_column_set_properties(struct libscols_column *cl, const char *opts) return rc; } +static void scols_column_reset_wrap(struct libscols_column *cl) +{ + if (!cl) + return; + + if (cl->wrap_data) + memset(cl->wrap_data, 0, cl->wrap_datamax); + cl->wrap_cell = NULL; + cl->wrap_datasz = 0; + cl->wrap_cur = NULL; + cl->wrap_next = NULL; +} + +static int scols_column_init_wrap( + struct libscols_column *cl, + struct libscols_cell *ce) +{ + const char *data = scols_cell_get_data(ce); + + if (!cl || !ce) + return -EINVAL; + + assert(cl->table->cur_column == cl); + assert(cl->table->cur_cell == ce); + + scols_column_reset_wrap(cl); + + cl->wrap_cell = ce; + if (data) { + void *tmp; + cl->wrap_datasz = strlen(data) + 1; /* TODO: use scols_cell_get_datasiz() */ + + if (cl->wrap_datasz > cl->wrap_datamax) { + cl->wrap_datamax = cl->wrap_datasz; + tmp = realloc(cl->wrap_data, cl->wrap_datamax); + if (!tmp) + return -ENOMEM; + cl->wrap_data = tmp; + } + memcpy(cl->wrap_data, data, cl->wrap_datasz); + cl->wrap_cur = cl->wrap_data; + cl->wrap_next = NULL; + } + + return 0; +} + +/* Returns the next chunk of cell data in multi-line cells */ +int scols_column_next_wrap( + struct libscols_column *cl, + struct libscols_cell *ce, + char **data) +{ + if (!cl || !data || (!cl->wrap_cell && !ce)) + return -EINVAL; + + *data = NULL; + + if (ce && cl->wrap_cell != ce) + scols_column_init_wrap(cl, ce); /* init */ + else { + cl->wrap_cur = cl->wrap_next; /* next step */ + cl->wrap_next = NULL; + } + + if (!cl->wrap_cur) + return 1; /* no more data */ + if (scols_column_is_customwrap(cl)) + cl->wrap_next = cl->wrap_nextchunk(cl, cl->wrap_cur, cl->wrapfunc_data); + + *data = cl->wrap_cur; + return 0; +} + +int scols_column_greatest_wrap( + struct libscols_column *cl, + struct libscols_cell *ce, + char **data) +{ + size_t maxsz = 0; + char *res = NULL;; + + if (!scols_column_is_customwrap(cl)) + return scols_column_next_wrap(cl, ce, data); + + while (scols_column_next_wrap(cl, ce, data) == 0) { + size_t sz = strlen(*data); + + maxsz = max(maxsz, sz); + if (maxsz == sz) + res = *data; + } + + *data = res; + return 0; +} + +/* Set the "next" chunk in multi-line cell to offset specified by @bytes. + * Don't use it for columns with custom wrapfunc(). + */ +int scols_column_move_wrap(struct libscols_column *cl, size_t bytes) +{ + size_t x; /* remaining bytes */ + + if (!cl->wrap_cur) + return -EINVAL; /* scols_column_init_wrap() not called */ + + x = cl->wrap_datasz - (cl->wrap_cur - cl->wrap_data); + if (bytes >= x) + cl->wrap_next = NULL; /* done */ + else + cl->wrap_next = cl->wrap_cur + bytes; + return 0; +} + +int scols_column_has_pending_wrap(struct libscols_column *cl) +{ + return cl && cl->wrap_next; +} diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c index 6a7e6da045..c0b5c5258a 100644 --- a/libsmartcols/src/print.c +++ b/libsmartcols/src/print.c @@ -241,7 +241,7 @@ static int has_pending_data(struct libscols_table *tb) while (scols_table_next_column(tb, &itr, &cl) == 0) { if (scols_column_is_hidden(cl)) continue; - if (cl->pending_data) + if (scols_column_has_pending_wrap(cl)) return 1; } return 0; @@ -423,95 +423,43 @@ static void print_newline_padding(struct libscols_table *tb, 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) +static int print_pending_data(struct libscols_table *tb, struct ul_buffer *buf) { - size_t width = cl->width, bytes; - size_t len = width, i; + struct libscols_line *ln; + struct libscols_column *cl; + struct libscols_cell *ce; char *data; - char *nextchunk = NULL; + size_t i, width = 0, len = 0, bytes = 0; - if (!cl->pending_data) - return 0; + scols_table_get_cursor(tb, &ln, &cl, &ce); + + width = cl->width; if (!width) return -EINVAL; DBG(COL, ul_debugobj(cl, "printing pending data")); - data = strdup(cl->pending_data); + 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) - goto err; + return 0; - if (scols_column_is_customwrap(cl) - && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { - bytes = nextchunk - data; + /* standard multi-line cell */ + if (len > width && scols_column_is_wrap(cl) + && !scols_column_is_customwrap(cl)) { - len = scols_table_is_noencoding(tb) ? - mbs_nwidth(data, bytes) : - mbs_safe_nwidth(data, bytes, NULL); - } else + len = width; bytes = mbs_truncate(data, &len); - if (bytes == (size_t) -1) - goto err; - - if (bytes) - step_pending_data(cl, bytes); + if (bytes != (size_t) -1 && bytes > 0) + scols_column_move_wrap(cl, mbs_safe_decode_size(data)); + } 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)) { @@ -535,9 +483,6 @@ static int print_pending_data( fputs(colsep(tb), tb->out); return 0; -err: - free(data); - return -errno; } static void print_json_data(struct libscols_table *tb, @@ -574,32 +519,30 @@ static void print_json_data(struct libscols_table *tb, 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); + 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); + } while (scols_column_next_wrap(cl, NULL, &data) == 0); 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) +static int print_data(struct libscols_table *tb, struct ul_buffer *buf) { + struct libscols_line *ln; /* NULL for header line! */ + struct libscols_column *cl; + struct libscols_cell *ce; size_t len = 0, i, width, bytes; - char *data, *nextchunk; + char *data; const char *name = NULL; int is_last; assert(tb); + + scols_table_get_cursor(tb, &ln, &cl, &ce); assert(cl); data = ul_buffer_get_data(buf, NULL, NULL); @@ -614,7 +557,7 @@ static int print_data(struct libscols_table *tb, is_last = is_last_column(cl); - if (is_last && scols_table_is_json(tb) && + if (ln && is_last && scols_table_is_json(tb) && scols_table_is_tree(tb) && has_children(ln)) /* "children": [] is the real last value */ is_last = 0; @@ -642,7 +585,7 @@ static int print_data(struct libscols_table *tb, break; /* continue below */ } - /* Encode. Note that 'len' and 'width' are number of cells, not bytes. + /* Encode. Note that 'len' and 'width' are number of glyphs not bytes. */ if (scols_table_is_noencoding(tb)) data = ul_buffer_get_data(buf, &bytes, &len); @@ -653,17 +596,6 @@ static int print_data(struct libscols_table *tb, 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) @@ -679,12 +611,12 @@ static int print_data(struct libscols_table *tb, /* 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 > 0) + scols_column_move_wrap(cl, mbs_safe_decode_size(data)); } if (bytes == (size_t) -1) { @@ -701,7 +633,6 @@ static int print_data(struct libscols_table *tb, len = width; } fputs(data, tb->out); - } /* minout -- don't fill */ @@ -732,16 +663,23 @@ static int print_data(struct libscols_table *tb, return 0; } -int __cell_to_buffer(struct libscols_table *tb, - struct libscols_line *ln, - struct libscols_column *cl, - struct ul_buffer *buf) +/* + * Copy curret cell data to buffer. The @cal means "calculation" phase. + */ +int __cursor_to_buffer(struct libscols_table *tb, + struct ul_buffer *buf, + int cal) { - const char *data; + const char *data = NULL; struct libscols_cell *ce; + struct libscols_line *ln; + struct libscols_column *cl; int rc = 0; assert(tb); + + scols_table_get_cursor(tb, &ln, &cl, &ce); + assert(ln); assert(cl); assert(buf); @@ -749,11 +687,22 @@ int __cell_to_buffer(struct libscols_table *tb, ul_buffer_reset_data(buf); - ce = scols_line_get_cell(ln, cl->seqnum); - data = ce ? scols_cell_get_data(ce) : NULL; + if (ce) { + if (scols_column_is_wrap(cl)) { + char *x = NULL; + + rc = cal ? scols_column_greatest_wrap(cl, ce, &x) : + scols_column_next_wrap(cl, ce, &x); + if (rc < 0) + return rc; + data = x; + rc = 0; + } else + data = scols_cell_get_data(ce); + } if (!scols_column_is_tree(cl)) - return data ? ul_buffer_append_string(buf, data) : 0; + goto notree; /* * Group stuff @@ -775,7 +724,7 @@ int __cell_to_buffer(struct libscols_table *tb, if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb)) ul_buffer_save_pointer(buf, SCOLS_BUFPTR_TREEEND); - +notree: if (!rc && data) rc = ul_buffer_append_string(buf, data); return rc; @@ -801,16 +750,18 @@ static int print_line(struct libscols_table *tb, /* 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) + + scols_table_set_cursor(tb, ln, cl, scols_line_get_cell(ln, cl->seqnum)); + rc = __cursor_to_buffer(tb, buf, 0); + if (!rc) + rc = print_data(tb, buf); + if (!rc && scols_column_has_pending_wrap(cl)) pending = 1; + scols_table_reset_cursor(tb); } fputs_color_line_close(tb); @@ -821,16 +772,23 @@ static int print_line(struct libscols_table *tb, 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) + + scols_table_set_cursor(tb, ln, cl, scols_line_get_cell(ln, cl->seqnum)); + if (scols_column_has_pending_wrap(cl)) { + rc = __cursor_to_buffer(tb, buf, 0); + if (!rc) + rc = print_pending_data(tb, buf); + if (!rc && scols_column_has_pending_wrap(cl)) pending = 1; } else print_empty_cell(tb, cl, ln, NULL, ul_buffer_get_bufsiz(buf)); + scols_table_reset_cursor(tb); } fputs_color_line_close(tb); } @@ -963,6 +921,7 @@ int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf) continue; ul_buffer_reset_data(buf); + scols_table_set_cursor(tb, NULL, cl, &cl->header); if (cl->is_groups && scols_table_is_tree(tb) && scols_column_is_tree(cl)) { @@ -979,7 +938,8 @@ int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf) scols_column_get_name_as_shellvar(cl) : scols_column_get_name(cl)); if (!rc) - rc = print_data(tb, cl, NULL, &cl->header, buf); + rc = print_data(tb, buf); + scols_table_reset_cursor(tb); } if (rc == 0) { diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h index 89a7f6ec4a..3abe8803a0 100644 --- a/libsmartcols/src/smartcolsP.h +++ b/libsmartcols/src/smartcolsP.h @@ -117,21 +117,21 @@ struct libscols_column { 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 *); + /* multi-line cell data wrapping */ + char *(*wrap_nextchunk)(const struct libscols_column *, char *, void *); void *wrapfunc_data; + size_t wrap_datasz; + size_t wrap_datamax; + char *wrap_data; + char *wrap_cur; + char *wrap_next; + struct libscols_cell *wrap_cell; struct libscols_cell header; /* column name with color etc. */ char *shellvar; /* raw colum name in shell compatible format */ @@ -303,6 +303,17 @@ int scols_line_next_group_child(struct libscols_line *ln, struct libscols_iter *itr, struct libscols_line **chld); +/* + * column.c + */ +int scols_column_next_wrap( struct libscols_column *cl, + struct libscols_cell *ce, + char **data); +int scols_column_greatest_wrap( struct libscols_column *cl, + struct libscols_cell *ce, + char **data); +int scols_column_has_pending_wrap(struct libscols_column *cl); +int scols_column_move_wrap(struct libscols_column *cl, size_t bytes); /* * table.c @@ -315,6 +326,8 @@ int scols_table_set_cursor(struct libscols_table *tb, struct libscols_column *cl, struct libscols_cell *ce); +#define scols_table_reset_cursor(_t) scols_table_set_cursor((_t), NULL, NULL, NULL) + /* * grouping.c @@ -348,10 +361,9 @@ 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); +int __cursor_to_buffer(struct libscols_table *tb, + struct ul_buffer *buf, + int cal); void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf); int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf);