lupa 0.1.0

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

[![Crates.io](https://img.shields.io/crates/v/lupa.svg)](https://crates.io/crates/lupa)
[![Documentation](https://docs.rs/lupa/badge.svg)](https://docs.rs/lupa)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/NameOfShadow/lupa/blob/main/LICENSE)

**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](docs/web-ui-1.png)

**Diffs tab** – line‑by‑line diff view  
![Web Diffs](docs/web-ui-2.png)

## TUI

**Snapshots panel** – navigate with ↑/↓, expand with Enter  
![TUI Snapshots](docs/tui-1.png)

**Diffs panel** – coloured diff lines  
![TUI Diffs](docs/tui-2.png)

## 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`:

```toml
[dependencies]
lupa = "0.1"
```

Then replace `dbg!` with `inspect!`:

```rust
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](http://localhost:7777) in your browser – you’ll see both snapshots and the diff.

## Using the TUI (terminal interface)

Enable the `tui` feature:

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

Then run the terminal inspector instead of the web server:

```rust
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:

```rust
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**:

```rust
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:

```toml
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](LICENSE) for details.

---

*Happy inspecting!*