lupa_macros/lib.rs
1//! Procedural macros for the `lupa` inspector.
2//!
3//! This crate provides three macros that are re‑exported by the main `lupa`
4//! crate. They are the primary interface for capturing snapshots and
5//! computing diffs.
6//!
7//! - [`inspect!`] – captures a snapshot and sends it to the inspector.
8//! - [`snapshot!`] – returns a `Snapshot` value for later diffing.
9//! - [`snapshot_diff!`] – compares two snapshots and sends the diff.
10//!
11//! All macros capture the source location (`file!()`, `line!()`) and the
12//! stringified expression automatically.
13
14use proc_macro::TokenStream;
15use quote::quote;
16use syn::{parse_macro_input, Expr};
17
18/// Captures a snapshot of any value that implements `Debug` and sends it
19/// to the lupa inspector (web UI and/or TUI).
20///
21/// The macro evaluates the expression, takes its `{:#?}` debug representation,
22/// and records it along with the source location and the expression itself as
23/// a label. The snapshot becomes visible in the inspector's "Snapshots" tab.
24///
25/// # Return value
26/// The macro returns the original value (by reference) so it can be used
27/// inline, similar to `dbg!`.
28///
29/// # Example
30/// ```rust,ignore
31/// let user = User::new("Alice");
32/// inspect!(user); // snapshot appears in lupa
33/// ```
34///
35/// # Feature flags
36/// When the `web` feature is enabled (default), the snapshot is also broadcast
37/// over WebSocket to all connected browser clients.
38#[proc_macro]
39pub fn inspect(input: TokenStream) -> TokenStream {
40 let expr = parse_macro_input!(input as Expr);
41 let expr_str = quote!(#expr).to_string();
42
43 let expanded = quote! {
44 {
45 let __value = &#expr;
46 ::lupa::__internal::send_snapshot(
47 ::lupa::__internal::RawSnapshot {
48 label: #expr_str.to_string(),
49 debug_repr: format!("{:#?}", __value),
50 file: file!().to_string(),
51 line: line!(),
52 }
53 );
54 __value
55 }
56 };
57
58 TokenStream::from(expanded)
59}
60
61/// Creates a named snapshot without immediately sending it to the inspector.
62///
63/// Unlike `inspect!`, this macro returns a `Snapshot` struct that can be
64/// stored and later used with `snapshot_diff!`. This is useful for capturing
65/// a "before" state that you want to compare with an "after" state.
66///
67/// # Return value
68/// Returns a `lupa::Snapshot` value.
69///
70/// # Example
71/// ```rust,ignore
72/// let s1 = snapshot!(user);
73/// user.change_something();
74/// snapshot_diff!(s1, user);
75/// ```
76#[proc_macro]
77pub fn snapshot(input: TokenStream) -> TokenStream {
78 let expr = parse_macro_input!(input as Expr);
79 let expr_str = quote!(#expr).to_string();
80
81 let expanded = quote! {
82 ::lupa::Snapshot::capture(
83 #expr_str,
84 format!("{:#?}", &#expr),
85 file!(),
86 line!(),
87 )
88 };
89
90 TokenStream::from(expanded)
91}
92
93/// Computes a diff between two snapshots and sends the result to the inspector.
94///
95/// The first argument must be a `Snapshot` (typically created earlier with
96/// `snapshot!`). The second argument is any value that implements `Debug`;
97/// it is immediately turned into a new snapshot. The macro computes a
98/// line‑by‑line diff between the two debug representations and sends the
99/// result to the inspector, where it appears in the "Diffs" tab.
100///
101/// # Example
102/// ```rust,ignore
103/// let before = snapshot!(user);
104/// user.name = "Bob".into();
105/// snapshot_diff!(before, user);
106/// ```
107///
108/// # Note
109/// The macro does not return a value. It only sends the diff to the inspector.
110#[proc_macro]
111pub fn snapshot_diff(input: TokenStream) -> TokenStream {
112 let args = parse_macro_input!(input with
113 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated);
114
115 let mut iter = args.iter();
116 let snap_expr = iter.next().expect("snapshot_diff! needs 2 args: (snapshot, expr)");
117 let cur_expr = iter.next().expect("snapshot_diff! needs 2 args: (snapshot, expr)");
118 let cur_str = quote!(#cur_expr).to_string();
119
120 let expanded = quote! {
121 {
122 let __current = ::lupa::Snapshot::capture(
123 #cur_str,
124 format!("{:#?}", &#cur_expr),
125 file!(),
126 line!(),
127 );
128 ::lupa::__internal::send_diff(&#snap_expr, &__current);
129 }
130 };
131
132 TokenStream::from(expanded)
133}