feat(ui): add spinner, progress output, and summary table
This commit is contained in:
@@ -1029,3 +1029,57 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o
|
||||
- `dub test` passes — all 18 modules, including search.d's 7 unittests.
|
||||
- `dub run -- -Ss neovim` fails on lock file creation (`~/.cache/tofu` directory doesn't exist) — pre-existing issue, expected without initialized environment.
|
||||
- Evidence logged to `.omo/evidence/task-20-tofu-core.log`.
|
||||
|
||||
---
|
||||
|
||||
## Task 25 — `tofu.ui` (spinner, progress output, summary table)
|
||||
|
||||
### Architecture
|
||||
- Module `tofu.ui` — file `src/tofu/ui.d`, standalone module with no external dependencies beyond `tofu.log` and `core.thread`.
|
||||
- Three public APIs: `Spinner` class, `buildSeparator`, `summaryTable`.
|
||||
- `startSpinner(string label)` factory returns `Spinner` handle — creates and starts animation.
|
||||
|
||||
### Spinner — thread-based animation
|
||||
- **TTY detection**: `isatty(1)` from `core.sys.posix.unistd` — when stdout is NOT a terminal, spinner prints `"<label>..."` once and becomes a no-op on `stop()`.
|
||||
- **Animation**: background `core.thread.Thread` loops every 100 ms, writing `\r <frame> <label>` with frames `/`, `-`, `\`, `-` (same as ZETA `spinner.lua`).
|
||||
- **Stop**: sets `shared bool _running = false`, calls `Thread.join()` (blocks until thread exits — thread checks flag every 100 ms so this is prompt), then clears the animation line (`\r` + spaces + `\r`), then prints ` ok <label>` via `logOk`.
|
||||
- **`@safe` / `@trusted` split**: `start()` and `stop()` are `@safe` public; `threadFn()` is `@trusted` (calls stdout.writef/flush). Thread constructor + start/join are wrapped in `@trusted` blocks (Thread APIs are `@system` in DMD 2.112).
|
||||
|
||||
### Thread.join() — no Duration overload
|
||||
- D's `core.thread.Thread.join()` takes `bool rethrow = true`, NOT a `Duration`. The plan spec says "join with timeout" but this API doesn't exist in Phobos.
|
||||
- No timeout needed in practice: the thread exits within one 100 ms cycle after `_running` is cleared — join returns promptly.
|
||||
- The Lua reference uses `kill $pid` (signal-based), not join — the thread model is fundamentally different.
|
||||
|
||||
### buildSeparator
|
||||
- Prints `──── building <name> (<i>/<n>) ────` (U+2500 box-drawing chars, 4 per side).
|
||||
- Duplicates build.d's inline separator (line 247: `logStep("──── building %s (%d/%d) ────", ...)`) — documented duplication, do NOT modify build.d.
|
||||
- Uses plain `stdout.writeln` (no color/logStep) so test can assert exact output.
|
||||
|
||||
### summaryTable
|
||||
- Struct `InstallSummary { string name; string ver; string status; }`.
|
||||
- Fixed-width columns: `PACKAGE` (20 chars), `VERSION` (16 chars), `STATUS` (variable). Format: `%-20s %-16s %s`.
|
||||
- Empty array → no output.
|
||||
|
||||
### stdout capture in tests
|
||||
- `captureStdout()` — swaps global `stdout` to temp `File(name, "w+")`, runs dg, flushes, closes file, reads back with `std.file.readText`.
|
||||
- Must call `file.close()` before reading — `readText` opens the file independently and can't read while another handle exists.
|
||||
- Pattern adapted from `log.d`'s `capture()` but reads full multi-line content (log.d's version only calls `readln` for single-line output).
|
||||
|
||||
### Test cases (4/4 pass)
|
||||
1. Spinner with stdout redirected → no `\r` in captured output, label present ✓
|
||||
2. buildSeparator exact: UTF-8 `\xe2\x94\x80` (U+2500) chars with "building foo (1/3)" ✓
|
||||
3. summaryTable: 3 items → header PACKAGE/VERSION/STATUS + all 3 rows with correct statuses ✓
|
||||
4. Spinner plain-path: non-TTY mode prints label, stop() is no-op (no " ok " in output) ✓
|
||||
|
||||
### `canFind` usage in tests
|
||||
- Free function form: `canFind(haystack, needle)` — avoids D's UFCS auto-decoding issue on `string` (where `output.canFind("PACKAGE")` tries to find a `string` element in a `dchar` range).
|
||||
|
||||
### Pre-existing breakage
|
||||
- `src/tofu/commands/{install,remove,info}.d` from parallel tasks 20-24 have compile errors (function/delegate mismatches, import issues, `@safe` violations).
|
||||
- These block `dub build` and `dub test` — excluded via `.skip` rename for verification. Not caused by task 25.
|
||||
- `ui.d` compiles standalone: `dmd -c -o- -unittest -Isrc src/tofu/ui.d` passes clean.
|
||||
|
||||
### Build verified
|
||||
- `dub test` (with broken siblings excluded) — 19 modules pass unittests, including ui.d's 4 test blocks.
|
||||
- `dub build` (with broken siblings excluded) — blocked by `main.d` importing `tofu.commands.info` (skipped). ui.d itself compiles clean.
|
||||
- Evidence logged to `.omo/evidence/task-25-tofu-core.log`.
|
||||
|
||||
Reference in New Issue
Block a user