Repository navigation
Description
Slice 3 of 3 under #953. Stacks on #988 — open the PR with base set to slice 2's branch.
Scope
Render query results as a table when stdout is a TTY, keeping the JSON array for pipes.
Why
Slices 1 and 2 reuse the existing JSON array serialization, which is right for dirsql query and wrong for a REPL. SELECT * FROM './' in a 5k-file repo dumps a 5000-element JSON array at a human being.
The two modes have genuinely different consumers: one-shot output is piped to jq, REPL output is read with eyes. sqlite3 prints a table for the same reason. This is a real product decision, which is why it is its own slice rather than a detail of slice 1.
Behavior
- stdout is a TTY → table
- stdout is piped/redirected → JSON array, byte-identical to
dirsql querytoday
Note this keys on stdout, not stdin. dirsql > out.json from an interactive terminal should still produce JSON — the destination decides the format, not the input source. Worth confirming that reading against slice 1's stdin-based TTY check, which governs prompting, a separate concern.
Open question — resolve before implementing
Once two formats exist, can the user choose?
.mode is the obvious answer and dot-commands were explicitly ruled out (see #953). Alternatives:
- Infer from TTY only — no user control. Simplest, no new surface, but no way to get JSON interactively or a table in a pipe.
- A
--format {auto,table,json}flag valid in both REPL and one-shot mode, defaulting toauto. Adds one documented flag instead of a meta-command namespace, and benefitsdirsql querytoo.
Option 2 is the better fit if control is wanted at all: it stays in flag-space rather than reopening the dot-command question. Decide before implementing.
Also to settle
- Column width strategy: truncate, wrap, or terminal-width-aware. Long
contentvalues will dominate otherwise. - NULL rendering, and how blobs/binary display.
- Row-count footer, and whether large results need paging (probably out of scope — say so explicitly rather than silently omitting).
Testing
- Snapshot testing is the natural fit: curtaincall's
term.to_snapshot()serializes the screen as a box-drawn string, designed to pair with syrupy. Snapshot the rendered table over a fixed fixture directory. - Pipe path: assert the JSON is byte-identical to
dirsql queryfor the same SQL — that is the regression that matters most here. - Unit: table-rendering logic is pure, so it unit-tests cleanly with no mocking. Plus
unit mutation.
Checklist
- Open question above resolved and recorded in the PR
- Red tests pushed as their own commit; CI observed red for the right reason before implementation
- Changelog fragment under
packages/rust/changelog.d/ - Migration fragment if
--format(or equivalent) changes any existing output path - Docs:
docs/reference/cli.mddocuments both formats and the selection rule -
PARITY.md: no drift (CLI is one shared implementation) - Re-attest both bindings
- Manual run — eyeball the table in a real terminal, then pipe it and diff against
dirsql query
Activity
dirsqlopens an interactive REPL #953feat(cli): render rows as a table when they are read rather than piped
test: pin `--format` and the table rendering (RED)