Files
workflow-miner/docs/research-rust-ecosystem.md
vikingowl 48923450f8 docs: add architecture plan and research notes
Initial project documentation for workflow-miner — a Rust CLI + zsh
plugin that mines recurring command workflows from Atuin shell history.
2026-02-22 08:41:50 +01:00

119 lines
4.0 KiB
Markdown

# Research: Rust Ecosystem & Dependencies
## SQLite Access: rusqlite
**Winner: [rusqlite](https://docs.rs/rusqlite/0.38) v0.38**
| Feature | rusqlite | sqlx |
|---------|----------|------|
| Async | No (sync) | Yes |
| SQLite-only | Yes | No (multi-DB) |
| Bundled SQLite | Yes (feature flag) | No |
| Read-only support | Yes (`SQLITE_OPEN_READ_ONLY`) | Yes |
Why rusqlite:
- Synchronous is fine for a CLI reading a local file
- `Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_ONLY)` ensures safety
- `features = ["bundled"]` includes SQLite 3.49.2 — no system dependency
- Simpler dependency tree than sqlx
- WAL-safe: reading an Atuin DB in WAL mode from a separate process is safe
```rust
use rusqlite::{Connection, OpenFlags};
let db = Connection::open_with_flags(
"~/.local/share/atuin/history.db",
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
)?;
```
## Shell Command Parsing
### Level 1: Word splitting — shell-words
[shell-words](https://docs.rs/shell-words) v1 — POSIX shell word splitting.
```rust
shell_words::split("git commit -m \"fix typo\"")
// → ["git", "commit", "-m", "fix typo"]
```
Simple, correct, well-maintained. Exactly what we need for command abstraction.
### Level 2: Full AST parsing (optional)
| Crate | Version | Description | Maturity |
|-------|---------|-------------|----------|
| [brush-parser](https://docs.rs/brush-parser) | 0.3.0 | Full POSIX/bash tokenizer + parser with AST | Active, part of brush-shell |
| [conch-parser](https://github.com/ipetkov/conch-parser) | 0.1.1 | Shell parser with AST builder | Stale |
| [mystsh](https://crates.io/crates/mystsh) | Early | POSIX/bash parser | Newer, active |
### Level 3: Variable expansion
[shellexpand](https://docs.rs/shellexpand) v3.1.1 — tilde and `$VAR` expansion. 10M+ downloads.
## TUI: ratatui
**Winner: [ratatui](https://github.com/ratatui/ratatui) v0.30**
| Library | Stars | Status |
|---------|-------|--------|
| **ratatui** | 18.6k | Active, community standard |
| cursive | ~3.8k | Maintained, less active |
| tui-rs | ~10k | **Unmaintained** (ratatui is the fork) |
Why ratatui:
- Immediate-mode rendering (you own the render loop)
- Widgets: Block, Paragraph, List, Table, Chart, BarChart, Tabs, etc.
- Backends: crossterm (default), termion, termwiz
- 100+ third-party widgets via [awesome-ratatui](https://github.com/ratatui/awesome-ratatui)
- 60+ FPS with complex layouts
- Since 0.30.0: modular architecture (ratatui-core, ratatui-widgets, ratatui-crossterm)
Relevant widgets for workflow dashboard:
- `Table` — displaying discovered patterns
- `List` — navigating workflows
- `BarChart` / `Sparkline` — frequency visualization
- `Tabs` — switching views
## CLI Framework: clap
[clap](https://docs.rs/clap/4) v4 with derive feature — standard, ergonomic.
## Sequential Pattern Mining
**No mature Rust crate exists.** Zero repos in Rust on the [sequential-pattern-mining GitHub topic](https://github.com/topics/sequential-pattern-mining).
Recommended: Implement PrefixSpan directly, porting from [PrefixSpan-py](https://github.com/chuanconggao/PrefixSpan-py). ~300-500 lines of Rust.
## Justfile Generation
No Rust crate for generating Justfiles. Format is simple enough for string formatting.
Key syntax rules:
- Recipe bodies use spaces (with `set indent`) or tabs
- Recipe names: lowercase, hyphens allowed
- Variables: `name := "value"`, interpolated with `{{name}}`
- Dependencies: `test: build lint`
- Parameters: `deploy target="default":`
- Shebang recipes: first line `#!`
- `@` prefix suppresses echo
- Validate with `just --fmt --check --unstable -f <file>`
## Recommended Cargo.toml
```toml
[workspace.dependencies]
rusqlite = { version = "0.38", features = ["bundled"] }
shell-words = "1"
ratatui = "0.30"
crossterm = "0.28"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
anyhow = "1"
dirs = "6"
```