Repository navigation
Extending
Tablix is deliberately small, but every seam is load-bearing and documented. This page is the how-to for each extension axis — with the exact files you'll touch, in order.
Type codes are wire format, so pick the next free code and never reuse
one (1 int · 2 float · 3 string → bool = 4, timestamp = 5 …).
-
libtblx/schema.go— add the constant, itsString()name, and accept it inValidate(). Decide the cycle order inNext()/Prev()(the wizard rotates through this). -
libtblx/writer.go— a new case inwriteValue: encode one value, and enforce the Go type (bool,int64-nanoseconds, …). -
libtblx/reader.go— the mirror case inReadColumn. -
libtblx/converter.go—ConvertCSVparsing rule and, if sensible, letGuessTypesdetect it. -
libtblx/clib/clib.go— the packed encoding for the new type intbl_col_data/tbl_writer_set_col(document it in the package comment; bit-packedboolwould be 1 bit per value — but thenlengthsmath changes, so v1-style 1 byte/value is the safe start). -
libtblx/python/tblx.py— the constant,_TYPE_NAMES, and the pack/unpack lines in_encode_column/_decode_column. -
tblxweb(src/lib/tbl.ts+ playground) — keep the website's mirror engine honest; it exists precisely to catch drift like this. - Spec — amend §6 and bump nothing else: new codes are backwards-compatible for readers that reject unknown types.
Tests to add: a round-trip fixture file and a Go↔Python byte-identity case (see the conformance idea below).
Commands are a flat switch in tblx/cmd/tblx/main.go (run()),
each backed by one small cmdX() function that composes libtblx.
case "describe":
if len(args) != 2 { return fmt.Errorf("usage: tblx describe <file.tblx>") }
return cmdDescribe(args[1])Conventions worth keeping: errors wrap with %w and mention the file;
human output goes to stdout, diagnostics to stderr; anything
interactive lives in internal/tui, never in main.go.
Ideas that fit the architecture especially well:
-
describe— min/max/mean/distincts per column, computed by reading one column at a time (the format's payoff, on display). -
slice --cols a,b --rows 1000:2000— partial reads; print bytes-read vs file-size to prove the seek index works. -
query— register columns as a SQLite virtual table and hand users real SQL over a columnar file. -
diff a.tblx b.tblx— schema diff + row diff.
Both models are plain Bubble Tea Models in tblx/internal/tui/:
-
view.gopre-renders the table to padded strings once; scroll (j/k,pgup/pgdnviaLineUp/LineDown— portable across bubbles versions) and pan (←/→shifting a rune offset) never re-decode data. Sort/filter features should operate on the pre-rendered lines, not on the decoded table. -
import.gois the wizard; the type cycle comes fromtblx.DataType.Next()/Prev(), and initial guesses fromtblx.GuessTypes— extend the library, not the TUI.
Charm pins matter: bubbletea v0.25, bubbles v0.18 (Update returns
(tea.Model, tea.Cmd); no HalfPageUp yet), lipgloss v0.10.
The C ABI is the contract (Library#the-c-abi):
-
make libinlibtblx→libtblx.so+libtblx.h. - Declare the signatures in your FFI of choice.
- Implement the ownership dance: copy out of every returned pointer,
then
tbl_freeit; check negative returns and calltbl_last_error. - Decode/encode columns with the packed layouts — for numerics this is a bulk memcpy/frombuffer, not a loop.
- Add a round-trip test against a fixture written by the Go core.
The flags byte per column is the sanctioned extension point
(Spec#11-versioning--the-future). A zstd sketch:
- writer: compress each block, set
flags |= 0x01, store the compressed size in the length array; - reader: if the bit is set, decompress before decoding;
ColLenreports the on-disk size, a newColLenUncompressedthe rest; - old readers see an unknown bit and refuse the column loudly — which is exactly the intended failure mode.
NULLs (0x02) would prepend a presence bitmap to the block; the value
bytes for absent rows stay the type default, keeping offsets stable.
Rule of thumb: a flag bit may change how a block is stored, never how it is located — the length array must stay trustworthy.
-
Debian/Ubuntu — mirror
tblx/packaging/rpm/tblx.specas adebian/directory; the build is one staticgo build, so the packaging is almost entirely metadata. -
Nix — a flake wrapping the same build;
CGO_ENABLED=0keeps it trivial. -
AUR — the existing
PKGBUILDis already AUR-shaped (pkgname=tblx, source tarball,makepkg); submit it as-is. - Whatever the target: keep the
sed -i '/^replace /d' go.mod && go mod tidystep — distro builds must resolvelibtblxfrom GitHub, not from a sibling checkout.
The format's value is that independent implementations agree byte for byte. The test suite worth building:
-
Golden files — a handful of committed
.tblxfixtures (empty table, all three types, unicode strings, ragged CSV) with expected SHA-256s; the writer must reproduce them exactly. - Go ↔ Python identity — write with Go, read with Python (via the C bridge), write back, compare hashes.
-
Fuzz the reader —
go test -fuzzwith random/truncated/corrupt inputs; the reader must return errors, never panic or hang. - Website mirror tests — the TypeScript engine encodes/decodes in the browser; a vitest suite comparing its output to the golden files catches spec drift at PR time.
- Errors:
fmt.Errorf("tblx: …: %w", err)— prefix, context, wrap. - The format core stays stdlib-only; dependencies belong in the CLI.
- Docs live in three places that must agree: godoc comments, the Spec, and the website. Change all three in one PR.
TBLX · Tablix
Repositories