summaryrefslogtreecommitdiffstats
path: root/lib/libtabular
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libtabular')
m---------lib/libtabular0
-rw-r--r--lib/libtabular/.gitignore8
-rw-r--r--lib/libtabular/.gitmodules0
-rw-r--r--lib/libtabular/.subgitrc9
-rw-r--r--lib/libtabular/build.jst0
-rw-r--r--lib/libtabular/build.rmk.tmpl70
-rwxr-xr-xlib/libtabular/configure8
-rw-r--r--lib/libtabular/include/tabular.h133
-rw-r--r--lib/libtabular/lib/liballoc/.gitignore7
-rw-r--r--lib/libtabular/lib/liballoc/LICENSE21
-rw-r--r--lib/libtabular/lib/liballoc/Makefile45
-rw-r--r--lib/libtabular/lib/liballoc/build.jst51
-rw-r--r--lib/libtabular/lib/liballoc/build.rmk.tmpl60
-rw-r--r--lib/libtabular/lib/liballoc/common.mk9
-rwxr-xr-xlib/libtabular/lib/liballoc/configure5
-rw-r--r--lib/libtabular/lib/liballoc/include/allocator.h22
-rw-r--r--lib/libtabular/lib/liballoc/liballoc.api4
-rw-r--r--lib/libtabular/lib/liballoc/liballoc.lds7
-rw-r--r--lib/libtabular/lib/liballoc/src/allocator.c150
-rw-r--r--lib/libtabular/lib/liballoc/src/test.c22
-rw-r--r--lib/libtabular/libtabular.api7
-rw-r--r--lib/libtabular/libtabular.lds7
-rw-r--r--lib/libtabular/src/tabular.c695
-rw-r--r--lib/libtabular/src/test.c154
-rw-r--r--lib/libtabular/src/util.c146
-rw-r--r--lib/libtabular/src/util.h25
26 files changed, 1665 insertions, 0 deletions
diff --git a/lib/libtabular b/lib/libtabular
deleted file mode 160000
-Subproject f518cb2853fa164b4c4d3871da464c8f54fc600
diff --git a/lib/libtabular/.gitignore b/lib/libtabular/.gitignore
new file mode 100644
index 0000000..65056fc
--- /dev/null
+++ b/lib/libtabular/.gitignore
@@ -0,0 +1,8 @@
+compile_commands.json
+build
+build.rmk
+.cache
+.gdb_history
+vgcore*
+/.subgit
+/.subrepo
diff --git a/lib/libtabular/.gitmodules b/lib/libtabular/.gitmodules
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/libtabular/.gitmodules
diff --git a/lib/libtabular/.subgitrc b/lib/libtabular/.subgitrc
new file mode 100644
index 0000000..0ec8d87
--- /dev/null
+++ b/lib/libtabular/.subgitrc
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+declare -A subgit subgitinfo
+
+subgit[lib/liballoc]=1
+subgitinfo[lib/liballoc/remote]='git@sinitax.com:sinitax/liballoc-c'
+subgitinfo[lib/liballoc/branch]='master'
+subgitinfo[lib/liballoc/commit]='fd51196ac2d1bf1c34c8578146ef1bc66c1c8ec4'
+
diff --git a/lib/libtabular/build.jst b/lib/libtabular/build.jst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/libtabular/build.jst
diff --git a/lib/libtabular/build.rmk.tmpl b/lib/libtabular/build.rmk.tmpl
new file mode 100644
index 0000000..5f874ed
--- /dev/null
+++ b/lib/libtabular/build.rmk.tmpl
@@ -0,0 +1,70 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef LIBTABULAR_DEBUG
+#define DEBUG 1
+#endif
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include -I lib/liballoc/include #{OPT_CFLAGS}
+ #{EXTRA_CFLAGS} #{LIBTABULAR_EXTRA_CFLAGS}
+
+rule liba
+ #{CC} -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=libtabular.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ #{CC} -o $out $in $cflags -shared -Wl,-version-script libtabular.lds
+
+rule cc
+ #{CC} -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target lib/liballoc/build/liballoc.a
+ rmk lib/liballoc
+
+target build/libtabular.a
+ liba src/tabular.c src/util.c lib/liballoc/build/liballoc.a
+ | build include/tabular.h libtabular.api
+
+target build/libtabular.so
+ libso src/tabular.c src/util.c
+ | build include/tabular.h libtabular.lds
+
+target build/test
+ cc src/test.c build/libtabular.a lib/liballoc/build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ rmk clean
+ rmk -C lib/liballoc cleanall
+
+command install
+ install -m644 include/tabular.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+ install -m755 build/libtabular.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/libtabular.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/tabular.h"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libtabular.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/libtabular.so"
+
+command all
+ rmk build/libtabular.a build/libtabular.so build/test
diff --git a/lib/libtabular/configure b/lib/libtabular/configure
new file mode 100755
index 0000000..b4dd05d
--- /dev/null
+++ b/lib/libtabular/configure
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+tmpl "$@" build.rmk.tmpl > build.rmk
+for lib in ./lib/*; do
+ pushd $lib
+ ./configure "$@"
+ popd
+done
diff --git a/lib/libtabular/include/tabular.h b/lib/libtabular/include/tabular.h
new file mode 100644
index 0000000..b3d1c54
--- /dev/null
+++ b/lib/libtabular/include/tabular.h
@@ -0,0 +1,133 @@
+#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_WRAP,
+ TABULAR_WRAP_WORDAWARE
+};
+
+enum {
+ TABULAR_ENTRY_HIDDEN,
+ TABULAR_ENTRY_STR_SET,
+ TABULAR_ENTRY_HIDDEN_SET,
+};
+
+struct tabular_user {
+ union {
+ void *ptr;
+ size_t id;
+ };
+};
+
+struct tabular_entry {
+ char *str;
+ uint32_t flags;
+ uint32_t ulen;
+};
+
+struct tabular_row {
+ struct tabular_user user;
+ struct tabular_entry *entries;
+ struct tabular_row *next;
+};
+
+struct tabular_col {
+ struct tabular_user user;
+
+ /* column name displayed in header */
+ const char *name;
+
+ /* create str entry to display from user objects */
+ char *(*to_str)(const struct tabular_user *user_row,
+ const struct tabular_user *user_col);
+
+ /* only show content if atleast one row is not 'hidden' */
+ bool (*is_hidden)(const struct tabular_user *user_row,
+ const struct tabular_user *user_col);
+
+ /* size restrictions */
+ size_t minwidth, maxwidth;
+
+ /* whitespace padding */
+ size_t lpad, rpad;
+
+ /* text alignment */
+ int align;
+
+ /* content packing strategy */
+ int strategy;
+
+ /* omiting column due to col contraints disallowed */
+ bool essential;
+
+ /* reducing length due to col contraints allowed */
+ bool squashable;
+};
+
+struct tabular_cfg {
+ struct tabular_user user;
+
+ 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;
+
+ /* lazy load rows */
+ struct tabular_row *(*row_gen)(const struct tabular_user *user);
+
+ /* style printing hook for header, separators and content */
+ 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);
+
+void tabular_load_row_entry_hidden(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col);
+void tabular_load_row_entry_str(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col);
+
+struct tabular_row *tabular_alloc_row(const struct tabular_cfg *cfg,
+ int *error, struct tabular_user user);
+int tabular_free_rows(const struct tabular_cfg *cfg, struct tabular_row *rows);
diff --git a/lib/libtabular/lib/liballoc/.gitignore b/lib/libtabular/lib/liballoc/.gitignore
new file mode 100644
index 0000000..b6a670a
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/.gitignore
@@ -0,0 +1,7 @@
+compile_commands.json
+build
+build.rmk
+.cache
+vgcore*
+.gdb_history
+test
diff --git a/lib/libtabular/lib/liballoc/LICENSE b/lib/libtabular/lib/liballoc/LICENSE
new file mode 100644
index 0000000..361f116
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Louis Burda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/libtabular/lib/liballoc/Makefile b/lib/libtabular/lib/liballoc/Makefile
new file mode 100644
index 0000000..dab8818
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/Makefile
@@ -0,0 +1,45 @@
+PREFIX ?= /usr/local
+LIBDIR ?= /lib
+INCLDIR ?= /include
+
+CFLAGS = -I include
+
+ifeq "$(DEBUG)" "1"
+CFLAGS += -Og -g
+else
+CFLAGS += -O2
+endif
+
+all: build/liballoc.so build/liballoc.a build/test
+
+clean:
+ rm -rf build
+
+cleanall: clean
+
+build:
+ mkdir build
+
+build/liballoc.a: src/allocator.c | build
+ $(CC) -o build/tmp.o src/allocator.c $(CFLAGS) -r
+ objcopy --keep-global-symbols=liballoc.api build/tmp.o build/fixed.o
+ ar rcs $@ build/fixed.o
+
+build/liballoc.so: src/allocator.c include/allocator.h | build
+ $(CC) -o $@ src/allocator.c $(CFLAGS) \
+ -shared -Wl,-version-script liballoc.lds
+
+build/test: src/test.c build/liballoc.a | build
+ $(CC) -o $@ $^ -I include
+
+install:
+ install -m755 include/allocator.h -t "$(DESTDIR)$(PREFIX)$(INCLDIR)"
+ install -m755 build/liballoc.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+ install -m755 build/liballoc.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(INCLDIR)/allocator.h"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.a"
+ rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/liballoc.so"
+
+.PHONY: all clean cleanall install uninstall
diff --git a/lib/libtabular/lib/liballoc/build.jst b/lib/libtabular/lib/liballoc/build.jst
new file mode 100644
index 0000000..f590d49
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/build.jst
@@ -0,0 +1,51 @@
+
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include -O2
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "/usr/local/lib"
+ install -m755 build/liballoc.so -t "/usr/local/lib"
+ install -m644 include/allocator.h -t "/usr/local/include"
+
+command uninstall
+ rm -f "/usr/local/lib/liballoc.a"
+ rm -f "/usr/local/lib/liballoc.so"
+ rm -f "/usr/local/include/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/libtabular/lib/liballoc/build.rmk.tmpl b/lib/libtabular/lib/liballoc/build.rmk.tmpl
new file mode 100644
index 0000000..410749d
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/build.rmk.tmpl
@@ -0,0 +1,60 @@
+#default PREFIX /usr/local
+#default INCLDIR /include
+#default LIBDIR /lib
+#default CC gcc
+
+#ifdef DEBUG
+#define OPT_CFLAGS -Og -g
+#else
+#define OPT_CFLAGS -O2
+#endif
+
+cflags = -Wunused-function -Wunused-variable -Wconversion -Wformat
+ -I include #{OPT_CFLAGS} #{EXTRA_CFLAGS}
+
+rule liba
+ gcc -o $out.tmp.o $in $cflags -r
+ objcopy --keep-global-symbols=liballoc.api $out.tmp.o $out.fixed.o
+ ar rcs $out $out.fixed.o
+ rm $out.tmp.o $out.fixed.o
+
+rule libso
+ gcc -o $out $in $cflags -shared -Wl,-version-script liballoc.lds
+
+rule cc
+ gcc -o $out $in $cflags
+
+rule mkdir
+ mkdir $out
+
+target build
+ mkdir
+
+target build/liballoc.a
+ liba src/allocator.c | include/allocator.h build
+
+target build/liballoc.so
+ libso src/allocator.c | include/allocator.h build
+
+target build/test
+ cc src/test.c build/liballoc.a | build
+
+command clean
+ rm -rf build
+
+command cleanall
+ just clean
+
+command install
+ install -m755 build/liballoc.a -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m755 build/liballoc.so -t "#{DESTDIR}#{PREFIX}#{LIBDIR}"
+ install -m644 include/allocator.h -t "#{DESTDIR}#{PREFIX}#{INCLDIR}"
+
+command uninstall
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.a"
+ rm -f "#{DESTDIR}#{PREFIX}#{LIBDIR}/liballoc.so"
+ rm -f "#{DESTDIR}#{PREFIX}#{INCLDIR}/allocator.h"
+
+command all
+ just build/liballoc.a build/liballoc.so build/test
+
diff --git a/lib/libtabular/lib/liballoc/common.mk b/lib/libtabular/lib/liballoc/common.mk
new file mode 100644
index 0000000..fe00d79
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/common.mk
@@ -0,0 +1,9 @@
+LIBALLOC_A = build/liballoc.a
+LIBALLOC_A_SRC = src/allocator.c
+LIBALLOC_A_DEP = $(LIBALLOC_A_SRC) include/allocator.h
+LIBALLOC_A_SRCDEP = $(LIBALLOC_A_DEP)
+
+LIBALLOC_SO = build/liballoc.so
+LIBALLOC_SO_SRC = src/allocator.c
+LIBALLOC_SO_DEP = $(LIBALLOC_SO_SRC) include/allocator.h
+LIBALLOC_SO_SRCDEP = $(LIBALLOC_SO_DEP)
diff --git a/lib/libtabular/lib/liballoc/configure b/lib/libtabular/lib/liballoc/configure
new file mode 100755
index 0000000..16d5220
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/configure
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -ex
+
+tmpl "$@" build.rmk.tmpl > build.rmk
diff --git a/lib/libtabular/lib/liballoc/include/allocator.h b/lib/libtabular/lib/liballoc/include/allocator.h
new file mode 100644
index 0000000..fc9a13d
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/include/allocator.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <stddef.h>
+
+struct allocator {
+ void *(*alloc)(const struct allocator *allocator,
+ size_t size, int *rc);
+ void *(*realloc)(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+ int (*free)(const struct allocator *allocator, void *p);
+};
+
+struct strict_allocator {
+ const struct allocator *allocator_ul;
+ struct allocator allocator;
+};
+
+void strict_allocator_init(struct strict_allocator *strict_allocator_init,
+ const struct allocator *allocator_ul);
+
+extern const struct allocator stdlib_heap_allocator;
+extern const struct allocator stdlib_strict_heap_allocator;
diff --git a/lib/libtabular/lib/liballoc/liballoc.api b/lib/libtabular/lib/liballoc/liballoc.api
new file mode 100644
index 0000000..e875b2b
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/liballoc.api
@@ -0,0 +1,4 @@
+strict_allocator_init
+
+stdlib_heap_allocator
+stdlib_strict_heap_allocator
diff --git a/lib/libtabular/lib/liballoc/liballoc.lds b/lib/libtabular/lib/liballoc/liballoc.lds
new file mode 100644
index 0000000..07e5fca
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/liballoc.lds
@@ -0,0 +1,7 @@
+LIBALLOC_2.1 {
+ global:
+ strict_allocator_init;
+ stdlib_heap_allocator;
+ stdlib_strict_heap_allocator;
+ local: *;
+};
diff --git a/lib/libtabular/lib/liballoc/src/allocator.c b/lib/libtabular/lib/liballoc/src/allocator.c
new file mode 100644
index 0000000..eb6bc6d
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/src/allocator.c
@@ -0,0 +1,150 @@
+#include "allocator.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+static void *stdlib_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_heap_free(const struct allocator *allocator, void *p);
+
+static void *stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc);
+static void *stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc);
+static int stdlib_strict_heap_free(const struct allocator *allocator, void *p);
+
+const struct allocator stdlib_heap_allocator = {
+ .alloc = stdlib_heap_alloc,
+ .realloc = stdlib_heap_realloc,
+ .free = stdlib_heap_free
+};
+
+const struct allocator stdlib_strict_heap_allocator = {
+ .alloc = stdlib_strict_heap_alloc,
+ .realloc = stdlib_strict_heap_realloc,
+ .free = stdlib_strict_heap_free
+};
+
+void *
+stdlib_heap_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (rc && !p) *rc = errno;
+
+ return p;
+}
+
+void *
+stdlib_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (rc && !np) *rc = errno;
+
+ return np;
+}
+
+int
+stdlib_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+stdlib_strict_heap_alloc(const struct allocator *allocator,
+ size_t size, int *rc)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+stdlib_strict_heap_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ void *np;
+
+ np = realloc(p, size);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+stdlib_strict_heap_free(const struct allocator *allocator, void *p)
+{
+ free(p);
+
+ return 0;
+}
+
+void *
+strict_allocator_alloc(const struct allocator *allocator, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *p;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ p = strict_allocator->allocator_ul->alloc(
+ strict_allocator->allocator_ul, size, rc);
+ if (!p) abort();
+
+ return p;
+}
+
+void *
+strict_allocator_realloc(const struct allocator *allocator,
+ void *p, size_t size, int *rc)
+{
+ const struct strict_allocator *strict_allocator;
+ void *np;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ np = strict_allocator->allocator_ul->realloc(
+ strict_allocator->allocator_ul, p, size, rc);
+ if (!np) abort();
+
+ return np;
+}
+
+int
+strict_allocator_free(const struct allocator *allocator, void *p)
+{
+ const struct strict_allocator *strict_allocator;
+ int rc;
+
+ strict_allocator = ((void *) allocator)
+ - offsetof(struct strict_allocator, allocator);
+
+ rc = strict_allocator->allocator_ul->free(
+ strict_allocator->allocator_ul, p);
+ if (rc) abort();
+
+ return 0;
+}
+
+void
+strict_allocator_init(struct strict_allocator *strict_allocator,
+ const struct allocator *allocator_ul)
+{
+ strict_allocator->allocator_ul = allocator_ul;
+ strict_allocator->allocator.alloc = strict_allocator_alloc;
+ strict_allocator->allocator.realloc = strict_allocator_realloc;
+ strict_allocator->allocator.free = strict_allocator_free;
+}
diff --git a/lib/libtabular/lib/liballoc/src/test.c b/lib/libtabular/lib/liballoc/src/test.c
new file mode 100644
index 0000000..7b3ee44
--- /dev/null
+++ b/lib/libtabular/lib/liballoc/src/test.c
@@ -0,0 +1,22 @@
+#include "allocator.h"
+
+#include <err.h>
+#include <string.h>
+#include <stdlib.h>
+
+const struct allocator *ga = &stdlib_heap_allocator;
+
+int
+main(int argc, const char **argv)
+{
+ struct test *test;
+ int rc;
+
+ if (argc <= 1) exit(1);
+
+ test = ga->alloc(ga, strtoull(argv[1], NULL, 10), &rc);
+ if (!test) errx(1, "alloc: %s", strerror(rc));
+
+ rc = ga->free(ga, test);
+ if (rc) errx(1, "free: %s", strerror(rc));
+}
diff --git a/lib/libtabular/libtabular.api b/lib/libtabular/libtabular.api
new file mode 100644
index 0000000..79ca84a
--- /dev/null
+++ b/lib/libtabular/libtabular.api
@@ -0,0 +1,7 @@
+tabular_format
+
+tabular_load_row_entry_str
+tabular_load_row_entry_hidden
+
+tabular_alloc_row
+tabular_free_rows
diff --git a/lib/libtabular/libtabular.lds b/lib/libtabular/libtabular.lds
new file mode 100644
index 0000000..5b1a674
--- /dev/null
+++ b/lib/libtabular/libtabular.lds
@@ -0,0 +1,7 @@
+LIBTABULAR_1.0 {
+ global:
+ tabular_format;
+ tabular_alloc_row;
+ tabular_free_rows;
+ local: *;
+};
diff --git a/lib/libtabular/src/tabular.c b/lib/libtabular/src/tabular.c
new file mode 100644
index 0000000..88fab17
--- /dev/null
+++ b/lib/libtabular/src/tabular.c
@@ -0,0 +1,695 @@
+#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>
+
+#define BIT(i) (1U << (i))
+
+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);
+static size_t calc_row_width(struct fmt_state *fmt);
+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, *nstr;
+ 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, " \v\t\r\n");
+ str += offset;
+
+ nstr = str;
+ nwidth = width = 0;
+ while (nwidth <= maxwidth) {
+ width = nwidth;
+ str = nstr;
+ if (!str) break;
+ sep = strchr(str, ' ');
+ if (!sep) {
+ wlen = u8strlen(str);
+ nstr = NULL;
+ } else {
+ wlen = u8strnlen(str, (size_t) (sep - str));
+ nstr = 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;
+ str = nstr; /* skip single word */
+ } else {
+ *out_width = width;
+ }
+ }
+
+ 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;
+
+ 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 off, wwidth, max_wwidth, rowcnt;
+ struct tabular_row *row;
+
+ max_wwidth = cfg->columns[col].minwidth;
+
+ rowcnt = 0;
+ for (row = rows; row && rowcnt < limit; row = row->next) {
+ calc_word_aware(&row->entries[col],
+ maxwidth, &off, &wwidth, NULL);
+ max_wwidth = MAX(max_wwidth, wwidth);
+ rowcnt += 1;
+ }
+
+ return max_wwidth;
+}
+
+size_t
+calc_row_width(struct fmt_state *fmt)
+{
+ size_t sum, i;
+
+ sum = 0;
+ for (i = 0; i < fmt->column_count; i++) {
+ if (fmt->columns[i].hidden) continue;
+ sum += fmt->columns[i].width + (sum ? fmt->hseplen : 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_WRAP:
+ entry_lines = CEILDIV(row->entries[i].ulen, width);
+ break;
+ case TABULAR_WRAP_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, remaining;
+ ssize_t i;
+
+ 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;
+ if (cfg->columns[i].squashable) {
+ fmt->columns[i].width = cfg->columns[i].minwidth;
+ } else {
+ width = MIN(fmt->columns[i].maxlen,
+ cfg->columns[i].maxwidth - cfg->columns[i].lpad
+ - cfg->columns[i].rpad);
+ if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE)
+ width = recalc_col_word_aware(cfg, rows,
+ limit, (size_t) i, width);
+ fmt->columns[i].width = width + cfg->columns[i].lpad
+ + cfg->columns[i].rpad;
+ }
+ }
+
+ /* could not fit all necessary columns at minimum width */
+ width = calc_row_width(fmt);
+ while (width > fullwidth) {
+ /* remove non-essential columns */
+ for (i = ((ssize_t) 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);
+ }
+
+ /* 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;
+ if (!cfg->columns[i].squashable) continue;
+ width = MIN(fmt->columns[i].maxlen, cfg->columns[i].maxwidth
+ - fmt->columns[i].lpad - fmt->columns[i].rpad);
+ width = MIN(width, fmt->columns[i].width + remaining
+ - fmt->columns[i].lpad - fmt->columns[i].rpad);
+ if (cfg->columns[i].strategy == TABULAR_WRAP_WORDAWARE)
+ width = recalc_col_word_aware(cfg, rows,
+ limit, (size_t) i, width);
+ 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 */
+ copy = cfg->allocator->alloc(cfg->allocator,
+ sizeof(struct col_state) * cfg->column_count, &rc);
+ if (!copy) return -rc;
+ memcpy(copy, fmt->columns, sizeof(struct col_state) * cfg->column_count);
+
+ /* unhide cols */
+ for (i = 0; i < cfg->column_count; i++) {
+ tabular_load_row_entry_hidden(cfg, row, i);
+ if (fmt->columns[i].hidden) {
+ fmt->columns[i].hidden =
+ (row->entries[i].flags & BIT(TABULAR_ENTRY_HIDDEN));
+ }
+ if (fmt->columns[i].hidden) continue;
+ }
+
+ /* update maxlen for visible cols */
+ for (i = 0; i < cfg->column_count; i++) {
+ tabular_load_row_entry_str(cfg, row, i);
+ 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(cfg->allocator, copy);
+
+ return 0;
+
+undo:
+ memcpy(fmt->columns, copy, sizeof(struct col_state) * fmt->column_count);
+ cfg->allocator->free(cfg->allocator, 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 != NULL && *cfg->vsep)
+ && (cfg->xsep != NULL && *cfg->xsep));
+
+ 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;
+ fmt->columns = cfg->allocator->alloc(cfg->allocator,
+ fmt->column_count * sizeof(struct col_state), &rc);
+ if (!fmt->columns) 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;
+ }
+
+ fmt->row_limit = 0;
+
+ if (!*rows && cfg->row_gen) *rows = cfg->row_gen(&cfg->user);
+ if (!*rows) return 0;
+
+ if (!recalc_cols(cfg, stats, fmt, *rows, 0)) {
+ stats->cols_truncated = true;
+ stats->rows_truncated = true;
+ return 0;
+ }
+
+ for (row = rows; ; row = &(*row)->next) {
+ if (!*row && cfg->row_gen) *row = cfg->row_gen(&cfg->user);
+ if (!*row) break;
+ rc = calc_params_row(cfg, stats, *rows,
+ *row, fmt, fmt->row_limit + 1);
+ if (rc < 0) return rc;
+ if (rc > 0) {
+ stats->rows_truncated = true;
+ 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;
+
+ if (cfg->columns[i].strategy == TABULAR_TRUNC) {
+ width = fmt->columns[i].width
+ - fmt->columns[i].lpad - fmt->columns[i].rpad;
+ if (row->entries[i].ulen > width) {
+ width = u8rawlen(row->entries[i].str, width - 2);
+ row->entries[i].str[width] = '.';
+ row->entries[i].str[width+1] = '.';
+ row->entries[i].str[width+2] = '\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;
+
+ if (!row->entries[i].str) {
+ print_pad(file, fmt->columns[i].width);
+ continue;
+ }
+
+ entry = row->entries[i].str + fmt->columns[i].written;
+
+ 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_WRAP_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");
+
+ 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\r\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;
+ }
+
+ 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->lines_used = 0;
+ stats->rows_displayed = 0;
+ stats->rows_truncated = false;
+ stats->cols_truncated = false;
+
+ if (!rows) return 1;
+
+ if (!cfg->column_count) 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_WRAP
+ && cfg->columns[i].strategy != TABULAR_WRAP_WORDAWARE)
+ 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(cfg->allocator, fmt.columns);
+ if (rc) return -rc;
+
+ return 0;
+}
+
+struct tabular_row *
+tabular_alloc_row(const struct tabular_cfg *cfg,
+ int *rc, struct tabular_user user)
+{
+ struct tabular_row *row;
+
+ row = cfg->allocator->alloc(cfg->allocator,
+ sizeof(struct tabular_row), rc);
+ if (!row && rc) *rc = -*rc;
+ if (!row) return NULL;
+
+ row->next = NULL;
+ row->user = user;
+ row->entries = cfg->allocator->alloc(cfg->allocator,
+ sizeof(struct tabular_entry) * cfg->column_count, rc);
+ if (!row->entries && rc) *rc = -*rc;
+ if (!row->entries) return NULL;
+ memset(row->entries, 0, sizeof(struct tabular_entry) * cfg->column_count);
+
+ return row;
+}
+
+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(cfg->allocator,
+ iter->entries[i].str);
+ if (rc) return -rc;
+ }
+ rc = cfg->allocator->free(cfg->allocator, iter->entries);
+ if (rc) return -rc;
+ rc = cfg->allocator->free(cfg->allocator, iter);
+ if (rc) return -rc;
+ }
+
+ return 0;
+}
+
+void
+tabular_load_row_entry_hidden(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col)
+{
+ bool hidden;
+
+ if (row->entries[col].flags & BIT(TABULAR_ENTRY_HIDDEN_SET)) return;
+
+ if (cfg->columns[col].is_hidden) {
+ hidden = cfg->columns[col].is_hidden(
+ &row->user, &cfg->columns[col].user);
+ } else {
+ hidden = false;
+ }
+
+ if (hidden) {
+ row->entries[col].flags |= BIT(TABULAR_ENTRY_HIDDEN);
+ } else {
+ row->entries[col].flags &= ~BIT(TABULAR_ENTRY_HIDDEN);
+ }
+
+ row->entries[col].flags |= BIT(TABULAR_ENTRY_HIDDEN_SET);
+}
+
+void
+tabular_load_row_entry_str(const struct tabular_cfg *cfg,
+ struct tabular_row *row, size_t col)
+{
+ if (row->entries[col].flags & BIT(TABULAR_ENTRY_STR_SET)) return;
+
+ row->entries[col].str = cfg->columns[col].to_str(
+ &row->user, &cfg->columns[col].user);
+ row->entries[col].ulen = (uint32_t) u8strlen(row->entries[col].str);
+ row->entries[col].flags |= BIT(TABULAR_ENTRY_STR_SET);
+}
diff --git a/lib/libtabular/src/test.c b/lib/libtabular/src/test.c
new file mode 100644
index 0000000..f33fab1
--- /dev/null
+++ b/lib/libtabular/src/test.c
@@ -0,0 +1,154 @@
+#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 *row, const struct tabular_user *col);
+bool col_pos_hidden(const struct tabular_user *user);
+
+char *col_name_str(const struct tabular_user *row, const struct tabular_user *col);
+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 = 80,
+ .lpad = 0,
+ .rpad = 0,
+
+ .align = TABULAR_ALIGN_LEFT,
+
+ .strategy = TABULAR_WRAP_WORDAWARE,
+ .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 *row, const struct tabular_user *col)
+{
+ char buf[16];
+
+ snprintf(buf, sizeof(buf), "%li", row->id);
+
+ return strdup(buf);
+}
+
+char *
+col_name_str(const struct tabular_user *row, const struct tabular_user *col)
+{
+ return strdup(argv[row->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,
+ (struct tabular_user) { .id = (size_t) i });
+ if (!*end) errx(1, "tabular_append_row %i", rc);
+ end = &(*end)->next;
+ }
+
+ rc = tabular_format(stdout, &cfg, &stats, &rows);
+ if (rc) errx(1, "tabular_format %i", rc);
+
+ printf("\n%lu lines, %lu rows\n",
+ stats.lines_used, stats.rows_displayed);
+
+ tabular_free_rows(&cfg, rows);
+}
diff --git a/lib/libtabular/src/util.c b/lib/libtabular/src/util.c
new file mode 100644
index 0000000..c2baa24
--- /dev/null
+++ b/lib/libtabular/src/util.c
@@ -0,0 +1,146 @@
+#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;
+
+ if (!str) return 0;
+
+ 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/lib/libtabular/src/util.h b/lib/libtabular/src/util.h
new file mode 100644
index 0000000..a325b18
--- /dev/null
+++ b/lib/libtabular/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);
+
+