libtabular-c

C tabular formatting library
git clone https://git.sinitax.com/sinitax/libtabular-c
Log | Files | Refs | Submodules | sfeed.txt

tabular.c (17565B)


      1#define _XOPEN_SOURCE
      2
      3#include "tabular.h"
      4#include "util.h"
      5
      6#include <sys/ioctl.h>
      7#include <wchar.h>
      8#include <errno.h>
      9#include <unistd.h>
     10#include <stdio.h>
     11#include <string.h>
     12#include <stdint.h>
     13#include <stddef.h>
     14
     15#define BIT(i) (1U << (i))
     16
     17struct col_state {
     18	size_t width;
     19	size_t written;
     20	size_t maxlen;
     21	size_t lpad, rpad;
     22	bool hidden;
     23};
     24
     25struct fmt_state {
     26	struct col_state *columns;
     27	size_t column_count;
     28
     29	size_t hseplen;
     30	size_t vseplen;
     31	size_t xseplen;
     32	size_t mseplen;
     33
     34	size_t line_limit;
     35	size_t row_limit;
     36
     37	bool header_line;
     38};
     39
     40static void calc_word_aware(struct tabular_entry *entry, size_t maxwidth,
     41	size_t *offset, size_t *width, size_t *lines);
     42static size_t recalc_col_word_aware(const struct tabular_cfg *cfg,
     43	struct tabular_row *rows, size_t limit,
     44	size_t col, size_t maxwidth);
     45static size_t calc_row_width(struct fmt_state *fmt);
     46static size_t calc_output_lines(const struct tabular_cfg *cfg,
     47	struct fmt_state *fmt, struct tabular_row *rows, size_t limit);
     48static bool recalc_cols(const struct tabular_cfg *cfg,
     49	struct tabular_stats *stats, struct fmt_state *fmt,
     50	struct tabular_row *rows, size_t limit);
     51
     52static int calc_params_row(const struct tabular_cfg *cfg,
     53	struct tabular_stats *stats, struct tabular_row *rows,
     54	struct tabular_row *row, struct fmt_state *fmt, size_t limit);
     55static int calc_params(const struct tabular_cfg *cfg,
     56	struct tabular_stats *stat, struct tabular_row **rows,
     57	struct fmt_state *fmt);
     58
     59static void output_header(FILE *file, const struct tabular_cfg *cfg,
     60	struct tabular_stats *stat, const struct fmt_state *fmt);
     61static void output_row(FILE *file, const struct tabular_cfg *cfg,
     62	struct tabular_stats *stat, const struct tabular_row *row,
     63	struct fmt_state *fmt);
     64static int output_rows(FILE *file, const struct tabular_cfg *cfg,
     65	struct tabular_stats *stats, struct tabular_row *rows,
     66	struct fmt_state *fmt);
     67
     68const char *
     69calc_word_aware_line(const char *str, size_t maxwidth,
     70	size_t *out_offset, size_t *out_width)
     71{
     72	const char *sep, *nstr;
     73	size_t width, nwidth;
     74	size_t offset, wlen;
     75
     76	if (!str) {
     77		if (out_offset) *out_offset = 0;
     78		if (out_width) *out_width = 0;
     79		return NULL;
     80	}
     81
     82	offset = strspn(str, " \v\t\r\n");
     83	str += offset;
     84
     85	nstr = str;
     86	nwidth = width = 0;
     87	while (nwidth <= maxwidth) {
     88		width = nwidth;
     89		str = nstr;
     90		if (!str) break;
     91		sep = strchr(str, ' ');
     92		if (!sep) {
     93			wlen = u8strlen(str);
     94			nstr = NULL;
     95		} else {
     96			wlen = u8strnlen(str, (size_t) (sep - str));
     97			nstr = sep + 1;
     98		}
     99		nwidth = width + (width > 0) + wlen;
    100	}
    101
    102	if (out_offset) *out_offset = offset;
    103	if (out_width) {
    104		/* single word > maxwidth */
    105		if (!width && nwidth) {
    106			*out_width = maxwidth;
    107			str = nstr; /* skip single word */
    108		} else {
    109			*out_width = width;
    110		}
    111	}
    112
    113	return str;
    114}
    115
    116void
    117calc_word_aware(struct tabular_entry *entry, size_t maxwidth,
    118	size_t *out_offset, size_t *out_width, size_t *out_lines)
    119{
    120	const char *str;
    121	size_t width, mwidth;
    122	size_t lines;
    123
    124	if (out_offset) *out_offset = 0;
    125
    126	lines = 0;
    127	mwidth = 0;
    128	str = entry->str;
    129	while (str) {
    130		str = calc_word_aware_line(str, maxwidth,
    131			str == entry->str ? out_offset : NULL, &width);
    132		lines++;
    133		mwidth = MAX(mwidth, width);
    134	}
    135
    136	if (out_width) *out_width = mwidth;
    137	if (out_lines) *out_lines = lines;
    138}
    139
    140size_t
    141recalc_col_word_aware(const struct tabular_cfg *cfg,
    142	struct tabular_row *rows, size_t limit, size_t col,
    143	size_t maxwidth)
    144{
    145	size_t off, wwidth, max_wwidth, rowcnt;
    146	struct tabular_row *row;
    147
    148	max_wwidth = cfg->columns[col].minwidth;
    149
    150	rowcnt = 0;
    151	for (row = rows; row && rowcnt < limit; row = row->next) {
    152		calc_word_aware(&row->entries[col],
    153			maxwidth, &off, &wwidth, NULL);
    154		max_wwidth = MAX(max_wwidth, wwidth);
    155		rowcnt += 1;
    156	}
    157
    158	return max_wwidth;
    159}
    160
    161size_t
    162calc_row_width(struct fmt_state *fmt)
    163{
    164	size_t sum, i;
    165
    166	sum = 0;
    167	for (i = 0; i < fmt->column_count; i++) {
    168		if (fmt->columns[i].hidden) continue;
    169		sum += fmt->columns[i].width + (sum ? fmt->hseplen : 0);
    170	}
    171
    172	return sum;
    173}
    174
    175size_t
    176calc_output_lines(const struct tabular_cfg *cfg, struct fmt_state *fmt,
    177	struct tabular_row *rows, size_t limit)
    178{
    179	struct tabular_row *row;
    180	size_t row_index, row_lines;
    181	size_t entry_lines;
    182	size_t lines, i;
    183	size_t width;
    184
    185	lines = 0;
    186	row_index = 0;
    187	for (row = rows; row; row = row->next) {
    188		if (row_index == limit) break;
    189
    190		row_lines = 0;
    191		for (i = 0; i < cfg->column_count; i++) {
    192			if (fmt->columns[i].hidden) continue;
    193			width = fmt->columns[i].width
    194				- fmt->columns[i].lpad - fmt->columns[i].rpad;
    195			switch (cfg->columns[i].strategy) {
    196			case TABULAR_TRUNC:
    197				entry_lines = 1;
    198				break;
    199			case TABULAR_WRAP:
    200				entry_lines = CEILDIV(row->entries[i].ulen, width);
    201				break;
    202			case TABULAR_WRAP_WORDAWARE:
    203				calc_word_aware(&row->entries[i],
    204					width, NULL, NULL, &entry_lines);
    205				break;
    206			}
    207			row_lines = MAX(row_lines, entry_lines);
    208		}
    209		lines += row_lines;
    210
    211		row_index += 1;
    212	}
    213
    214	return lines;
    215}
    216
    217bool
    218recalc_cols(const struct tabular_cfg *cfg, struct tabular_stats *stats,
    219	struct fmt_state *fmt, struct tabular_row *rows, size_t limit)
    220{
    221	size_t width, fullwidth, remaining;
    222	ssize_t i;
    223
    224	fullwidth = cfg->outw - cfg->lpad - cfg->rpad;
    225
    226	/* reset widths to minimum requirement */
    227	for (i = 0; i < cfg->column_count; i++) {
    228		if (fmt->columns[i].hidden) continue;
    229		if (cfg->columns[i].squashable) {
    230			fmt->columns[i].width = cfg->columns[i].minwidth;
    231		} else {
    232			width = MIN(fmt->columns[i].maxlen,
    233				cfg->columns[i].maxwidth - cfg->columns[i].lpad
    234				- cfg->columns[i].rpad);
    235			if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE)
    236				width = recalc_col_word_aware(cfg, rows,
    237					limit, (size_t) i, width);
    238			fmt->columns[i].width = width + cfg->columns[i].lpad
    239				+ cfg->columns[i].rpad;
    240		}
    241	}
    242
    243	/* could not fit all necessary columns at minimum width */
    244	width = calc_row_width(fmt);
    245	while (width > fullwidth) {
    246		/* remove non-essential columns */
    247		for (i = ((ssize_t) cfg->column_count) - 1; i >= 0; i--) {
    248			if (!cfg->columns[i].essential
    249					&& !fmt->columns[i].hidden) {
    250				fmt->columns[i].hidden = true;
    251				break;
    252			}
    253		}
    254
    255		/* failed to remove any more */
    256		if (i < 0) return false;
    257
    258		stats->cols_truncated = true;
    259		width = calc_row_width(fmt);
    260	}
    261
    262	/* redistribute excess width to columns, left to right */
    263	remaining = fullwidth - width;
    264	for (i = 0; remaining > 0 && i < cfg->column_count; i++) {
    265		if (fmt->columns[i].hidden) continue;
    266		if (!cfg->columns[i].squashable) continue;
    267		width = MIN(fmt->columns[i].maxlen, cfg->columns[i].maxwidth
    268			- fmt->columns[i].lpad - fmt->columns[i].rpad);
    269		width = MIN(width, fmt->columns[i].width + remaining
    270			- fmt->columns[i].lpad - fmt->columns[i].rpad);
    271		if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE)
    272			width = recalc_col_word_aware(cfg, rows,
    273				limit, (size_t) i, width);
    274		width = MAX(width + fmt->columns[i].lpad + fmt->columns[i].rpad,
    275			fmt->columns[i].width);
    276		remaining -= width - fmt->columns[i].width;
    277		fmt->columns[i].width = width;
    278	}
    279
    280	return true;
    281}
    282
    283int
    284calc_params_row(const struct tabular_cfg *cfg, struct tabular_stats *stats,
    285	struct tabular_row *rows, struct tabular_row *row,
    286	struct fmt_state *fmt, size_t limit)
    287{
    288	struct colinfo *copy;
    289	size_t i, height;
    290	int rc;
    291
    292	/* make copy of current width in case the next row does not fit */
    293	copy = cfg->allocator->alloc(cfg->allocator,
    294		sizeof(struct col_state) * cfg->column_count, &rc);
    295	if (!copy) return -rc;
    296	memcpy(copy, fmt->columns, sizeof(struct col_state) * cfg->column_count);
    297
    298	/* unhide cols */
    299	for (i = 0; i < cfg->column_count; i++) {
    300		tabular_load_row_entry_hidden(cfg, row, i);
    301		if (fmt->columns[i].hidden) {
    302			fmt->columns[i].hidden =
    303				(row->entries[i].flags & BIT(TABULAR_ENTRY_HIDDEN));
    304		}
    305		if (fmt->columns[i].hidden) continue;
    306	}
    307
    308	/* update maxlen for visible cols */
    309	for (i = 0; i < cfg->column_count; i++) {
    310		tabular_load_row_entry_str(cfg, row, i);
    311		fmt->columns[i].maxlen = MAX(fmt->columns[i].maxlen,
    312			row->entries[i].ulen);
    313	}
    314
    315	/* recalc column sizes to fit new column */
    316	if (!recalc_cols(cfg, stats, fmt, rows, limit))
    317		goto undo;
    318
    319	height = calc_output_lines(cfg, fmt, rows, limit);
    320
    321	/* check the line limit if applicable */
    322	if (fmt->line_limit > 0 && height > fmt->line_limit)
    323		goto undo;
    324
    325	cfg->allocator->free(cfg->allocator, copy);
    326
    327	return 0;
    328
    329undo:
    330	memcpy(fmt->columns, copy, sizeof(struct col_state) * fmt->column_count);
    331	cfg->allocator->free(cfg->allocator, copy);
    332
    333	return 1;
    334}
    335
    336int
    337calc_params(const struct tabular_cfg *cfg, struct tabular_stats *stats,
    338	struct tabular_row **rows, struct fmt_state *fmt)
    339{
    340	struct tabular_row **row;
    341	ssize_t first, last;
    342	size_t i;
    343	int rc;
    344
    345	fmt->header_line = ((cfg->vsep != NULL && *cfg->vsep)
    346		&& (cfg->xsep != NULL && *cfg->xsep));
    347
    348	fmt->line_limit = 0;
    349	if (cfg->fit_rows) {
    350		fmt->line_limit = MAX(0, cfg->outh
    351			- cfg->skip_lines - 1 - fmt->header_line);
    352	}
    353
    354	fmt->hseplen = u8strlen(cfg->hsep);
    355	fmt->vseplen = u8strlen(cfg->vsep);
    356	fmt->xseplen = u8strlen(cfg->xsep);
    357	fmt->mseplen = MAX(fmt->hseplen, fmt->xseplen);
    358
    359	fmt->column_count = cfg->column_count;
    360	fmt->columns = cfg->allocator->alloc(cfg->allocator,
    361		fmt->column_count * sizeof(struct col_state), &rc);
    362	if (!fmt->columns) return -rc;
    363
    364	for (i = 0; i < cfg->column_count; i++) {
    365		fmt->columns[i].lpad = cfg->columns[i].lpad;
    366		fmt->columns[i].rpad = cfg->columns[i].rpad;
    367		fmt->columns[i].hidden = true;
    368		fmt->columns[i].width = cfg->columns[i].minwidth;
    369		fmt->columns[i].maxlen = u8strlen(cfg->columns[i].name)
    370			+ fmt->columns[i].lpad + fmt->columns[i].rpad;
    371		if (fmt->columns[i].maxlen > cfg->columns[i].minwidth)
    372			return 1;
    373		fmt->columns[i].written = 0;
    374	}
    375
    376	fmt->row_limit = 0;
    377
    378	if (!*rows && cfg->row_gen) *rows = cfg->row_gen(&cfg->user);
    379	if (!*rows) return 0;
    380
    381	if (!recalc_cols(cfg, stats, fmt, *rows, 0)) {
    382		stats->cols_truncated = true;
    383		stats->rows_truncated = true;
    384		return 0;
    385	}
    386
    387	for (row = rows; ; row = &(*row)->next) {
    388		if (!*row && cfg->row_gen) *row = cfg->row_gen(&cfg->user);
    389		if (!*row) break;
    390		rc = calc_params_row(cfg, stats, *rows,
    391			*row, fmt, fmt->row_limit + 1);
    392		if (rc < 0) return rc;
    393		if (rc > 0) {
    394			stats->rows_truncated = true;
    395			break;
    396		}
    397		fmt->row_limit++;
    398	}
    399
    400	first = last = -1;
    401	for (i = 0; i < cfg->column_count; i++) {
    402		if (fmt->columns[i].hidden) continue;
    403		if (first < 0) first = (ssize_t) i;
    404		last = (ssize_t) i;
    405	}
    406
    407	if (first >= 0 && last >= 0) {
    408		fmt->columns[first].lpad += cfg->lpad;
    409		fmt->columns[first].width += cfg->lpad;
    410		fmt->columns[last].rpad += cfg->rpad;
    411		fmt->columns[last].width += cfg->rpad;
    412	}
    413
    414	return 0;
    415}
    416
    417void
    418output_header(FILE *file, const struct tabular_cfg *cfg,
    419	struct tabular_stats *stats, const struct fmt_state *fmt)
    420{
    421	size_t i, k, width;
    422	bool dirty, first;
    423
    424	first = true;
    425	for (i = 0; i < cfg->column_count; i++) {
    426		if (fmt->columns[i].hidden) continue;
    427		if (!first) {
    428			dirty = cfg->print_style(file, cfg, NULL, NULL);
    429			fprintf(file, "%s%*.s", cfg->hsep,
    430				(int) (fmt->mseplen - fmt->hseplen), "");
    431			if (dirty) fprintf(file, "\x1b[0m");
    432		}
    433		first = false;
    434
    435		width = fmt->columns[i].width
    436			- fmt->columns[i].lpad - fmt->columns[i].rpad;
    437
    438		dirty = cfg->print_style(file, cfg,
    439			NULL, &cfg->columns[i]);
    440		print_pad(file, fmt->columns[i].lpad);
    441		print_left(file, cfg->columns[i].name, width, width);
    442		print_pad(file, fmt->columns[i].rpad);
    443		if (dirty) fprintf(file, "\x1b[0m");
    444	}
    445
    446	fprintf(file, "\n");
    447	stats->lines_used += 1;
    448
    449	if (fmt->header_line) {
    450		first = true;
    451		dirty = cfg->print_style(file, cfg, NULL, NULL);
    452		for (i = 0; i < cfg->column_count; i++) {
    453			if (fmt->columns[i].hidden) continue;
    454			if (!first) {
    455				fprintf(file, "%s%*.s", cfg->xsep,
    456					(int) (fmt->mseplen - fmt->xseplen), "");
    457			}
    458			for (k = 0; k < fmt->columns[i].width; k++)
    459				fprintf(file, "%s", cfg->vsep);
    460			first = false;
    461		}
    462		if (dirty) fprintf(file, "\x1b[0m");
    463		fprintf(file, "\n");
    464		stats->lines_used += 1;
    465	}
    466}
    467
    468void
    469output_row(FILE *file, const struct tabular_cfg *cfg,
    470	struct tabular_stats *stat, const struct tabular_row *row,
    471	struct fmt_state *fmt)
    472{
    473	size_t wwidth, padwidth, outlen;
    474	size_t i, off, width;
    475	bool first, done, dirty;
    476	char *entry;
    477
    478	for (i = 0; i < cfg->column_count; i++) {
    479		fmt->columns[i].written = 0;
    480
    481		if (cfg->columns[i].strategy == TABULAR_TRUNC) {
    482			width = fmt->columns[i].width
    483				- fmt->columns[i].lpad - fmt->columns[i].rpad;
    484			if (row->entries[i].ulen > width) {
    485				width = u8rawlen(row->entries[i].str, width - 2);
    486				row->entries[i].str[width] = '.';
    487				row->entries[i].str[width+1] = '.';
    488				row->entries[i].str[width+2] = '\0';
    489			}
    490		}
    491	}
    492
    493	do {
    494		done = true;
    495		first = true;
    496		for (i = 0; i < cfg->column_count; i++) {
    497			if (fmt->columns[i].hidden) continue;
    498
    499			if (!first) {
    500				dirty = cfg->print_style(file, cfg, NULL, NULL);
    501				fprintf(file, "%s", cfg->hsep);
    502				if (dirty) fprintf(file, "\x1b[0m");
    503			}
    504			first = false;
    505
    506			if (!row->entries[i].str) {
    507				print_pad(file, fmt->columns[i].width);
    508				continue;
    509			}
    510
    511			entry = row->entries[i].str + fmt->columns[i].written;
    512
    513			dirty = cfg->print_style(file, cfg, row, &cfg->columns[i]);
    514
    515			off = 0;
    516			width = fmt->columns[i].width
    517				- fmt->columns[i].lpad - fmt->columns[i].rpad;
    518			padwidth = width;
    519			if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE) {
    520				calc_word_aware_line(entry, width, &off, &wwidth);
    521				entry += off;
    522				width = wwidth;
    523			}
    524
    525			print_pad(file, fmt->columns[i].lpad);
    526			if (cfg->columns[i].align == TABULAR_ALIGN_LEFT) {
    527				print_left(file, entry, width, padwidth);
    528			} else if (cfg->columns[i].align == TABULAR_ALIGN_RIGHT) {
    529				print_right(file, entry, width, padwidth);
    530			} else {
    531				print_center(file, entry, width, padwidth);
    532			}
    533			print_pad(file, fmt->columns[i].rpad);
    534
    535			if (dirty) fprintf(file, "\x1b[0m");
    536
    537			outlen = MIN(u8strlen(entry), width + off);
    538			fmt->columns[i].written += u8rawlen(entry, outlen);
    539
    540			/* check if anything other than whitespace left */
    541			entry += u8rawlen(entry, outlen);
    542			if (strspn(entry, " \t\v\r\n") != u8strlen(entry))
    543				done = false;
    544		}
    545		fprintf(file, "\n");
    546		stat->lines_used += 1;
    547	} while (!done);
    548
    549	stat->rows_displayed += 1;
    550}
    551
    552int
    553output_rows(FILE *file, const struct tabular_cfg *cfg,
    554	struct tabular_stats *stats, struct tabular_row *rows,
    555	struct fmt_state *fmt)
    556{
    557	struct tabular_row *row;
    558	size_t count;
    559
    560	if (fmt->row_limit == 0) return 0;
    561
    562	output_header(file, cfg, stats, fmt);
    563
    564	count = 0;
    565	for (row = rows; row; row = row->next) {
    566		if (count == fmt->row_limit)
    567			break;
    568		output_row(file, cfg, stats, row, fmt);
    569		count += 1;
    570	}
    571
    572	return 0;
    573}
    574
    575int
    576tabular_format(FILE *file, const struct tabular_cfg *cfg,
    577	struct tabular_stats *stats, struct tabular_row **rows)
    578{
    579	struct fmt_state fmt;
    580	size_t i;
    581	int rc;
    582
    583	stats->lines_used = 0;
    584	stats->rows_displayed = 0;
    585	stats->rows_truncated = false;
    586	stats->cols_truncated = false;
    587
    588	if (!rows) return 1;
    589
    590	if (!cfg->column_count) return 0;
    591
    592	for (i = 0; i < cfg->column_count; i++) {
    593		if (cfg->columns[i].minwidth > cfg->columns[i].maxwidth)
    594			return 1;
    595		if (cfg->columns[i].lpad + cfg->columns[i].rpad
    596				>= cfg->columns[i].minwidth)
    597			return 1;
    598		if (cfg->columns[i].strategy != TABULAR_TRUNC
    599				&& cfg->columns[i].strategy != TABULAR_WRAP
    600				&& cfg->columns[i].strategy != TABULAR_WRAP_WORDAWARE)
    601			return 1;
    602	}
    603
    604	rc = calc_params(cfg, stats, rows, &fmt);
    605	if (rc) return rc;
    606
    607	rc = output_rows(file, cfg, stats, *rows, &fmt);
    608	if (rc) return rc;
    609
    610	rc = cfg->allocator->free(cfg->allocator, fmt.columns);
    611	if (rc) return -rc;
    612
    613	return 0;
    614}
    615
    616struct tabular_row *
    617tabular_alloc_row(const struct tabular_cfg *cfg,
    618	int *rc, struct tabular_user user)
    619{
    620	struct tabular_row *row;
    621
    622	row = cfg->allocator->alloc(cfg->allocator,
    623		sizeof(struct tabular_row), rc);
    624	if (!row && rc) *rc = -*rc;
    625	if (!row) return NULL;
    626
    627	row->next = NULL;
    628	row->user = user;
    629	row->entries = cfg->allocator->alloc(cfg->allocator,
    630		sizeof(struct tabular_entry) * cfg->column_count, rc);
    631	if (!row->entries && rc) *rc = -*rc;
    632	if (!row->entries) return NULL;
    633	memset(row->entries, 0, sizeof(struct tabular_entry) * cfg->column_count);
    634
    635	return row;
    636}
    637
    638int
    639tabular_free_rows(const struct tabular_cfg *cfg, struct tabular_row *rows)
    640{
    641	struct tabular_row *iter, *next;
    642	size_t i;
    643	int rc;
    644
    645	for (iter = rows; iter; iter = next) {
    646		next = iter->next;
    647		for (i = 0; i < cfg->column_count; i++) {
    648			rc = cfg->allocator->free(cfg->allocator,
    649				iter->entries[i].str);
    650			if (rc) return -rc;
    651		}
    652		rc = cfg->allocator->free(cfg->allocator, iter->entries);
    653		if (rc) return -rc;
    654		rc = cfg->allocator->free(cfg->allocator, iter);
    655		if (rc) return -rc;
    656	}
    657
    658	return 0;
    659}
    660
    661void
    662tabular_load_row_entry_hidden(const struct tabular_cfg *cfg,
    663	struct tabular_row *row, size_t col)
    664{
    665	bool hidden;
    666
    667	if (row->entries[col].flags & BIT(TABULAR_ENTRY_HIDDEN_SET)) return;
    668
    669	if (cfg->columns[col].is_hidden) {
    670		hidden = cfg->columns[col].is_hidden(
    671			&row->user, &cfg->columns[col].user);
    672	} else {
    673		hidden = false;
    674	}
    675
    676	if (hidden) {
    677		row->entries[col].flags |= BIT(TABULAR_ENTRY_HIDDEN);
    678	} else {
    679		row->entries[col].flags &= ~BIT(TABULAR_ENTRY_HIDDEN);
    680	}
    681
    682	row->entries[col].flags |= BIT(TABULAR_ENTRY_HIDDEN_SET);
    683}
    684
    685void
    686tabular_load_row_entry_str(const struct tabular_cfg *cfg,
    687	struct tabular_row *row, size_t col)
    688{
    689	if (row->entries[col].flags & BIT(TABULAR_ENTRY_STR_SET)) return;
    690
    691	row->entries[col].str = cfg->columns[col].to_str(
    692		&row->user, &cfg->columns[col].user);
    693	row->entries[col].ulen = (uint32_t) u8strlen(row->entries[col].str);
    694	row->entries[col].flags |= BIT(TABULAR_ENTRY_STR_SET);
    695}