From 4770522e2e70ef5f40be4d85429f0fcd50d28b8e Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Thu, 14 May 2026 20:14:05 +0530 Subject: [PATCH] docs: add mdBook documentation site - book.toml with navy/ayu theme, fold enabled, build-dir = book/html - 15 pages covering introduction, installation, usage, architecture (overview, concurrency, UI/rendering, fuzzy search), all four backends (pacman, AUR/yay, APT, Homebrew), adding a new backend guide, configuration, contributing, coding guidelines, and roadmap - .github/workflows/docs.yml deploys to GitHub Pages on push to docs/mdbook branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docs.yml | 50 ++++++ .gitignore | 1 + book.toml | 20 +++ book/src/SUMMARY.md | 49 ++++++ book/src/architecture/concurrency.md | 65 ++++++++ book/src/architecture/fuzzy-search.md | 54 +++++++ book/src/architecture/overview.md | 90 +++++++++++ book/src/architecture/ui-rendering.md | 71 +++++++++ book/src/backends/apt.md | 53 +++++++ book/src/backends/aur-yay.md | 50 ++++++ book/src/backends/homebrew.md | 55 +++++++ book/src/backends/index.md | 86 +++++++++++ book/src/backends/new-backend.md | 171 +++++++++++++++++++++ book/src/backends/pacman.md | 78 ++++++++++ book/src/configuration.md | 41 +++++ book/src/contributing/coding-guidelines.md | 55 +++++++ book/src/contributing/index.md | 55 +++++++ book/src/installation.md | 62 ++++++++ book/src/introduction.md | 47 ++++++ book/src/roadmap.md | 46 ++++++ book/src/usage.md | 82 ++++++++++ 21 files changed, 1281 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 book.toml create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/architecture/concurrency.md create mode 100644 book/src/architecture/fuzzy-search.md create mode 100644 book/src/architecture/overview.md create mode 100644 book/src/architecture/ui-rendering.md create mode 100644 book/src/backends/apt.md create mode 100644 book/src/backends/aur-yay.md create mode 100644 book/src/backends/homebrew.md create mode 100644 book/src/backends/index.md create mode 100644 book/src/backends/new-backend.md create mode 100644 book/src/backends/pacman.md create mode 100644 book/src/configuration.md create mode 100644 book/src/contributing/coding-guidelines.md create mode 100644 book/src/contributing/index.md create mode 100644 book/src/installation.md create mode 100644 book/src/introduction.md create mode 100644 book/src/roadmap.md create mode 100644 book/src/usage.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..2601fb3 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,50 @@ +name: Deploy mdBook Docs + +on: + push: + branches: + - docs/mdbook + paths: + - "book/**" + - "book.toml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install mdBook + run: | + curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz \ + | tar -xz --directory /usr/local/bin + + - name: Build docs + run: mdbook build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: book/html + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 798e65b..0925116 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ target # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ /target +book/html diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..105b1c5 --- /dev/null +++ b/book.toml @@ -0,0 +1,20 @@ +[book] +title = "TRX — Package Manager TUI" +authors = ["pie-314"] +description = "Documentation for TRX, a fast keyboard-driven TUI package manager written in Rust." +language = "en" +src = "book/src" + +[build] +build-dir = "book/html" + +[output.html] +default-theme = "navy" +preferred-dark-theme = "ayu" +git-repository-url = "https://github.com/pie-314/trx" +edit-url-template = "https://github.com/pie-314/trx/edit/docs/mdbook/book/src/{path}" +site-url = "/trx/" + +[output.html.fold] +enable = true +level = 1 diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000..fe9fb1f --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,49 @@ +# Summary + +[Introduction](./introduction.md) + +--- + +# Getting Started + +- [Installation](./installation.md) +- [Usage](./usage.md) + +--- + +# Architecture + +- [Overview](./architecture/overview.md) +- [Concurrency Model](./architecture/concurrency.md) +- [UI & Rendering](./architecture/ui-rendering.md) +- [Fuzzy Search Engine](./architecture/fuzzy-search.md) + +--- + +# Package Manager Backends + +- [Overview](./backends/index.md) +- [Arch Linux — Pacman](./backends/pacman.md) +- [Arch Linux — AUR (yay)](./backends/aur-yay.md) +- [Debian / Ubuntu — APT](./backends/apt.md) +- [macOS — Homebrew](./backends/homebrew.md) +- [Adding a New Backend](./backends/new-backend.md) + +--- + +# Configuration + +- [Config File](./configuration.md) + +--- + +# Contributing + +- [Contributing Guide](./contributing/index.md) +- [Coding Guidelines](./contributing/coding-guidelines.md) + +--- + +# Roadmap + +- [Roadmap](./roadmap.md) diff --git a/book/src/architecture/concurrency.md b/book/src/architecture/concurrency.md new file mode 100644 index 0000000..c7826ea --- /dev/null +++ b/book/src/architecture/concurrency.md @@ -0,0 +1,65 @@ +# Concurrency Model + +TRX deliberately avoids an async runtime. All background work is done with **OS threads** and **`std::sync::mpsc` channels**. This keeps the dependency tree small, makes the code easy to reason about, and avoids the overhead of an executor in a single-user TUI. + +--- + +## Channel Architecture + +Two channels flow into the main event loop: + +| Channel | Producer | Consumer | Payload | +|---------|----------|----------|---------| +| `result_rx` | Search / list-load threads | `App::run` | `(String, Vec)` — a tag plus a list of packages | +| `details_rx` | Details-fetch threads | `App::run` | `DetailsState` | + +The tag in `result_rx` lets the event loop distinguish between results for **Search**, **Installed** (`"__INSTALLED__"`), and **Updates** (`"__UPDATES__"`). Stale results (where the tag no longer matches the current UI state) are discarded. + +--- + +## Search Flow + +``` +User types a character + │ + (50 ms debounce) + │ + App spawns OS thread + │ + ▼ + PackageManager::search(&query) ← runs system command, parses output, scores results + │ + result_tx.send((query, packages)) + │ + Main loop: result_rx.try_recv() + │ + App updates packages list + triggers details fetch +``` + +The **50 ms debounce** is implemented in `input.rs` / `app.rs`: `last_input_time` is refreshed on every keystroke. `check_and_execute_search` is called each frame and only fires a thread when `Instant::now() - last_input_time >= 50ms` and the query has changed. + +--- + +## Details Fetch Flow + +Whenever the selected row changes (navigation or new search results), `trigger_details_fetch` spawns a thread that calls `PackageManager::get_details`. Results arrive on `details_rx` and update the sidebar. + +A global `DETAILS_CACHE` (`Arc>>>`) prevents redundant system calls for packages that have been inspected before. + +--- + +## External Command Execution + +When the user triggers an install (`i`), remove (`x`), upgrade (`U`), or refresh (`R`), TRX must hand control of the terminal to the package manager's interactive output. This is handled by `execute_external_command` in `main.rs`: + +1. **Disable raw mode** — so the child process receives normal terminal I/O. +2. **Leave alternate screen** — the TUI disappears; the package manager's output is printed normally. +3. **Run the command** — via `std::process::Command`. +4. **Wait for Enter** — the user can review the output. +5. **Re-enter alternate screen and raw mode** — TRX redraws the TUI. + +--- + +## Thread Safety + +`manager` is wrapped in `Arc>` (where `PackageManager: Send + Sync`). Cloning the `Arc` into a spawned thread is the only synchronisation needed for backend calls. diff --git a/book/src/architecture/fuzzy-search.md b/book/src/architecture/fuzzy-search.md new file mode 100644 index 0000000..474fce8 --- /dev/null +++ b/book/src/architecture/fuzzy-search.md @@ -0,0 +1,54 @@ +# Fuzzy Search Engine + +The fuzzy search engine lives in `src/fuzzy/mod.rs`. It is intentionally self-contained — no external crates — and is optimised for the substring-heavy patterns typical in package names. + +--- + +## Public API + +```rust +/// Returns a score in [0.0, ∞). Returns 0.0 when there is no fuzzy match. +pub fn fuzzy_match(query: &str, target: &str) -> f64; + +/// Returns the character indices in `target` that match `query` in order, +/// or `None` if no such sequence exists. +pub fn fuzzy_get_indexes(query: &[char], target: &[char]) -> Option>; + +/// Computes the final score given the matched positions. +pub fn calculate_score(query: &[char], target: &[char], indices: &[usize]) -> f64; +``` + +--- + +## Matching Algorithm + +`fuzzy_get_indexes` performs a greedy left-to-right scan: for each character in `query` it finds the first remaining position in `target` that matches (case-insensitively). If any query character cannot be matched, `None` is returned and the package is excluded from results. + +--- + +## Scoring + +`calculate_score` is inspired by the [VS Code fuzzy finder algorithm](https://github.com/microsoft/vscode/blob/main/src/vs/base/common/fuzzyScorer.ts). The score rewards: + +| Condition | Bonus | +|-----------|-------| +| Every matched character | +1.0 | +| Consecutive run of matches | +1.0 + 0.3 × run length | +| Match at position 0 (start of name) | +4.0 | +| Match after a separator (`-`, `_`, `/`, `.`, ` `) | +2.5 | + +And penalises gaps between matched characters: + +| Condition | Penalty | +|-----------|---------| +| Gap of *n* characters between consecutive matches | −0.15 × n | + +The raw score is then normalised by `target_length * 0.15 + 1.0` to prevent long package names from dominating. + +--- + +## Integration + +`fuzzy_match` is called from `parse_alternating_lines` in `src/managers/mod.rs` for every package returned by a backend. Packages with a score ≤ 0.01 are dropped, and the remainder are sorted descending by score before being sent to the UI. + +Each backend's `search` method may also pass the query directly to the underlying tool (e.g. `pacman -Ss `), so the fuzzy layer acts as a **re-ranking** step on top of the backend's own filtering, not a replacement for it. diff --git a/book/src/architecture/overview.md b/book/src/architecture/overview.md new file mode 100644 index 0000000..e181d18 --- /dev/null +++ b/book/src/architecture/overview.md @@ -0,0 +1,90 @@ +# Architecture Overview + +TRX is split into a small set of focused modules. Each module has a single responsibility and communicates with the others through well-defined interfaces. + +``` +src/ +├── main.rs # Entry point, terminal setup, execute_external_command helper +├── config.rs # TOML configuration loading +├── updater.rs # GitHub release polling and binary self-replacement +├── ui/ +│ ├── mod.rs +│ ├── app.rs # App state, event loop, channel polling +│ ├── draw.rs # ratatui rendering logic +│ └── input.rs # InputMode enum, character-level editing, debounce state +├── managers/ +│ ├── mod.rs # PackageManager trait, Package struct, parse_alternating_lines, DETAILS_CACHE +│ ├── arch.rs # ArchManager — delegates to pacman.rs and yay.rs +│ ├── pacman.rs # Pacman system-call wrapper +│ ├── yay.rs # yay/AUR system-call wrapper +│ ├── apt.rs # AptManager +│ └── brew.rs # BrewManager +└── fuzzy/ + └── mod.rs # Scoring-based fuzzy match engine +``` + +--- + +## Key Data Structures + +### `Package` + +Defined in `src/managers/mod.rs`, this is the universal package representation passed through all layers of the application: + +```rust +pub struct Package { + pub provider: String, // e.g. "pacman", "aur", "apt", "brew" + pub name: String, // full name, possibly prefixed: "core/ripgrep" + pub version: String, + pub description: String, + pub score: f64, // fuzzy match score used for ranking +} +``` + +### `App` + +Defined in `src/ui/app.rs`. Holds all runtime state: + +- `input` — current search string +- `current_tab` — which of the three tabs is active +- `packages` — the currently displayed list +- `checked` / `selected_names` — multi-selection state +- `installed_packages` — `HashSet` fetched once on startup +- `details_state` — sidebar content (`Empty | Loading | Success | Error`) +- `loading` — drives the spinner in the header +- `manager` — `Arc>` shared across spawned threads + +--- + +## Module Interactions + +``` +keyboard event + │ + ▼ + App::run() ──── spawns thread ──► PackageManager::search() + │ │ + │◄── result_rx (mpsc) ◄───────────────┘ + │ + ▼ + draw_ui() (ratatui frame render) +``` + +The event loop in `App::run` does three things every iteration: + +1. **Poll keyboard** — via `crossterm::event::poll` with a short timeout so the loop never blocks long. +2. **Drain channels** — `try_recv` on `result_rx` and `details_rx` (non-blocking). +3. **Render** — call `draw_ui` to produce the next terminal frame. + +--- + +## Startup Sequence + +1. Parse CLI flags (`--version`, `--help`). +2. Call `updater::check_for_updates()` — if a newer release exists, self-update and exit. +3. Initialise the ratatui terminal (`ratatui::init`). +4. Load `Config` from the TOML file (or write defaults). +5. Call `managers::get_system_manager(&config)` to select the correct backend. +6. Create the `mpsc` channels and construct `App`. +7. Enter `App::run()` — the main event loop. +8. On exit, restore the terminal (`ratatui::restore`). diff --git a/book/src/architecture/ui-rendering.md b/book/src/architecture/ui-rendering.md new file mode 100644 index 0000000..9d9e2a9 --- /dev/null +++ b/book/src/architecture/ui-rendering.md @@ -0,0 +1,71 @@ +# UI & Rendering + +TRX's TUI is built on [ratatui](https://ratatui.rs/), a Rust library for building terminal UIs with a retained-mode, declarative rendering model. + +--- + +## Layout + +`draw_ui` in `src/ui/draw.rs` computes the full layout on every frame: + +``` +┌─────────────────────────────────────────┐ +│ Help header (1 line) │ +├─────────────────────────────────────────┤ +│ Tab bar (3 lines) │ +├───────────────────────┬─────────────────┤ +│ │ │ +│ Package list │ Details panel │ +│ + search input │ │ +│ (Search tab only) │ │ +│ │ │ +├─────────────────────────────────────────┤ +│ Status bar (1 line) │ +└─────────────────────────────────────────┘ +``` + +The split between the list and details panel is **responsive**: +- Terminal width ≥ 100 columns → 50 / 50 horizontal split +- Terminal width < 100 columns → 60 / 40 split (still horizontal but tighter) + +--- + +## Input Modes + +`InputMode` in `src/ui/input.rs` is a simple two-variant enum: + +```rust +pub enum InputMode { + Normal, + Editing, +} +``` + +- **Normal** — navigation, package operations, tab switching. +- **Editing** — the search input bar is focused; characters are routed to `App::enter_char` / `App::delete_char`. + +The cursor is hidden in Normal mode and shown at the current character position in Editing mode. + +--- + +## Spinner + +While a background search or list-load is in progress, `App::loading` is `true`. The draw layer reads `App::spinner_tick` (incremented each frame) to index into a Braille spinner sequence: + +```rust +const SPINNERS: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; +``` + +--- + +## Help Overlay + +Pressing `?` sets `App::show_help = true`. `draw_ui` renders a centred popup over the main content using the `centered_rect` helper, which computes a rectangle as a percentage of the terminal area. The overlay is cleared with the ratatui `Clear` widget before drawing the help text on top. + +--- + +## Performance + +- **Minimal redraws** — the event loop uses a short poll timeout (not a busy-wait), so frames are only rendered when there is user input or a channel message. +- **No double-buffer diffing overhead** — ratatui handles diffing internally; TRX simply calls `terminal.draw(|frame| draw_ui(frame, app))` each iteration. +- **Pure functions in draw layer** — `draw_ui` and its helpers take `&mut App` (for `ListState` selection) but do not mutate business logic, keeping rendering predictable and testable. diff --git a/book/src/backends/apt.md b/book/src/backends/apt.md new file mode 100644 index 0000000..7e4e38c --- /dev/null +++ b/book/src/backends/apt.md @@ -0,0 +1,53 @@ +# Debian / Ubuntu — APT + +The APT backend is implemented in `src/managers/apt.rs` as `AptManager` (a zero-size struct, since APT requires no runtime state). + +--- + +## Search + +`AptManager::search` calls `apt-cache search `, which outputs one package per line in `name - description` format: + +``` +ripgrep - recursively searches directories for a regex pattern +``` + +The name is extracted, fuzzy-scored against the query, and packages with score ≤ 0.01 are dropped. Note that `apt-cache search` does not return version numbers, so `Package::version` is empty for search results. + +--- + +## Installed Packages + +`get_installed` runs `dpkg-query -W -f='${Package}\n'` and returns a `HashSet`. + +--- + +## Package Details + +`get_details` runs `apt-cache show ` and parses the colon-separated RFC 822-style output into a `HashMap`. Keys include `Package`, `Version`, `Description`, `Depends`, `Homepage`, and others. + +--- + +## Install / Remove + +| Operation | Command | +|-----------|---------| +| Install | `sudo apt install ` | +| Remove | `sudo apt remove ` | + +Both operations hand control of the terminal to the interactive APT output via `execute_external_command`. + +--- + +## System Upgrade & Refresh + +| Key | Command | +|-----|---------| +| `U` | `sudo apt upgrade` | +| `R` | `sudo apt update` | + +--- + +## Updates + +`get_updates` parses the output of `apt list --upgradable` to build a list of packages with newer versions available. diff --git a/book/src/backends/aur-yay.md b/book/src/backends/aur-yay.md new file mode 100644 index 0000000..c4d97c8 --- /dev/null +++ b/book/src/backends/aur-yay.md @@ -0,0 +1,50 @@ +# Arch Linux — AUR (yay) + +The AUR backend in `src/managers/yay.rs` wraps an AUR helper (default: **yay**) using the same interface as the Pacman backend. The AUR helper is configurable — see [Configuration](../configuration.md). + +--- + +## AUR Helper Configuration + +The helper name is read from `Config::aur_helper` (default: `"yay"`). Any helper that accepts yay-compatible CLI flags should work: + +```toml +# ~/.config/trx/config.toml +aur_helper = "paru" +``` + +--- + +## Search + +`search_aur` calls ` -Ss ` and parses the alternating-line output via `parse_alternating_lines`. Packages receive the provider string `"aur"`. + +--- + +## Package Details + +`aur_details` runs ` -Si ` and parses the colon-separated output into a `HashMap`. The `Maintainer`, `URL`, `Votes`, and `Popularity` fields typically appear in AUR results. + +--- + +## Install + +`aur_install` runs ` -S ` via `execute_external_command`, handing control of the terminal to the helper's interactive output (which typically presents a PKGBUILD review step). + +--- + +## Provider Routing + +When the user selects packages for install, `ArchManager::install` inspects each package name: + +```rust +for name in pkgs { + if name.starts_with("aur/") { + yay::aur_install(terminal, &[name])?; + } else { + pacman::pacman_install(terminal, &[name])?; + } +} +``` + +Packages prefixed with `aur/` go to the AUR helper; all others go to pacman. diff --git a/book/src/backends/homebrew.md b/book/src/backends/homebrew.md new file mode 100644 index 0000000..e16af54 --- /dev/null +++ b/book/src/backends/homebrew.md @@ -0,0 +1,55 @@ +# macOS — Homebrew + +The Homebrew backend is implemented in `src/managers/brew.rs` as `BrewManager` (a zero-size struct). + +--- + +## Search + +`BrewManager::search` calls `brew search `, which returns one formula or cask name per line. Because `brew search` does not return descriptions or versions, both fields are left empty in the initial search results — they are populated lazily when the user selects a package and `get_details` is called. + +--- + +## Installed Packages + +`get_installed` calls `brew list --formula` and returns a `HashSet` of formula names. + +> **Note:** Casks (`brew list --cask`) are not yet included in the installed list. This is a known limitation tracked in the roadmap. + +--- + +## Package Details + +`get_details` calls `brew info --json=v2 ` and parses the JSON response. Key fields surfaced in the TUI sidebar include: + +- `name` +- `desc` +- `homepage` +- `versions.stable` +- `installed` (list of installed versions) + +--- + +## Install / Remove + +| Operation | Command | +|-----------|---------| +| Install | `brew install ` | +| Remove | `brew uninstall ` | + +Both hand control of the terminal to Homebrew's output via `execute_external_command`. + +--- + +## System Upgrade & Refresh + +| Key | Command | +|-----|---------| +| `U` | `brew upgrade` | +| `R` | `brew update` | + +--- + +## Updates + +`get_updates` runs `brew outdated --formula` and constructs a list of formulas with newer versions available. diff --git a/book/src/backends/index.md b/book/src/backends/index.md new file mode 100644 index 0000000..102c858 --- /dev/null +++ b/book/src/backends/index.md @@ -0,0 +1,86 @@ +# Package Manager Backends + +TRX abstracts all package manager interaction behind the `PackageManager` trait defined in `src/managers/mod.rs`. The correct backend is selected at runtime in `get_system_manager`. + +--- + +## `PackageManager` Trait + +```rust +pub trait PackageManager: Send + Sync { + fn name(&self) -> &str; + + fn search(&self, query: &str) -> Vec; + fn get_installed(&self) -> HashSet; + fn get_installed_details(&self) -> Vec; + fn get_updates(&self) -> Vec; + fn get_details(&self, pkg: &str, provider: &str) -> Option>; + + fn install( + &self, + terminal: &mut DefaultTerminal, + pkgs: &HashSet, + ) -> Result<(), Box>; + + fn remove( + &self, + terminal: &mut DefaultTerminal, + pkgs: &HashSet, + ) -> Result<(), Box>; + + fn system_upgrade( + &self, + terminal: &mut DefaultTerminal, + ) -> Result<(), Box>; + + fn refresh_databases( + &self, + terminal: &mut DefaultTerminal, + ) -> Result<(), Box>; +} +``` + +`terminal` is passed into mutating operations so that the backend can call `execute_external_command` — which temporarily hands the terminal back to the underlying package manager's interactive output. + +--- + +## Backend Selection + +`get_system_manager` in `src/managers/mod.rs` uses a simple priority order: + +1. `OS == "macos"` → `BrewManager` +2. `pacman --version` succeeds → `ArchManager` (Pacman + optional AUR helper) +3. `apt --version` succeeds → `AptManager` +4. Fallback → `ArchManager` (with default `yay` AUR helper) + +--- + +## Shared Utilities + +### `parse_alternating_lines` + +Many package manager CLI tools output results in alternating-line format: + +``` + [flags...] + + ... + +``` + +`parse_alternating_lines` parses this format, calls `fuzzy_match` on each package name, drops scores ≤ 0.01, and returns results sorted by score. + +### `DETAILS_CACHE` + +A global `Arc>>>` used by all backends to cache detail lookups (the `get_details` call). This avoids repeated subprocess invocations when the user scrolls back to a previously inspected package. + +--- + +## Supported Backends + +| Backend | Source file | Platform | +|---------|------------|----------| +| [Pacman](./pacman.md) | `src/managers/pacman.rs` | Arch Linux | +| [AUR / yay](./aur-yay.md) | `src/managers/yay.rs` | Arch Linux | +| [APT](./apt.md) | `src/managers/apt.rs` | Debian / Ubuntu | +| [Homebrew](./homebrew.md) | `src/managers/brew.rs` | macOS | diff --git a/book/src/backends/new-backend.md b/book/src/backends/new-backend.md new file mode 100644 index 0000000..8f0ae92 --- /dev/null +++ b/book/src/backends/new-backend.md @@ -0,0 +1,171 @@ +# Adding a New Backend + +TRX is designed to make adding a new package manager straightforward. You only need to: + +1. Create a new file in `src/managers/` +2. Implement the `PackageManager` trait +3. Register the backend in `get_system_manager` + +--- + +## Step 1 — Create the backend file + +``` +src/managers/dnf.rs # example: Fedora/RHEL dnf +``` + +Add the module to `src/managers/mod.rs`: + +```rust +pub mod dnf; +``` + +--- + +## Step 2 — Implement `PackageManager` + +Below is a minimal skeleton. All methods must be implemented (the trait has no default implementations): + +```rust +use crate::managers::{Package, PackageManager}; +use ratatui::DefaultTerminal; +use std::collections::{HashMap, HashSet}; +use std::process::Command; + +pub struct DnfManager; + +impl PackageManager for DnfManager { + fn name(&self) -> &str { + "DNF (Fedora/RHEL)" + } + + fn search(&self, query: &str) -> Vec { + if query.is_empty() { + return Vec::new(); + } + let output = Command::new("dnf") + .args(["search", query]) + .output() + .ok(); + + // Parse output and return Vec. + // Use parse_alternating_lines if the format fits, + // or write a custom parser. + todo!() + } + + fn get_installed(&self) -> HashSet { + let output = Command::new("dnf") + .args(["list", "--installed"]) + .output() + .ok(); + // Parse and return package names. + todo!() + } + + fn get_installed_details(&self) -> Vec { + todo!() + } + + fn get_updates(&self) -> Vec { + let output = Command::new("dnf") + .args(["list", "--upgrades"]) + .output() + .ok(); + todo!() + } + + fn get_details(&self, pkg: &str, _provider: &str) -> Option> { + // Check DETAILS_CACHE first. + { + let cache = crate::managers::DETAILS_CACHE.lock().unwrap(); + if let Some(cached) = cache.get(pkg) { + return Some(cached.clone()); + } + } + + let output = Command::new("dnf") + .args(["info", pkg]) + .output() + .ok()?; + + // Parse colon-separated key: value lines and store in DETAILS_CACHE. + todo!() + } + + fn install( + &self, + terminal: &mut DefaultTerminal, + pkgs: &HashSet, + ) -> Result<(), Box> { + let names: Vec<&str> = pkgs.iter().map(String::as_str).collect(); + let mut args = vec!["dnf", "install"]; + args.extend(names); + crate::execute_external_command(terminal, "sudo", &args) + } + + fn remove( + &self, + terminal: &mut DefaultTerminal, + pkgs: &HashSet, + ) -> Result<(), Box> { + let names: Vec<&str> = pkgs.iter().map(String::as_str).collect(); + let mut args = vec!["dnf", "remove"]; + args.extend(names); + crate::execute_external_command(terminal, "sudo", &args) + } + + fn system_upgrade( + &self, + terminal: &mut DefaultTerminal, + ) -> Result<(), Box> { + crate::execute_external_command(terminal, "sudo", &["dnf", "upgrade"]) + } + + fn refresh_databases( + &self, + terminal: &mut DefaultTerminal, + ) -> Result<(), Box> { + crate::execute_external_command(terminal, "sudo", &["dnf", "check-update"]) + } +} +``` + +--- + +## Step 3 — Register the backend + +In `src/managers/mod.rs`, add a detection branch in `get_system_manager`: + +```rust +pub fn get_system_manager(config: &crate::config::Config) -> Box { + if std::env::consts::OS == "macos" { + return Box::new(brew::BrewManager); + } + + if std::process::Command::new("pacman").arg("--version").output().is_ok() { + return Box::new(arch::ArchManager::new(config.aur_helper.clone())); + } + + if std::process::Command::new("apt").arg("--version").output().is_ok() { + return Box::new(apt::AptManager); + } + + // Add your backend here: + if std::process::Command::new("dnf").arg("--version").output().is_ok() { + return Box::new(dnf::DnfManager); + } + + Box::new(arch::ArchManager::new(config.aur_helper.clone())) +} +``` + +--- + +## Tips + +- **Use `parse_alternating_lines`** if the package manager outputs results in the standard `name version\n description` format. +- **Always check `DETAILS_CACHE`** at the top of `get_details` to avoid redundant subprocess calls. +- **Call `execute_external_command`** for any operation that produces interactive output (confirmations, progress bars, etc.). +- **Make the struct `Send + Sync`** — the `PackageManager` trait bound requires it. Zero-size structs and structs with only owned data satisfy this automatically. +- Run `cargo clippy` before opening a PR — Clippy warnings should ideally be zero. diff --git a/book/src/backends/pacman.md b/book/src/backends/pacman.md new file mode 100644 index 0000000..46bb977 --- /dev/null +++ b/book/src/backends/pacman.md @@ -0,0 +1,78 @@ +# Arch Linux — Pacman + +The Pacman backend is implemented across two files: + +- `src/managers/pacman.rs` — low-level wrappers around the `pacman` CLI +- `src/managers/arch.rs` — `ArchManager` struct that implements `PackageManager` by composing Pacman and the AUR helper + +--- + +## `ArchManager` + +`ArchManager` is constructed with an `aur_helper` string (default: `"yay"`, configurable in `config.toml`): + +```rust +pub struct ArchManager { + pub aur_helper: String, +} +``` + +It implements `PackageManager` by delegating to `pacman::*` and `yay::*` functions. + +--- + +## Search + +`ArchManager::search` merges results from both `pacman -Ss` and the AUR helper, sorts by fuzzy score, and truncates to **50 results**: + +```rust +fn search(&self, query: &str) -> Vec { + let mut all = pacman::search_pacman(query); + all.extend(yay::search_aur(query, &self.aur_helper)); + all.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(Equal)); + all.truncate(50); + all +} +``` + +`search_pacman` calls `pacman -Ss ` and parses the alternating-line output via `parse_alternating_lines`. + +--- + +## Package Details + +`pacman_info` first tries `pacman -Si ` (remote package info), then falls back to `pacman -Qi ` (locally installed info). The colon-separated key-value output is parsed into a `HashMap`. + +Results are cached in `DETAILS_CACHE` to avoid repeated subprocess calls. + +--- + +## Installed Packages + +`get_installed_packages()` runs `pacman -Q` and returns a `HashSet` of package names. `get_installed_packages_details()` runs `pacman -Q` and constructs `Package` structs from the name-version pairs. + +--- + +## Updates + +`get_updates()` runs `pacman -Qu` to list packages with newer versions available and returns them as `Vec`. + +--- + +## Install / Remove + +``` +pacman -S # install +pacman -Rns # remove with unused dependencies +``` + +Both require `sudo` and hand control of the terminal to the interactive pacman output via `execute_external_command`. + +--- + +## System Upgrade & Refresh + +| Key | Command | +|-----|---------| +| `U` | `sudo pacman -Syu` | +| `R` | `sudo pacman -Sy` | diff --git a/book/src/configuration.md b/book/src/configuration.md new file mode 100644 index 0000000..0f06da9 --- /dev/null +++ b/book/src/configuration.md @@ -0,0 +1,41 @@ +# Configuration + +TRX reads a single TOML configuration file on startup. If the file does not exist, it is created automatically with default values. + +--- + +## Location + +The config file follows the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) convention via the [`directories`](https://crates.io/crates/directories) crate: + +| Platform | Path | +|----------|------| +| Linux | `$XDG_CONFIG_HOME/trx/config.toml` (usually `~/.config/trx/config.toml`) | +| macOS | `~/Library/Application Support/trx/config.toml` | +| Windows | `%APPDATA%\trx\config.toml` | + +--- + +## Options + +```toml +# ~/.config/trx/config.toml + +# The AUR helper to use on Arch Linux systems. +# Any helper with yay-compatible CLI flags works (e.g. "paru", "aura"). +# Default: "yay" +aur_helper = "yay" +``` + +--- + +## Future Options + +The following options are planned for future releases: + +- **Keybindings** — remap any key to a different action +- **Theme** — colour scheme selection (dark / light / custom) +- **Search limit** — maximum number of results per tab +- **Metadata cache TTL** — how long `DETAILS_CACHE` entries remain valid + +See the [Roadmap](./roadmap.md) for status. diff --git a/book/src/contributing/coding-guidelines.md b/book/src/contributing/coding-guidelines.md new file mode 100644 index 0000000..4c84374 --- /dev/null +++ b/book/src/contributing/coding-guidelines.md @@ -0,0 +1,55 @@ +# Coding Guidelines + +## Code Style + +TRX follows standard Rust idioms. Before committing, always run: + +```bash +cargo fmt +cargo clippy +``` + +Clippy warnings should ideally be zero. Rustfmt is non-negotiable. + +--- + +## Architecture Principles + +- **Keep the UI thread non-blocking.** Never call blocking I/O on the main loop thread. Use `std::thread::spawn` + `mpsc` channels for any subprocess call. +- **Prefer OS threads over async.** TRX intentionally does not use Tokio or async/await. New features should follow the existing `std::thread` + `mpsc` pattern. +- **Return structured errors.** Use `Box` for backend method errors, matching the trait signature. Do not panic on expected failure paths. +- **Cache detail lookups.** Any `get_details` implementation should check and populate `DETAILS_CACHE` to avoid redundant subprocess calls. +- **Pure rendering.** The `draw_ui` function and its helpers should read state but not mutate business logic. Keep rendering predictable. + +--- + +## Commit Messages + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +``` +feat(dnf): implement DnfManager backend +fix(ui): prevent double redraw on search +docs: update architecture overview +refactor(fuzzy): simplify gap penalty calculation +``` + +Scope tokens: `ui`, `fuzzy`, `pacman`, `apt`, `brew`, `aur`, `config`, `updater`, `docs`. + +--- + +## Development Tools + +| Tool | Install | Purpose | +|------|---------|---------| +| `rustfmt` | `rustup component add rustfmt` | Code formatting | +| `clippy` | `rustup component add clippy` | Linting | +| `cargo-expand` | `cargo install cargo-expand` | Macro debugging | + +--- + +## Environment Requirements + +- Rust **1.70+** +- A terminal supporting **Unicode** and **truecolor** +- The package manager for your platform installed and on `$PATH` diff --git a/book/src/contributing/index.md b/book/src/contributing/index.md new file mode 100644 index 0000000..b1858e2 --- /dev/null +++ b/book/src/contributing/index.md @@ -0,0 +1,55 @@ +# Contributing Guide + +Contributions to TRX are welcome — whether that's a bug fix, a new backend, a UI improvement, or documentation. This page provides an overview. For full details, see [CONTRIBUTING.md](https://github.com/pie-314/trx/blob/main/CONTRIBUTING.md) in the repository. + +--- + +## Getting Started + +```bash +git clone https://github.com/pie-314/trx.git +cd trx +cargo build # ensure the project compiles +cargo run # smoke test in your terminal +cargo test # run the test suite +``` + +--- + +## Contribution Areas + +| Area | Examples | +|------|---------| +| **Backend Integrations** | New package managers (dnf, zypper, winget) | +| **TUI Improvements** | New widgets, themes, layout changes | +| **Fuzzy Search** | Better scoring heuristics, performance | +| **Performance** | Caching, parallel execution | +| **Bug Fixes** | Reproduce, isolate, and fix issues | +| **Documentation** | Improve this site, README, examples | + +--- + +## Pull Request Workflow + +1. Fork the repository and create a feature branch: + ```bash + git checkout -b feat/dnf-backend + ``` +2. Make your changes. +3. Run `cargo fmt` and `cargo clippy` (zero warnings preferred). +4. Run `cargo test`. +5. Commit using conventional commit messages (see [Coding Guidelines](./coding-guidelines.md)). +6. Open a PR describing what changed, why, and how it was tested. + +--- + +## Issues + +Before filing an issue, check if it already exists. When reporting a bug, include: + +- Steps to reproduce +- Platform (OS, package manager version) +- TRX version (`trx --version`) +- Relevant terminal output or screenshots + +Common issue labels: `good first issue`, `help wanted`, `backend`, `tui`, `fuzzy`, `performance`. diff --git a/book/src/installation.md b/book/src/installation.md new file mode 100644 index 0000000..28f9b1b --- /dev/null +++ b/book/src/installation.md @@ -0,0 +1,62 @@ +# Installation + +## One-liner (Recommended) + +The quickest way to install TRX on any supported platform: + +```bash +curl -fsSL https://trx.pidev.tech/install.sh | sh +``` + +This script detects your OS and architecture, downloads the appropriate pre-built binary from GitHub Releases, and places it in `/usr/local/bin`. + +--- + +## Cargo + +If you have a Rust toolchain installed, you can install directly from [crates.io](https://crates.io/crates/trx-cli): + +```bash +cargo install trx-cli +``` + +The binary is named `trx`. + +--- + +## Build from Source + +```bash +git clone https://github.com/pie-314/trx.git +cd trx +cargo build --release +sudo cp target/release/trx /usr/local/bin/ +``` + +**Requirements:** +- Rust **1.70** or later (`rustup` is the easiest way to get it) +- A terminal with **Unicode** and **truecolor** support (most modern terminals qualify) +- The package manager for your platform must be installed and available on `$PATH` + +--- + +## Self-updating + +TRX checks the [GitHub Releases API](https://api.github.com/repos/pie-314/trx/releases/latest) on every startup. If a newer version is found, it downloads and replaces the running binary automatically, then exits with a prompt to restart. + +Supported auto-update targets: + +| OS | Architecture | +|----|-------------| +| Linux | x86\_64 | +| macOS | x86\_64 | +| macOS | aarch64 (Apple Silicon) | + +--- + +## Verify Installation + +```bash +trx --version +trx --help +``` diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 0000000..b322b69 --- /dev/null +++ b/book/src/introduction.md @@ -0,0 +1,47 @@ +# Introduction + +
+ TRX logo +
+ +**TRX** is a fast, keyboard-driven terminal UI (TUI) package manager written in Rust. It gives you a unified, keyboard-first interface for searching, inspecting, and managing packages — whether you're on macOS with Homebrew, Arch Linux with Pacman + AUR, or Debian/Ubuntu with APT. + +> Search 50,000+ packages in under 50 ms. Install, remove, and update without leaving your terminal. + +No daemon. No config file required. Just run `trx`. + +--- + +## Highlights + +| Feature | Detail | +|---------|--------| +| **Speed** | Fuzzy search with sub-50 ms results | +| **Keyboard-first** | Vim-inspired navigation; no mouse needed | +| **Unified interface** | Same keybindings across all package managers | +| **Non-blocking** | All I/O on OS threads — UI never freezes | +| **Self-updating** | Checks GitHub releases on startup | +| **Extensible** | Pluggable backend trait — add a new PM in one file | + +--- + +## Supported Platforms + +| Package Manager | Platform | Status | +|-----------------|----------|--------| +| Pacman | Arch Linux | ✅ Implemented | +| yay (AUR) | Arch Linux | ✅ Implemented | +| APT | Debian / Ubuntu | ✅ Implemented | +| Homebrew | macOS | ✅ Implemented | +| dnf / yum | Fedora / RHEL | 🔜 Planned | +| zypper | openSUSE | 🔜 Planned | +| winget / scoop | Windows | 🔜 Planned | + +--- + +## Quick Links + +- [Installation](./installation.md) — get TRX running in 30 seconds +- [Usage](./usage.md) — keybindings and daily workflow +- [Architecture](./architecture/overview.md) — how TRX is structured internally +- [Adding a Backend](./backends/new-backend.md) — extend TRX to support a new package manager diff --git a/book/src/roadmap.md b/book/src/roadmap.md new file mode 100644 index 0000000..637909a --- /dev/null +++ b/book/src/roadmap.md @@ -0,0 +1,46 @@ +# Roadmap + +This page tracks planned and in-progress features for TRX. + +--- + +## In Progress / Near-term + +| Feature | Description | +|---------|-------------| +| **dnf / yum backend** | Fedora and RHEL support via `dnf` | +| **zypper backend** | openSUSE support | +| **winget / scoop backend** | Windows support | + +--- + +## Planned + +| Feature | Description | +|---------|-------------| +| **Configurable keybindings** | Remap any key via `config.toml` | +| **Pluggable themes** | Colour scheme selection and custom themes via config | +| **Transaction history** | Log of installs/removes with rollback support | +| **Batch / scripting mode** | Non-interactive mode for CI and shell scripts | +| **Dependency graph visualiser** | Visual view of package dependency trees | +| **Metadata caching** | Persist `DETAILS_CACHE` across sessions for faster repeated searches | +| **Plugin system** | Load custom backends and widgets from shared libraries | + +--- + +## Completed + +| Feature | Version | +|---------|---------| +| Pacman backend | v0.1.0 | +| AUR (yay) backend | v0.1.0 | +| APT backend | v0.1.0 | +| Homebrew backend | v0.1.0 | +| Self-updating mechanism | v0.1.2 | +| Binary releases via GitHub Actions | v0.1.4 | + +--- + +## Contributing to the Roadmap + +If you want to work on a planned feature, open a GitHub Discussion or comment on the relevant issue. See the [Contributing Guide](./contributing/index.md) for how to get started. diff --git a/book/src/usage.md b/book/src/usage.md new file mode 100644 index 0000000..ba1a804 --- /dev/null +++ b/book/src/usage.md @@ -0,0 +1,82 @@ +# Usage + +Start TRX by simply running: + +```bash +trx +``` + +TRX will detect your system's package manager automatically and open the TUI. + +--- + +## Tabs + +TRX has three tabs, cycled with `Tab` / `Shift+Tab`: + +| Tab | Description | +|-----|-------------| +| **Search** | Fuzzy-search all available packages | +| **Installed** | Browse packages currently installed on the system | +| **Updates** | Packages with a newer version available | + +--- + +## Keybindings + +### Global + +| Key | Action | +|-----|--------| +| `Tab` | Switch to next tab | +| `Shift+Tab` | Switch to previous tab | +| `?` | Toggle help overlay | +| `q` / `Esc` | Quit TRX (or exit current mode) | + +### Navigation + +| Key | Action | +|-----|--------| +| `↑` / `k` | Move selection up | +| `↓` / `j` | Move selection down | + +### Package Operations + +| Key | Action | +|-----|--------| +| `Space` | Toggle package selection | +| `i` | Install all selected packages | +| `x` | Remove all selected packages | +| `U` | Full system upgrade | +| `R` | Refresh package databases | + +### Search Tab + +| Key | Action | +|-----|--------| +| `e` | Enter search / editing mode | +| `Esc` | Exit search mode (return to normal navigation) | + +--- + +## Workflow Example + +1. Press `e` to enter search mode. +2. Type a package name (e.g. `ripgrep`). Results appear within **50 ms** as you type. +3. Use `↓` / `j` to move through results. The details panel on the right updates automatically. +4. Press `Space` to select one or more packages. +5. Press `i` to install the selection. + +--- + +## Command-line Options + +TRX accepts a small set of flags when called from the command line (before the TUI starts): + +``` +trx [OPTIONS] + +Options: + -v, --version Print version information + -h, --help Print help information +```