#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
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;
else 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) {
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--;
}
if (scols_cell_get_data(&cl->header)) {
size_t len = mbs_safe_width(scols_cell_get_data(&cl->header));
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
* calcualtion. 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 = 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 || 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;
}