lupa-macros 0.1.0

Procedural macros for lupa - interactive object inspector
Documentation
//! Procedural macros for the `lupa` inspector.
//!
//! This crate provides three macros that are re‑exported by the main `lupa`
//! crate. They are the primary interface for capturing snapshots and
//! computing diffs.
//!
//! - [`inspect!`] – captures a snapshot and sends it to the inspector.
//! - [`snapshot!`] – returns a `Snapshot` value for later diffing.
//! - [`snapshot_diff!`] – compares two snapshots and sends the diff.
//!
//! All macros capture the source location (`file!()`, `line!()`) and the
//! stringified expression automatically.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Expr};

/// Captures a snapshot of any value that implements `Debug` and sends it
/// to the lupa inspector (web UI and/or TUI).
///
/// The macro evaluates the expression, takes its `{:#?}` debug representation,
/// and records it along with the source location and the expression itself as
/// a label. The snapshot becomes visible in the inspector's "Snapshots" tab.
///
/// # Return value
/// The macro returns the original value (by reference) so it can be used
/// inline, similar to `dbg!`.
///
/// # Example
/// ```rust,ignore
/// let user = User::new("Alice");
/// inspect!(user);   // snapshot appears in lupa
/// ```
///
/// # Feature flags
/// When the `web` feature is enabled (default), the snapshot is also broadcast
/// over WebSocket to all connected browser clients.
#[proc_macro]
pub fn inspect(input: TokenStream) -> TokenStream {
    let expr = parse_macro_input!(input as Expr);
    let expr_str = quote!(#expr).to_string();

    let expanded = quote! {
        {
            let __value = &#expr;
            ::lupa::__internal::send_snapshot(
                ::lupa::__internal::RawSnapshot {
                    label: #expr_str.to_string(),
                    debug_repr: format!("{:#?}", __value),
                    file: file!().to_string(),
                    line: line!(),
                }
            );
            __value
        }
    };

    TokenStream::from(expanded)
}

/// Creates a named snapshot without immediately sending it to the inspector.
///
/// Unlike `inspect!`, this macro returns a `Snapshot` struct that can be
/// stored and later used with `snapshot_diff!`. This is useful for capturing
/// a "before" state that you want to compare with an "after" state.
///
/// # Return value
/// Returns a `lupa::Snapshot` value.
///
/// # Example
/// ```rust,ignore
/// let s1 = snapshot!(user);
/// user.change_something();
/// snapshot_diff!(s1, user);
/// ```
#[proc_macro]
pub fn snapshot(input: TokenStream) -> TokenStream {
    let expr = parse_macro_input!(input as Expr);
    let expr_str = quote!(#expr).to_string();

    let expanded = quote! {
        ::lupa::Snapshot::capture(
            #expr_str,
            format!("{:#?}", &#expr),
            file!(),
            line!(),
        )
    };

    TokenStream::from(expanded)
}

/// Computes a diff between two snapshots and sends the result to the inspector.
///
/// The first argument must be a `Snapshot` (typically created earlier with
/// `snapshot!`). The second argument is any value that implements `Debug`;
/// it is immediately turned into a new snapshot. The macro computes a
/// line‑by‑line diff between the two debug representations and sends the
/// result to the inspector, where it appears in the "Diffs" tab.
///
/// # Example
/// ```rust,ignore
/// let before = snapshot!(user);
/// user.name = "Bob".into();
/// snapshot_diff!(before, user);
/// ```
///
/// # Note
/// The macro does not return a value. It only sends the diff to the inspector.
#[proc_macro]
pub fn snapshot_diff(input: TokenStream) -> TokenStream {
    let args = parse_macro_input!(input with
        syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated);

    let mut iter = args.iter();
    let snap_expr = iter.next().expect("snapshot_diff! needs 2 args: (snapshot, expr)");
    let cur_expr  = iter.next().expect("snapshot_diff! needs 2 args: (snapshot, expr)");
    let cur_str   = quote!(#cur_expr).to_string();

    let expanded = quote! {
        {
            let __current = ::lupa::Snapshot::capture(
                #cur_str,
                format!("{:#?}", &#cur_expr),
                file!(),
                line!(),
            );
            ::lupa::__internal::send_diff(&#snap_expr, &__current);
        }
    };

    TokenStream::from(expanded)
}