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}