libtabular-c

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

commit 46972dd2694b2b7ba4395861787c6a63d6dd4d53
Author: Louis Burda <quent.burda@gmail.com>
Date:   Sat, 18 Mar 2023 18:42:12 +0100

Initial version

Diffstat:
A.gitignore | 3+++
A.gitmodules | 3+++
AMakefile | 48++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/tabular.h | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/liballoc | 1+
Alibtabular.api | 3+++
Alibtabular.lds | 7+++++++
Asrc/tabular.c | 636+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util.h | 25+++++++++++++++++++++++++
11 files changed, 1124 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +compile_commands.json +build +.cache diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/liballoc"] + path = lib/liballoc + url = git@sinitax.com:snx/liballoc diff --git a/Makefile b/Makefile @@ -0,0 +1,48 @@ +PREFIX ?= /usr/local +LIBDIR ?= /lib +INCLDIR ?= /include + +CFLAGS = -I src -I include -I lib/liballoc/include -Wformat +CFLAGS += -Wconversion -Wunused-variable -Wunused-function + +ifeq "$(DEBUG)" "1" +CFLAGS += -g +endif + +SRC = src/tabular.c src/util.c + +all: build/libtabular.a build/libtabular.so build/test + +clean: + rm -rf build + +build: + mkdir build + +lib/liballoc/build/liballoc.a: + make -C lib/liballoc build/liballoc.a + +build/libtabular.a: $(SRC) include/tabular.h libtabular.api | build + $(CC) -o build/tmp.o $(SRC) $(CFLAGS) -r + objcopy --keep-global-symbols=libtabular.api build/tmp.o build/fixed.o + ar rcs $@ build/fixed.o + +build/libtabular.so: $(SRC) include/tabular.h libtabular.lds | build + $(CC) -o $@ $(SRC) -fPIC $(CFLAGS) \ + -shared -Wl,-version-script libtabular.lds + +build/test: src/test.c build/libtabular.a \ + lib/liballoc/build/liballoc.a | build + $(CC) -o $@ $^ $(CFLAGS) + +install: + install -m644 include/tabular.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)" + install -m755 build/libtabular.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)" + install -m755 build/libtabular.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/tabular.h" + rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libtabular.so" + rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libtabular.a" + +.PHONY: all clean install uninstall diff --git a/include/tabular.h b/include/tabular.h @@ -0,0 +1,102 @@ +#pragma once + +#include "allocator.h" + +#include <wchar.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <wctype.h> + +enum { + TABULAR_ALIGN_LEFT, + TABULAR_ALIGN_CENTER, + TABULAR_ALIGN_RIGHT +}; + +enum { + TABULAR_TRUNC, + TABULAR_SQUASH, + TABULAR_SQUASH_WORDAWARE +}; + +struct tabular_user { + union { + void *ptr; + size_t id; + }; +}; + +struct tabular_entry { + char *str; + bool avail; + size_t ulen; +}; + +struct tabular_row { + struct tabular_user user; + struct tabular_entry *entries; + + struct tabular_row *next; +}; + +struct tabular_col { + const char *name; + + struct tabular_user user; + + char *(*to_str)(const struct tabular_user *id); + bool (*is_hidden)(const struct tabular_user *id); + + size_t minwidth, maxwidth; + size_t lpad, rpad; + + int align; + + int strategy; + + bool essential; +}; + +struct tabular_cfg { + const struct tabular_col *columns; + size_t column_count; + + /* color mode: 1, 16, 256 */ + int colors; + + /* fit rows to output size */ + bool fit_rows; + + /* amount of available lines to skip */ + size_t skip_lines; + + /* output dimensions */ + size_t outw, outh; + + /* entry separators */ + const char *hsep, *vsep, *xsep; + + /* total lpad, rpad */ + size_t lpad, rpad; + + bool (*print_style)(FILE *file, const struct tabular_cfg *cfg, + const struct tabular_row *row, const struct tabular_col *col); + + const struct allocator *allocator; +}; + +struct tabular_stats { + bool rows_truncated; + bool cols_truncated; + size_t rows_displayed; + size_t lines_used; +}; + +int tabular_format(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stat, struct tabular_row *rows); + +struct tabular_row **tabular_alloc_row(const struct tabular_cfg *cfg, + int *error, struct tabular_row **rows, struct tabular_user user); +int tabular_free_rows(const struct tabular_cfg *cfg, struct tabular_row *rows); diff --git a/lib/liballoc b/lib/liballoc @@ -0,0 +1 @@ +Subproject commit 22d274eb7a43de119af51cf46a892aa6be6a7d91 diff --git a/libtabular.api b/libtabular.api @@ -0,0 +1,3 @@ +tabular_format +tabular_alloc_row +tabular_free_rows diff --git a/libtabular.lds b/libtabular.lds @@ -0,0 +1,7 @@ +LIBTABULAR_1.0 { + global: + tabular_format; + tabular_alloc_row; + tabular_free_rows; + local: *; +}; diff --git a/src/tabular.c b/src/tabular.c @@ -0,0 +1,636 @@ +#define _XOPEN_SOURCE + +#include "tabular.h" +#include "util.h" + +#include <sys/ioctl.h> +#include <wchar.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stddef.h> + +struct col_state { + size_t width; + size_t written; + size_t maxlen; + size_t lpad, rpad; + bool hidden; +}; + +struct fmt_state { + struct col_state *columns; + size_t column_count; + + size_t hseplen; + size_t vseplen; + size_t xseplen; + size_t mseplen; + + size_t line_limit; + size_t row_limit; + + bool header_line; +}; + +static void calc_word_aware(struct tabular_entry *entry, size_t maxwidth, + size_t *offset, size_t *width, size_t *lines); +static size_t recalc_col_word_aware(const struct tabular_cfg *cfg, + struct tabular_row *rows, size_t limit, + size_t col, size_t maxwidth, size_t remaining); +static size_t calc_row_width(struct col_state *cols, size_t count); +static size_t calc_output_lines(const struct tabular_cfg *cfg, + struct fmt_state *fmt, struct tabular_row *rows, size_t limit); +static bool recalc_cols(const struct tabular_cfg *cfg, + struct tabular_stats *stats, struct fmt_state *fmt, + struct tabular_row *rows, size_t limit); + +static int calc_params_row(const struct tabular_cfg *cfg, + struct tabular_stats *stats, struct tabular_row *rows, + struct tabular_row *row, struct fmt_state *fmt, size_t limit); +static int calc_params(const struct tabular_cfg *cfg, + struct tabular_stats *stat, struct tabular_row *rows, + struct fmt_state *fmt); + +static void output_header(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stat, const struct fmt_state *fmt); +static void output_row(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stat, const struct tabular_row *row, + struct fmt_state *fmt); +static int output_rows(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stats, struct tabular_row *rows, + struct fmt_state *fmt); + +const char * +calc_word_aware_line(const char *str, size_t maxwidth, + size_t *out_offset, size_t *out_width) +{ + const char *sep; + size_t width, nwidth; + size_t offset, wlen; + + if (!str) { + if (out_offset) *out_offset = 0; + if (out_width) *out_width = 0; + return NULL; + } + + offset = strspn(str, " "); + str += offset; + + nwidth = width = 0; + while (nwidth <= maxwidth) { + if (!str) break; + sep = strchr(str, ' '); + if (!sep) { + wlen = u8strlen(str); + str = NULL; + } else { + wlen = u8strnlen(str, (size_t) (sep - str)); + str = sep + 1; + } + nwidth = width + (width > 0) + wlen; + } + + if (out_offset) *out_offset = offset; + if (out_width) { + /* single word > maxwidth */ + if (!width && nwidth) + *out_width = maxwidth; + else + *out_offset = offset; + } + + return str; +} + +void +calc_word_aware(struct tabular_entry *entry, size_t maxwidth, + size_t *out_offset, size_t *out_width, size_t *out_lines) +{ + const char *str; + size_t width, mwidth; + size_t lines; + + if (out_offset) *out_offset = 0; + + if (!entry->str) { + if (out_width) *out_width = 0; + if (out_lines) *out_lines = 1; + return; + } + + lines = 0; + mwidth = 0; + str = entry->str; + while (str) { + str = calc_word_aware_line(str, maxwidth, + str == entry->str ? out_offset : NULL, &width); + lines++; + mwidth = MAX(mwidth, width); + } + + if (out_width) *out_width = mwidth; + if (out_lines) *out_lines = lines; +} + +size_t +recalc_col_word_aware(const struct tabular_cfg *cfg, + struct tabular_row *rows, size_t limit, size_t col, + size_t maxwidth, size_t remaining) +{ + size_t off, wwidth, max_wwidth, rowcnt; + struct tabular_row *row; + + max_wwidth = cfg->columns[col].minwidth; + + rowcnt = 0; + for (row = rows; row; row = row->next) { + calc_word_aware(&row->entries[col], + maxwidth, &off, &wwidth, NULL); + max_wwidth = MAX(max_wwidth, wwidth); + + rowcnt += 1; + if (rowcnt == limit) break; + } + + return max_wwidth; +} + +size_t +calc_row_width(struct col_state *cols, size_t count) +{ + size_t sum, i; + + sum = 0; + for (i = 0; i < count; i++) { + if (cols[i].hidden) continue; + sum += cols[i].width + (sum ? 1 : 0); + } + + return sum; +} + +size_t +calc_output_lines(const struct tabular_cfg *cfg, struct fmt_state *fmt, + struct tabular_row *rows, size_t limit) +{ + struct tabular_row *row; + size_t row_index, row_lines; + size_t entry_lines; + size_t lines, i; + size_t width; + + lines = 0; + row_index = 0; + for (row = rows; row; row = row->next) { + if (row_index == limit) break; + + row_lines = 0; + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + width = fmt->columns[i].width + - fmt->columns[i].lpad - fmt->columns[i].rpad; + switch (cfg->columns[i].strategy) { + case TABULAR_TRUNC: + entry_lines = 1; + break; + case TABULAR_SQUASH: + entry_lines = CEILDIV(row->entries[i].ulen, width); + break; + case TABULAR_SQUASH_WORDAWARE: + calc_word_aware(&row->entries[i], + width, NULL, NULL, &entry_lines); + break; + } + row_lines = MAX(row_lines, entry_lines); + } + lines += row_lines; + + row_index += 1; + } + + return lines; +} + +bool +recalc_cols(const struct tabular_cfg *cfg, struct tabular_stats *stats, + struct fmt_state *fmt, struct tabular_row *rows, size_t limit) +{ + size_t width, fullwidth; + size_t i, remaining; + + fullwidth = cfg->outw - cfg->lpad - cfg->rpad; + + /* reset widths to minimum requirement */ + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + fmt->columns[i].width = cfg->columns[i].minwidth; + } + + /* could not fit all necessary columns at minimum width */ + width = calc_row_width(fmt->columns, fmt->column_count); + while (width > fullwidth) { + /* remove non-essential columns */ + for (i = cfg->column_count - 1; i >= 0; i--) { + if (!cfg->columns[i].essential + && !fmt->columns[i].hidden) { + fmt->columns[i].hidden = true; + break; + } + } + + /* failed to remove any more */ + if (i < 0) return false; + + stats->cols_truncated = true; + width = calc_row_width(fmt->columns, fmt->column_count); + } + + /* redistribute excess width to columns, left to right */ + remaining = fullwidth - width; + for (i = 0; remaining > 0 && i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + width = MIN(fmt->columns[i].maxlen, cfg->columns[i].maxwidth); + width = MIN(width, fmt->columns[i].width + remaining); + width = width - fmt->columns[i].lpad - fmt->columns[i].rpad; + if (cfg->columns[i].strategy == TABULAR_SQUASH_WORDAWARE) + width = recalc_col_word_aware(cfg, rows, + limit, i, width, remaining); + width = MAX(width + fmt->columns[i].lpad + fmt->columns[i].rpad, + fmt->columns[i].width); + remaining -= width - fmt->columns[i].width; + fmt->columns[i].width = width; + } + + return true; +} + +int +calc_params_row(const struct tabular_cfg *cfg, struct tabular_stats *stats, + struct tabular_row *rows, struct tabular_row *row, + struct fmt_state *fmt, size_t limit) +{ + struct colinfo *copy; + size_t i, height; + int rc; + + /* make copy of current width in case the next row does not fit */ + rc = cfg->allocator->alloc((void **)&copy, + sizeof(struct col_state) * cfg->column_count); + if (rc) return -rc; + memcpy(copy, fmt->columns, sizeof(struct col_state) * cfg->column_count); + + /* unhide cols and update maxlen */ + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden && cfg->columns[i].to_str) { + if (cfg->columns[i].is_hidden) { + fmt->columns[i].hidden = + cfg->columns[i].is_hidden(&row->user); + } else { + fmt->columns[i].hidden = false; + } + } + if (fmt->columns[i].hidden) continue; + + if (!row->entries[i].avail) { + row->entries[i].str = cfg->columns[i].to_str(&row->user); + row->entries[i].ulen = (uint32_t) u8strlen(row->entries[i].str); + row->entries[i].avail = true; + } + + fmt->columns[i].maxlen = MAX(fmt->columns[i].maxlen, + row->entries[i].ulen); + } + + /* recalc column sizes to fit new column */ + if (!recalc_cols(cfg, stats, fmt, rows, limit)) + goto undo; + + height = calc_output_lines(cfg, fmt, rows, limit); + + /* check the line limit if applicable */ + if (fmt->line_limit > 0 && height > fmt->line_limit) + goto undo; + + cfg->allocator->free(copy); + + return 0; + +undo: + memcpy(fmt->columns, copy, sizeof(struct col_state) * fmt->column_count); + cfg->allocator->free(copy); + + return 1; +} + +int +calc_params(const struct tabular_cfg *cfg, struct tabular_stats *stats, + struct tabular_row *rows, struct fmt_state *fmt) +{ + struct tabular_row *row; + ssize_t first, last; + size_t i; + int rc; + + fmt->header_line = (cfg->vsep != 0 && cfg->xsep != 0); + + fmt->line_limit = 0; + if (cfg->fit_rows) { + fmt->line_limit = MAX(0, cfg->outh + - cfg->skip_lines - 1 - fmt->header_line); + } + + fmt->hseplen = u8strlen(cfg->hsep); + fmt->vseplen = u8strlen(cfg->vsep); + fmt->xseplen = u8strlen(cfg->xsep); + fmt->mseplen = MAX(fmt->hseplen, fmt->xseplen); + + fmt->column_count = cfg->column_count; + rc = cfg->allocator->alloc((void **)&fmt->columns, + fmt->column_count * sizeof(struct col_state)); + if (rc) return -rc; + + for (i = 0; i < cfg->column_count; i++) { + fmt->columns[i].lpad = cfg->columns[i].lpad; + fmt->columns[i].rpad = cfg->columns[i].rpad; + fmt->columns[i].hidden = true; + fmt->columns[i].width = cfg->columns[i].minwidth; + fmt->columns[i].maxlen = u8strlen(cfg->columns[i].name) + + fmt->columns[i].lpad + fmt->columns[i].rpad; + if (fmt->columns[i].maxlen > cfg->columns[i].minwidth) + return 1; + fmt->columns[i].written = 0; + } + + if (!recalc_cols(cfg, stats, fmt, rows, 0)) { + stats->cols_truncated = true; + stats->rows_truncated = true; + return 0; + } + + fmt->row_limit = 0; + for (row = rows; row; row = row->next) { + rc = calc_params_row(cfg, stats, rows, + row, fmt, fmt->row_limit + 1); + if (rc < 0) return rc; + if (rc > 0) break; + fmt->row_limit++; + } + + first = last = -1; + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + if (first < 0) first = (ssize_t) i; + last = (ssize_t) i; + } + + if (first >= 0 && last >= 0) { + fmt->columns[first].lpad += cfg->lpad; + fmt->columns[first].width += cfg->lpad; + fmt->columns[last].rpad += cfg->rpad; + fmt->columns[last].width += cfg->rpad; + } + + return 0; +} + +void +output_header(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stats, const struct fmt_state *fmt) +{ + size_t i, k, width; + bool dirty, first; + + first = true; + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + if (!first) { + dirty = cfg->print_style(file, cfg, NULL, NULL); + fprintf(file, "%s%*.s", cfg->hsep, + (int) (fmt->mseplen - fmt->hseplen), ""); + if (dirty) fprintf(file, "\x1b[0m"); + } + first = false; + + width = fmt->columns[i].width + - fmt->columns[i].lpad - fmt->columns[i].rpad; + + dirty = cfg->print_style(file, cfg, + NULL, &cfg->columns[i]); + print_pad(file, fmt->columns[i].lpad); + print_left(file, cfg->columns[i].name, width, width); + print_pad(file, fmt->columns[i].rpad); + if (dirty) fprintf(file, "\x1b[0m"); + } + + fprintf(file, "\n"); + stats->lines_used += 1; + + if (fmt->header_line) { + first = true; + dirty = cfg->print_style(file, cfg, NULL, NULL); + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + if (!first) { + fprintf(file, "%s%*.s", cfg->xsep, + (int) (fmt->mseplen - fmt->xseplen), ""); + } + for (k = 0; k < fmt->columns[i].width; k++) + fprintf(file, "%s", cfg->vsep); + first = false; + } + if (dirty) fprintf(file, "\x1b[0m"); + fprintf(file, "\n"); + stats->lines_used += 1; + } +} + +void +output_row(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stat, const struct tabular_row *row, + struct fmt_state *fmt) +{ + size_t wwidth, padwidth, outlen; + size_t i, off, width; + bool first, done, dirty; + char *entry; + + for (i = 0; i < cfg->column_count; i++) + fmt->columns[i].written = 0; + + do { + done = true; + first = true; + for (i = 0; i < cfg->column_count; i++) { + if (fmt->columns[i].hidden) continue; + + if (!first) { + dirty = cfg->print_style(file, cfg, NULL, NULL); + fprintf(file, "%s", cfg->hsep); + if (dirty) fprintf(file, "\x1b[0m"); + } + first = false; + + entry = row->entries[i].str + fmt->columns[i].written; + + if (cfg->columns[i].strategy == TABULAR_TRUNC) { + if (u8strlen(entry) > fmt->columns[i].width) { + width = fmt->columns[i].width; + entry[MAX(width - 2, 0)] = '.'; + entry[MAX(width - 1, 0)] = '.'; + entry[MAX(width - 0, 0)] = '\0'; + } + } + + dirty = cfg->print_style(file, cfg, row, &cfg->columns[i]); + + off = 0; + width = fmt->columns[i].width + - fmt->columns[i].lpad - fmt->columns[i].rpad; + padwidth = width; + if (cfg->columns[i].strategy == TABULAR_SQUASH_WORDAWARE) { + calc_word_aware_line(entry, width, &off, &wwidth); + entry += off; + width = wwidth; + } + + print_pad(file, fmt->columns[i].lpad); + if (cfg->columns[i].align == TABULAR_ALIGN_LEFT) { + print_left(file, entry, width, padwidth); + } else if (cfg->columns[i].align == TABULAR_ALIGN_RIGHT) { + print_right(file, entry, width, padwidth); + } else { + print_center(file, entry, width, padwidth); + } + print_pad(file, fmt->columns[i].rpad); + + if (dirty) fprintf(file, "\x1b[0m"); + + if (cfg->columns[i].strategy == TABULAR_TRUNC) { + fmt->columns[i].written += u8strlen(entry); + continue; + } + + outlen = MIN(u8strlen(entry), width + off); + fmt->columns[i].written += u8rawlen(entry, outlen); + + /* check if anything other than whitespace left */ + entry += u8rawlen(entry, outlen); + if (strspn(entry, " \t\v\n") != u8strlen(entry)) + done = false; + } + fprintf(file, "\n"); + stat->lines_used += 1; + } while (!done); + + stat->rows_displayed += 1; +} + +int +output_rows(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stats, struct tabular_row *rows, + struct fmt_state *fmt) +{ + struct tabular_row *row; + size_t count; + + if (fmt->row_limit == 0) return 0; + + output_header(file, cfg, stats, fmt); + + count = 0; + for (row = rows; row; row = row->next) { + if (count == fmt->row_limit) + break; + output_row(file, cfg, stats, row, fmt); + count += 1; + } + + stats->rows_truncated = !row; + + return 0; +} + +int +tabular_format(FILE *file, const struct tabular_cfg *cfg, + struct tabular_stats *stats, struct tabular_row *rows) +{ + struct fmt_state fmt; + size_t i; + int rc; + + stats->rows_displayed = 0; + stats->rows_truncated = false; + stats->cols_truncated = false; + stats->lines_used = 0; + + if (!cfg->column_count || !rows) + return 0; + + for (i = 0; i < cfg->column_count; i++) { + if (cfg->columns[i].minwidth > cfg->columns[i].maxwidth) + return 1; + if (cfg->columns[i].lpad + cfg->columns[i].rpad + >= cfg->columns[i].minwidth) + return 1; + if (cfg->columns[i].strategy != TABULAR_TRUNC + && cfg->columns[i].strategy != TABULAR_SQUASH) + return 1; + } + + rc = calc_params(cfg, stats, rows, &fmt); + if (rc) return rc; + + rc = output_rows(file, cfg, stats, rows, &fmt); + if (rc) return rc; + + rc = cfg->allocator->free(fmt.columns); + if (rc) return -rc; + + return 0; +} + +struct tabular_row ** +tabular_alloc_row(const struct tabular_cfg *cfg, int *rc, + struct tabular_row **end, struct tabular_user user) +{ + struct tabular_row *row; + + *rc = -cfg->allocator->alloc((void **)end, sizeof(struct tabular_row)); + if (*rc) return NULL; + + row = *end; + row->next = NULL; + row->user = user; + *rc = -cfg->allocator->alloc((void **)&row->entries, + sizeof(struct tabular_entry) * cfg->column_count); + if (*rc) return NULL; + memset(row->entries, 0, sizeof(struct tabular_entry) * cfg->column_count); + + return &row->next; +} + +int +tabular_free_rows(const struct tabular_cfg *cfg, struct tabular_row *rows) +{ + struct tabular_row *iter, *next; + size_t i; + int rc; + + for (iter = rows; iter; iter = next) { + next = iter->next; + for (i = 0; i < cfg->column_count; i++) { + rc = cfg->allocator->free(iter->entries[i].str); + if (rc) return -rc; + } + rc = cfg->allocator->free(iter->entries); + if (rc) return -rc; + rc = cfg->allocator->free(iter); + if (rc) return -rc; + } + + return 0; +} diff --git a/src/test.c b/src/test.c @@ -0,0 +1,153 @@ +#include "tabular.h" +#include "allocator.h" + +#include <sys/ioctl.h> +#include <err.h> +#include <string.h> +#include <stdbool.h> +#include <stdio.h> + +#define ARRLEN(x) (sizeof(x)/sizeof(*(x))) + +bool print_style(FILE *file, const struct tabular_cfg *cfg, + const struct tabular_row *row, const struct tabular_col *col); + +char *col_pos_str(const struct tabular_user *user); +bool col_pos_hidden(const struct tabular_user *user); + +char *col_name_str(const struct tabular_user *user); +bool col_name_hidden(const struct tabular_user *user); + +static const char **argv = NULL; + +static struct tabular_col columns[] = { + { + .name = "Pos", + + .to_str = col_pos_str, + .is_hidden = NULL, + + .minwidth = 3, + .maxwidth = 3, + .lpad = 0, + .rpad = 0, + + .align = TABULAR_ALIGN_CENTER, + + .strategy = TABULAR_TRUNC, + .essential = true, + }, + { + .name = "Value", + + .to_str = col_name_str, + .is_hidden = NULL, + + .minwidth = 5, + .maxwidth = 20, + .lpad = 0, + .rpad = 0, + + .align = TABULAR_ALIGN_LEFT, + + .strategy = TABULAR_SQUASH, + .essential = true, + }, +}; + +static struct tabular_cfg cfg = { + .colors = 256, + + .columns = columns, + .column_count = ARRLEN(columns), + + .fit_rows = true, + + .hsep = "│ ", + .vsep = "─", + .xsep = "┼─", + + .outw = 0, + .outh = 0, + + .lpad = 1, + .rpad = 1, + + .print_style = print_style, + + .skip_lines = 3, + + .allocator = &stdlib_heap_allocator +}; + +bool +print_style(FILE *file, const struct tabular_cfg *cfg, + const struct tabular_row *row, const struct tabular_col *col) +{ + if (cfg->colors == 256) { + if (!col) { /* separators */ + fprintf(file, "\x1b[90m"); + return true; + } else if (!row) { /* header */ + fprintf(file, "\x1b[1m"); + return true; + } else if (!strcmp(col->name, "Name")) { + fprintf(file, "\x1b[35m"); + return true; + } + } + + return false; +} + +char * +col_pos_str(const struct tabular_user *user) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%li", user->id); + + return strdup(buf); +} + +char * +col_name_str(const struct tabular_user *user) +{ + return strdup(argv[user->id]); +} + +int +main(int argc, const char **_argv) +{ + struct tabular_row *rows, **end; + struct tabular_stats stats; + struct winsize ws; + int i, rc; + + argv = _argv; + + rc = ioctl(1, TIOCGWINSZ, &ws); + if (!rc) { + cfg.outw = ws.ws_col; + cfg.outh = ws.ws_row; + } else { + cfg.outw = 80; + cfg.outh = 26; + } + + rows = NULL; + end = &rows; + for (i = 0; i < argc; i++) { + end = tabular_alloc_row(&cfg, &rc, end, + (struct tabular_user) { .id = (size_t) i }); + if (!end) errx(1, "tabular_append_row"); + } + + rc = tabular_format(stdout, &cfg, &stats, rows); + if (rc) errx(1, "tabular_format"); + + printf("\n%lu lines, %lu rows\n", + stats.lines_used, stats.rows_displayed); + + tabular_free_rows(&cfg, rows); +} diff --git a/src/util.c b/src/util.c @@ -0,0 +1,143 @@ +#include "util.h" + +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> + +size_t +u8strlen(const char *str) +{ + size_t i, len; + + if (!str) return 0; + + len = 0; + for (i = 0; str[i]; i++) { + if ((str[i] & 0xC0) != 0x80) + len += 1; + } + + return len; +} + +size_t +u8strnlen(const char *str, size_t max) +{ + size_t i, len; + + if (!str) return 0; + + len = 0; + for (i = 0; str[i] && i < max; i++) { + if ((str[i] & 0xC0) != 0x80) + len += 1; + } + + return len; +} + +size_t +u8rawlen(const char *str, size_t max) +{ + size_t i, len; + + len = 0; + for (i = 0; str[i] && len < max; i++) + if ((str[i] & 0xC0) != 0x80) { + len += 1; + } + + return i; +} + +size_t +print_pad(FILE *file, size_t len) +{ + fprintf(file, "%*.s", (int) len, ""); + return len; +} + +size_t +print_hex(FILE *file, const void *bytes, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + fprintf(file, "%02X", *(uint8_t *)(bytes + i)); + + return len * 2; +} + +size_t +print_trunc(FILE *file, const char *str, size_t width) +{ + size_t len; + + len = u8strlen(str); + if (len > width && width >= 2) { + fprintf(file, "%*.*s..", (int) width - 2, (int) width - 2, str); + } else if (len > width&& width < 2) { + fprintf(file, "%*.*s", (int) width, (int) width, ".."); + } else { + fprintf(file, "%s", str); + } + + return MIN(len, width); +} + +size_t +print_left(FILE *file, const char *str, size_t width, size_t padwidth) +{ + size_t rawlen, u8len; + ssize_t ret; + + u8len = MIN(u8strlen(str), width); + rawlen = u8rawlen(str, u8len); + + /* make up for invisible bytes */ + padwidth += rawlen - u8len; + + ret = fprintf(file, "%-*.*s", (int) padwidth, (int) rawlen, str); + + return (size_t) MAX(0, ret); +} + +size_t +print_center(FILE *file, const char *str, size_t width, size_t padwidth) +{ + size_t u8len, rawlen; + size_t lpad, rpad; + ssize_t ret; + + u8len = MIN(width, u8strlen(str)); + rawlen = u8rawlen(str, u8len); + lpad = MAX(padwidth - u8len, 0) / 2; + rpad = MAX(padwidth - u8len - lpad, 0); + + /* make up for invisible bytes */ + rpad += rawlen - u8len; + + ret = fprintf(file, "%*s%*.*s%*s", (int) lpad, "", + (int) rawlen, (int) rawlen, str, (int) rpad, ""); + + return (size_t) MAX(0, ret); +} + +size_t +print_right(FILE *file, const char *str, size_t width, size_t padwidth) +{ + size_t rawlen, u8len; + ssize_t ret; + + u8len = MIN(width, u8strlen(str)); + rawlen = u8rawlen(str, u8len); + + /* make up for invisible bytes */ + padwidth += rawlen - u8len; + + ret = fprintf(file, "%*.*s", (int) padwidth, (int) rawlen, str); + + return (size_t) MAX(0, ret); +} + diff --git a/src/util.h b/src/util.h @@ -0,0 +1,25 @@ +#pragma once + +#include <stdio.h> +#include <stdlib.h> + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define CEILDIV(a, b) (((a) / (b)) + !!((a) % (b))) + +size_t u8strlen(const char *str); +size_t u8strnlen(const char *str, size_t max); +size_t u8rawlen(const char *str, size_t max); + +size_t print_pad(FILE *file, size_t len); +size_t print_hex(FILE *file, const void *in, size_t len); + +size_t print_trunc(FILE *file, const char *str, size_t width); +size_t print_left(FILE *file, const char *str, + size_t width, size_t padwidth); +size_t print_center(FILE *file, const char *str, + size_t width, size_t padwidth); +size_t print_right(FILE *file, const char *str, + size_t width, size_t padwidth); + +