Skip to main content

bubbletea_macros/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(clippy::nursery)]
3#![allow(clippy::pedantic)]
4
5//! # bubbletea-macros
6//!
7//! Procedural macros for the bubbletea TUI framework.
8//!
9//! This crate provides the `#[derive(Model)]` macro which reduces boilerplate
10//! when implementing the `Model` trait for your TUI applications.
11//!
12//! ## Role in `charmed_rust`
13//!
14//! bubbletea-macros is an optional ergonomic layer for the core framework:
15//! - **bubbletea** re-exports the derive macro when the `macros` feature is enabled.
16//! - **demo_showcase** uses the derive macro for concise models in examples and tests.
17//!
18//! ## Quick Start
19//!
20//! ```rust,ignore
21//! use bubbletea::{Cmd, Message, Model};
22//!
23//! #[derive(Model)]
24//! struct Counter {
25//!     #[state]  // Marks fields for optimized change detection
26//!     count: i32,
27//! }
28//!
29//! impl Counter {
30//!     fn init(&self) -> Option<Cmd> {
31//!         None
32//!     }
33//!
34//!     fn update(&mut self, msg: Message) -> Option<Cmd> {
35//!         if let Some(&delta) = msg.downcast_ref::<i32>() {
36//!             self.count += delta;
37//!         }
38//!         None
39//!     }
40//!
41//!     fn view(&self) -> String {
42//!         format!("Count: {}", self.count)
43//!     }
44//! }
45//! ```
46//!
47//! ## How It Works
48//!
49//! The `#[derive(Model)]` macro generates a `Model` trait implementation
50//! that delegates to inherent methods on your struct. You implement the
51//! `init`, `update`, and `view` methods directly on your struct, and the
52//! macro bridges them to the trait.
53//!
54//! ### Required Methods
55//!
56//! Your struct must implement these inherent methods:
57//!
58//! | Method | Signature | Purpose |
59//! |--------|-----------|---------|
60//! | `init` | `fn init(&self) -> Option<Cmd>` | Initial command on startup |
61//! | `update` | `fn update(&mut self, msg: Message) -> Option<Cmd>` | Handle messages |
62//! | `view` | `fn view(&self) -> String` | Render the UI |
63//!
64//! ## State Tracking with `#[state]`
65//!
66//! The `#[state]` attribute enables optimized re-rendering by tracking which
67//! fields trigger view updates. Fields marked with `#[state]` are monitored
68//! for changes, and only changes to these fields signal that a re-render
69//! is needed.
70//!
71//! ### Basic Usage
72//!
73//! ```rust,ignore
74//! #[derive(Model)]
75//! struct App {
76//!     #[state]
77//!     counter: i32,      // Changes trigger re-render
78//!
79//!     cache: String,     // Not tracked (no re-render on change)
80//! }
81//! ```
82//!
83//! ### Advanced State Options
84//!
85//! ```rust,ignore
86//! #[derive(Model)]
87//! struct App {
88//!     // Custom equality function for floating-point comparison
89//!     #[state(eq = "float_approx_eq")]
90//!     progress: f64,
91//!
92//!     // Excluded from change detection (internal bookkeeping)
93//!     #[state(skip)]
94//!     last_tick: std::time::Instant,
95//!
96//!     // Debug logging when this field changes
97//!     #[state(debug)]
98//!     selected_index: usize,
99//! }
100//!
101//! fn float_approx_eq(a: &f64, b: &f64) -> bool {
102//!     (a - b).abs() < 0.001
103//! }
104//! ```
105//!
106//! ### State Options
107//!
108//! | Option | Description |
109//! |--------|-------------|
110//! | `eq = "fn_name"` | Custom equality function `fn(&T, &T) -> bool` |
111//! | `skip` | Exclude field from change detection |
112//! | `debug` | Log changes to this field (debug builds only) |
113//!
114//! ## Generated Code
115//!
116//! The macro generates:
117//!
118//! 1. **Model trait implementation** - Delegates to your inherent methods
119//! 2. **State snapshot struct** - For change detection (only if `#[state]` fields exist)
120//! 3. **Helper methods** - `__snapshot_state()` and `__state_changed()`
121//!
122//! ## Generic Structs
123//!
124//! The derive macro supports generic structs with type parameters:
125//!
126//! ```rust,ignore
127//! #[derive(Model)]
128//! struct Container<T: Clone + PartialEq + Send + 'static> {
129//!     #[state]
130//!     value: T,
131//! }
132//! ```
133//!
134//! ## Error Messages
135//!
136//! The macro provides helpful error messages for common mistakes:
137//!
138//! - Using `#[derive(Model)]` on enums, unions, or tuple structs
139//! - Fields marked `#[state]` that don't implement `Clone` or `PartialEq`
140//!
141//! ## Migration from Manual Implementation
142//!
143//! **Before (manual):**
144//! ```rust,ignore
145//! impl Model for Counter {
146//!     fn init(&self) -> Option<Cmd> { None }
147//!     fn update(&mut self, msg: Message) -> Option<Cmd> {
148//!         // handle message
149//!         None
150//!     }
151//!     fn view(&self) -> String {
152//!         format!("{}", self.count)
153//!     }
154//! }
155//! ```
156//!
157//! **After (derive):**
158//! ```rust,ignore
159//! #[derive(Model)]
160//! struct Counter {
161//!     #[state]
162//!     count: i32,
163//! }
164//!
165//! impl Counter {
166//!     fn init(&self) -> Option<Cmd> { None }
167//!     fn update(&mut self, msg: Message) -> Option<Cmd> {
168//!         // handle message
169//!         None
170//!     }
171//!     fn view(&self) -> String {
172//!         format!("{}", self.count)
173//!     }
174//! }
175//! ```
176//!
177//! The benefit is automatic state change tracking and cleaner separation
178//! of your model logic from the trait boilerplate.
179
180use proc_macro::TokenStream;
181use proc_macro_error2::proc_macro_error;
182
183mod attributes;
184mod error;
185mod model;
186mod state;
187
188/// Derive macro for implementing the `Model` trait.
189///
190/// This macro generates a `Model` trait implementation that delegates to
191/// inherent methods named `init`, `update`, and `view` on your struct.
192///
193/// # Requirements
194///
195/// - Applied to a named struct (not enum, union, tuple struct, or unit struct)
196/// - Struct must implement `init(&self) -> Option<Cmd>` as an inherent method
197/// - Struct must implement `update(&mut self, msg: Message) -> Option<Cmd>`
198/// - Struct must implement `view(&self) -> String`
199///
200/// # Field Attributes
201///
202/// - `#[state]` - Track field for change detection
203/// - `#[state(eq = "fn_name")]` - Use custom equality function
204/// - `#[state(skip)]` - Exclude from change detection
205/// - `#[state(debug)]` - Log changes in debug builds
206///
207/// # Example
208///
209/// ```rust,ignore
210/// use bubbletea::{Cmd, Message, Model};
211///
212/// #[derive(Model)]
213/// struct Counter {
214///     #[state]
215///     count: i32,
216/// }
217///
218/// impl Counter {
219///     fn init(&self) -> Option<Cmd> {
220///         None
221///     }
222///
223///     fn update(&mut self, msg: Message) -> Option<Cmd> {
224///         if let Some(&delta) = msg.downcast_ref::<i32>() {
225///             self.count += delta;
226///         }
227///         None
228///     }
229///
230///     fn view(&self) -> String {
231///         format!("Count: {}", self.count)
232///     }
233/// }
234/// ```
235#[proc_macro_derive(Model, attributes(state, init, update, view, model))]
236#[proc_macro_error]
237pub fn derive_model(input: TokenStream) -> TokenStream {
238    model::derive_model_impl(input.into()).into()
239}