diff options
Diffstat (limited to 'libsmartcols/src/calculate.c')
-rw-r--r-- | libsmartcols/src/calculate.c | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c new file mode 100644 index 0000000..b6137fd --- /dev/null +++ b/libsmartcols/src/calculate.c @@ -0,0 +1,454 @@ +#include "smartcolsP.h" +#include "mbsalign.h" + +static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) +{ + if (scols_column_is_hidden(cl)) { + DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); + return; + } + + DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " + "hint=%d, avg=%zu, max=%zu, min=%zu, " + "extreme=%s %s", + + cl->header.data, cl->seqnum, cl->width, + cl->width_hint > 1 ? (int) cl->width_hint : + (int) (cl->width_hint * tb->termwidth), + cl->width_avg, + cl->width_max, + cl->width_min, + cl->is_extreme ? "yes" : "not", + cl->flags & SCOLS_FL_TRUNC ? "trunc" : "")); +} + +static void dbg_columns(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) + dbg_column(tb, cl); +} + +static int count_cell_width(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct libscols_buffer *buf) +{ + size_t len; + char *data; + int rc; + + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc) + return rc; + + data = buffer_get_data(buf); + if (!data) + len = 0; + else if (scols_column_is_customwrap(cl)) + len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); + else if (scols_table_is_noencoding(tb)) + len = mbs_width(data); + else + len = mbs_safe_width(data); + + if (len == (size_t) -1) /* ignore broken multibyte strings */ + len = 0; + cl->width_max = max(len, cl->width_max); + + if (cl->is_extreme && cl->width_avg && len > cl->width_avg * 2) + return 0; + + if (scols_column_is_noextremes(cl)) { + cl->extreme_sum += len; + cl->extreme_count++; + } + cl->width = max(len, cl->width); + if (scols_column_is_tree(cl)) { + size_t treewidth = buffer_get_safe_art_size(buf); + cl->width_treeart = max(cl->width_treeart, treewidth); + } + return 0; +} + + +static int walk_count_cell_width(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + void *data) +{ + return count_cell_width(tb, ln, cl, (struct libscols_buffer *) data); +} + +/* + * This function counts column width. + * + * For the SCOLS_FL_NOEXTREMES columns it is possible to call this function + * two times. The first pass counts the width and average width. If the column + * contains fields that are too large (a width greater than 2 * average) then + * the column is marked as "extreme". In the second pass all extreme fields + * are ignored and the column width is counted from non-extreme fields only. + */ +static int count_column_width(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_buffer *buf) +{ + int rc = 0, no_header = 0; + + assert(tb); + assert(cl); + + cl->width = 0; + if (!cl->width_min) { + const char *data; + + if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { + cl->width_min = (size_t) (cl->width_hint * tb->termwidth); + if (cl->width_min && !is_last_column(cl)) + cl->width_min--; + } + + data = scols_cell_get_data(&cl->header); + if (data) { + size_t len = scols_table_is_noencoding(tb) ? + mbs_width(data) : mbs_safe_width(data); + cl->width_min = max(cl->width_min, len); + } else + no_header = 1; + + if (!cl->width_min) + cl->width_min = 1; + } + + if (scols_table_is_tree(tb)) { + /* Count width for tree */ + rc = scols_walk_tree(tb, cl, walk_count_cell_width, (void *) buf); + if (rc) + goto done; + } else { + /* Count width for list */ + struct libscols_iter itr; + struct libscols_line *ln; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + rc = count_cell_width(tb, ln, cl, buf); + if (rc) + goto done; + } + } + + if (scols_column_is_tree(cl) && has_groups(tb)) { + /* We don't fill buffer with groups tree ascii art during width + * calculation. The print function only enlarge grpset[] and we + * calculate final width from grpset_size. + */ + size_t gprwidth = tb->grpset_size + 1; + cl->width_treeart += gprwidth; + cl->width_max += gprwidth; + cl->width += gprwidth; + if (cl->extreme_count) + cl->extreme_sum += gprwidth; + } + + if (cl->extreme_count && cl->width_avg == 0) { + cl->width_avg = cl->extreme_sum / cl->extreme_count; + if (cl->width_avg && cl->width_max > cl->width_avg * 2) + cl->is_extreme = 1; + } + + /* enlarge to minimal width */ + if (cl->width < cl->width_min && !scols_column_is_strict_width(cl)) + cl->width = cl->width_min; + + /* use absolute size for large columns */ + else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint + && cl->width_min < (size_t) cl->width_hint) + + cl->width = (size_t) cl->width_hint; + + + /* Column without header and data, set minimal size to zero (default is 1) */ + if (cl->width_max == 0 && no_header && cl->width_min == 1 && cl->width <= 1) + cl->width = cl->width_min = 0; + +done: + ON_DBG(COL, dbg_column(tb, cl)); + return rc; +} + +/* + * This is core of the scols_* voodoo... + */ +int __scols_calculate(struct libscols_table *tb, struct libscols_buffer *buf) +{ + struct libscols_column *cl; + struct libscols_iter itr; + size_t width = 0, width_min = 0; /* output width */ + int stage, rc = 0; + int extremes = 0, group_ncolumns = 0; + size_t colsepsz; + + + DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth)); + tb->is_dummy_print = 1; + + colsepsz = scols_table_is_noencoding(tb) ? + mbs_width(colsep(tb)) : + mbs_safe_width(colsep(tb)); + + if (has_groups(tb)) + group_ncolumns = 1; + + /* set basic columns width + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + int is_last; + + if (scols_column_is_hidden(cl)) + continue; + + /* we print groups chart only for the for the first tree column */ + if (scols_column_is_tree(cl) && group_ncolumns == 1) { + cl->is_groups = 1; + group_ncolumns++; + } + + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + is_last = is_last_column(cl); + + width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ + width_min += cl->width_min + (is_last ? 0 : colsepsz); + if (cl->is_extreme) + extremes++; + } + + if (!tb->is_term) { + DBG(TAB, ul_debugobj(tb, " non-terminal output")); + goto done; + } + + /* be paranoid */ + if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (width_min > tb->termwidth + && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + width_min--; + cl->width_min--; + } + DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); + } + + /* reduce columns with extreme fields */ + if (width > tb->termwidth && extremes) { + DBG(TAB, ul_debugobj(tb, " reduce width (extreme columns)")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t org_width; + + if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + org_width = cl->width; + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + if (org_width > cl->width) + width -= org_width - cl->width; + else + extremes--; /* hmm... nothing reduced */ + } + } + + if (width < tb->termwidth) { + if (extremes) { + DBG(TAB, ul_debugobj(tb, " enlarge width (extreme columns)")); + + /* enlarge the first extreme column */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t add; + + if (!cl->is_extreme || scols_column_is_hidden(cl)) + continue; + + /* this column is too large, ignore? + if (cl->width_max - cl->width > + (tb->termwidth - width)) + continue; + */ + + add = tb->termwidth - width; + if (add && cl->width + add > cl->width_max) + add = cl->width_max - cl->width; + + cl->width += add; + width += add; + + if (width == tb->termwidth) + break; + } + } + + if (width < tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); + + /* try enlarging all columns */ + while (width < tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + cl->width++; + width++; + if (width == tb->termwidth) + break; + } + } + } else if (width < tb->termwidth) { + /* enlarge the last column */ + struct libscols_column *col = list_entry( + tb->tb_columns.prev, struct libscols_column, cl_columns); + + DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); + + if (!scols_column_is_right(col) && tb->termwidth - width > 0) { + col->width += tb->termwidth - width; + width = tb->termwidth; + } + } + } + + /* bad, we have to reduce output width, this is done in three stages: + * + * 1) trunc relative with trunc flag if the column width is greater than + * expected column width (it means "width_hint * terminal_width"). + * + * 2) trunc all with trunc flag + * + * 3) trunc relative without trunc flag + * + * Note that SCOLS_FL_WRAP (if no custom wrap function is specified) is + * interpreted as SCOLS_FL_TRUNC. + */ + for (stage = 1; width > tb->termwidth && stage <= 3; ) { + size_t org_width = width; + + DBG(TAB, ul_debugobj(tb, " reduce width - #%d stage (current=%zu, wanted=%zu)", + stage, width, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + int trunc_flag = 0; + + DBG(TAB, ul_debugobj(cl, " checking %s (width=%zu, treeart=%zu)", + cl->header.data, cl->width, cl->width_treeart)); + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + + /* never truncate if already minimal width */ + if (cl->width == cl->width_min) + continue; + + /* never truncate the tree */ + if (scols_column_is_tree(cl) && width <= cl->width_treeart) + continue; + + /* nothing to truncate */ + if (cl->width == 0) + continue; + + trunc_flag = scols_column_is_trunc(cl) + || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); + + switch (stage) { + /* #1 stage - trunc relative with TRUNC flag */ + case 1: + if (!trunc_flag) /* ignore: missing flag */ + break; + if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ + break; + if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) /* ignore: smaller than expected width */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (relative with flag)")); + cl->width--; + width--; + break; + + /* #2 stage - trunc all with TRUNC flag */ + case 2: + if (!trunc_flag) /* ignore: missing flag */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (all with flag)")); + cl->width--; + width--; + break; + + /* #3 stage - trunc relative without flag */ + case 3: + if (cl->width_hint <= 0 || cl->width_hint >= 1) /* ignore: no relative */ + break; + + DBG(TAB, ul_debugobj(tb, " reducing (relative without flag)")); + cl->width--; + width--; + break; + } + + /* hide zero width columns */ + if (cl->width == 0) + cl->flags |= SCOLS_FL_HIDDEN; + } + + /* the current stage is without effect, go to the next */ + if (org_width == width) + stage++; + } + + /* ignore last column(s) or force last column to be truncated if + * nowrap mode enabled */ + if (tb->no_wrap && width > tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + if (width - cl->width < tb->termwidth) { + size_t r = width - tb->termwidth; + + cl->flags |= SCOLS_FL_TRUNC; + cl->width -= r; + width -= r; + } else { + cl->flags |= SCOLS_FL_HIDDEN; + width -= cl->width + colsepsz; + } + } + } +done: + tb->is_dummy_print = 0; + DBG(TAB, ul_debugobj(tb, "-----final width: %zu (rc=%d)-----", width, rc)); + ON_DBG(TAB, dbg_columns(tb)); + + return rc; +} |