lupa 0.1.0

Interactive object inspector for Rust — web UI + TUI + snapshot diffing
Documentation

lupa 🔍

Crates.io Documentation License: MIT

Interactive object inspector for Rust – a drop‑in replacement for dbg! that opens a local web UI and a terminal interface (TUI) where you can explore your structs as a collapsible tree, diff snapshots, and search fields – all with zero configuration.

Web UI

Snapshots tab – collapsible tree view with syntax highlighting
Web Snapshots

Diffs tab – line‑by‑line diff view
Web Diffs

TUI

Snapshots panel – navigate with ↑/↓, expand with Enter
TUI Snapshots

Diffs panel – coloured diff lines
TUI Diffs

Features

  • Zero setup – just add lupa to your Cargo.toml and start using inspect!.
  • Web inspector – automatic HTTP + WebSocket server on localhost:7777 with a modern, reactive UI.
  • Terminal inspector – full‑screen TUI with navigation, syntax highlighting, and diff view.
  • Snapshot diffing – capture “before” and “after” states and see exactly what changed line by line.
  • Works everywhere – in async runtimes (tokio, async‑std), in CLI tools, in web servers – anywhere Rust runs.
  • Lightweight – only pulls in dependencies you need via feature flags (web enabled by default, tui optional).

Quick start

Add lupa to your Cargo.toml:

[dependencies]
lupa = "0.1"

Then replace dbg! with inspect!:

use lupa::{inspect, snapshot, snapshot_diff};

#[derive(Debug)]
struct User {
    name: String,
    age: u32,
}

fn main() {
    let mut user = User { name: "Alice".into(), age: 30 };
    inspect!(user);               // snapshot appears in web UI

    let before = snapshot!(user); // capture before state
    user.age = 31;
    inspect!(user);               // second snapshot

    snapshot_diff!(before, user); // diff shown in inspector

    // Keep the web server alive (or use run() / run_mode())
    lupa::keep_alive();
}

Open http://localhost:7777 in your browser – you’ll see both snapshots and the diff.

Using the TUI (terminal interface)

Enable the tui feature:

lupa = { version = "0.1", features = ["tui"] }

Then run the terminal inspector instead of the web server:

lupa::run_mode(lupa::RunMode::Tui).unwrap();

Key bindings:

  • / – move selection
  • Tab – switch between Snapshots and Diffs panels
  • Enter – expand / collapse the detail view
  • PgUp / PgDn – scroll
  • q / Esc / Ctrl+C – quit

Runtime mode selection

When both web and tui features are enabled, you can choose which interface(s) to run:

use lupa::RunMode;

// Only web server (blocks until Ctrl+C)
lupa::run_mode(RunMode::Web).unwrap();

// Only TUI (blocks until user quits)
lupa::run_mode(RunMode::Tui).unwrap();

// Both: web server in background, TUI in foreground
lupa::run_mode(RunMode::Both).unwrap();

The default lupa::run() automatically picks Web if only web is enabled, Tui if only tui is enabled, and Both if both are enabled.

Integration with web frameworks (Axum, Actix, etc.)

Because lupa stores all data in a global thread‑safe state, you can call inspect! from any thread, including inside async request handlers. Here is a complete example using Axum:

use axum::extract::State;
use axum::{routing::get, Router};
use lupa::{inspect, snapshot, snapshot_diff};
use std::sync::{Arc, Mutex};
use tokio::net::TcpListener;

struct AppState {
    counter: u32,
}

async fn increment(State(state): State<Arc<Mutex<AppState>>>) -> String {
    let mut s = state.lock().unwrap();
    let before = snapshot!(s.counter);
    s.counter += 1;
    inspect!(s.counter);
    snapshot_diff!(before, s.counter);
    
    format!("Counter is now {}", s.counter)
}

#[tokio::main]
async fn main() {
    let state = Arc::new(Mutex::new(AppState { counter: 0 }));

    let app = Router::new()
        .route("/inc", get(increment))
        .with_state(state);

    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();

    tokio::spawn(async move {
        axum::serve(listener, app).await.unwrap();
    });

    lupa::run_mode(lupa::RunMode::Tui).unwrap();
}

Every HTTP request that hits /inc will record a new snapshot and diff in the lupa inspector.

Macros reference

Macro Description
inspect!(expr) Takes a debug‑printable expression, captures a snapshot, and sends it to the inspector. Returns a reference to the value.
snapshot!(expr) Captures a snapshot and returns it as a lupa::Snapshot value for later diffing.
snapshot_diff!(snapshot, expr) Compares an old snapshot with a new expression, computes a line diff, and sends it to the inspector.

All macros automatically capture the source file path, line number, and the expression text as a label.

Feature flags

Flag Default Description
web Enables the HTTP + WebSocket server and the web UI.
tui Enables the terminal interface (ratatui + crossterm).

You can disable default features to use only the TUI:

lupa = { version = "0.1", default-features = false, features = ["tui"] }

Configuration

  • Port: set the environment variable LUPA_PORT to change the HTTP port (the WebSocket port becomes LUPA_PORT + 1). Default is 7777.

How it works

  1. inspect! and snapshot! capture the Debug representation of your value ({:#?}) together with the file, line, and a label.
  2. All snapshots are stored in a global, thread‑safe InspectorState (append‑only).
  3. The web server serves the static UI and provides a JSON API for past snapshots/diffs.
  4. A WebSocket connection pushes every new snapshot/diff to all connected browsers in real time.

License

Licensed under the MIT license. See LICENSE for details.


Happy inspecting!