Skip to main content

zyn_derive/
lib.rs

1//! Procedural macros for the zyn framework.
2//!
3//! Re-exported through the root `zyn` crate. All macros are accessed as
4//! `zyn::zyn!`, `#[zyn::element]`, etc.
5//!
6//! # Quick reference
7//!
8//! ```ignore
9//! // Template expansion
10//! zyn::zyn! { fn {{ name | snake }}() {} }
11//!
12//! // Reusable component
13//! #[zyn::element]
14//! fn my_getter(name: syn::Ident, ty: syn::Type) -> zyn::TokenStream { ... }
15//!
16//! // Derive macro entry point
17//! #[zyn::derive]
18//! fn my_derive(
19//!     #[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
20//!     #[zyn(input)] fields: zyn::Fields,
21//! ) -> zyn::TokenStream { ... }
22//!
23//! // Typed attribute parsing
24//! #[derive(zyn::Attribute)]
25//! #[zyn("my_attr")]
26//! struct MyAttr { skip: bool, rename: Option<String> }
27//! ```
28
29mod attribute;
30mod common;
31mod macros;
32
33/// Expands a zyn template into a [`proc_macro2::TokenStream`].
34///
35/// Everything outside `{{ }}` and `@` directives passes through as literal tokens.
36///
37/// # Interpolation
38///
39/// `{{ expr }}` inserts any [`quote::ToTokens`] value:
40///
41/// ```ignore
42/// let name = format_ident!("my_fn");
43/// zyn! { fn {{ name }}() {} }
44/// // output: fn my_fn() {}
45/// ```
46///
47/// # Pipes
48///
49/// `{{ expr | pipe }}` transforms the value before inserting it. Pipes chain left to right:
50///
51/// ```ignore
52/// zyn! { fn {{ name | snake }}() {} }
53/// // name = "HelloWorld" → fn hello_world() {}
54///
55/// zyn! { fn {{ name | snake | ident:"get_{}" }}() {} }
56/// // name = "HelloWorld" → fn get_hello_world() {}
57/// ```
58///
59/// Built-in pipes:
60///
61/// | Pipe | Input | Output |
62/// |------|-------|--------|
63/// | `snake` | `HelloWorld` | `hello_world` |
64/// | `pascal` | `hello_world` | `HelloWorld` |
65/// | `camel` | `hello_world` | `helloWorld` |
66/// | `screaming` | `HelloWorld` | `HELLO_WORLD` |
67/// | `kebab` | `HelloWorld` | `"hello-world"` (string literal) |
68/// | `upper` | `hello` | `HELLO` |
69/// | `lower` | `HELLO` | `hello` |
70/// | `str` | `hello` | `"hello"` (string literal) |
71/// | `plural` | `user` | `users` |
72/// | `singular` | `users` | `user` |
73/// | `ident:"pattern_{}"` | `hello` | `pattern_hello` (ident) |
74/// | `fmt:"pattern_{}"` | `hello` | `"pattern_hello"` (string literal) |
75/// | `trim` | `__foo__` | `foo` |
76///
77/// # `@if`
78///
79/// ```ignore
80/// zyn! {
81///     @if (is_async) {
82///         async fn {{ name }}() {}
83///     } @else if (is_unsafe) {
84///         unsafe fn {{ name }}() {}
85///     } @else {
86///         fn {{ name }}() {}
87///     }
88/// }
89/// ```
90///
91/// Without `@else`, emits nothing when false:
92///
93/// ```ignore
94/// zyn! { @if (is_pub) { pub } fn {{ name }}() {} }
95/// // is_pub = true  → pub fn my_fn() {}
96/// // is_pub = false →     fn my_fn() {}
97/// ```
98///
99/// # `@for`
100///
101/// Iterator form:
102///
103/// ```ignore
104/// zyn! {
105///     @for (field in fields.iter()) {
106///         pub {{ field.ident }}: {{ field.ty }},
107///     }
108/// }
109/// // output: pub x: f64, pub y: f64,
110/// ```
111///
112/// Count form (no binding, repeats N times):
113///
114/// ```ignore
115/// zyn! { @for (3) { x, }}
116/// // output: x, x, x,
117/// ```
118///
119/// # `@match`
120///
121/// ```ignore
122/// zyn! {
123///     @match (kind) {
124///         Kind::Struct => { struct {{ name }} {} }
125///         Kind::Enum   => { enum {{ name }} {} }
126///         _            => {}
127///     }
128/// }
129/// ```
130///
131/// # Element invocation
132///
133/// Call a `#[zyn::element]` component with named props:
134///
135/// ```ignore
136/// zyn! {
137///     @for (field in fields.iter()) {
138///         @getter(name = field.ident.clone().unwrap(), ty = field.ty.clone())
139///     }
140/// }
141/// ```
142///
143/// With a children block:
144///
145/// ```ignore
146/// zyn! {
147///     @wrapper(title = "hello") { inner content }
148/// }
149/// ```
150#[proc_macro]
151pub fn zyn(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
152    macros::template::expand(input.into()).into()
153}
154
155/// Defines a reusable template component generating a struct that implements `Render`.
156///
157/// Function parameters become either **props** (struct fields passed at call site)
158/// or **extractors** (marked `#[zyn(input)]`, resolved automatically from `Input`).
159///
160/// Built-in extractor types: `Extract<T>`, `Attr<T>`, `Fields`, `Variants`, `Data<T>`.
161/// A parameter named `children` receives the inner token stream from a children block.
162///
163/// # Examples
164///
165/// Simple element with props:
166///
167/// ```ignore
168/// #[zyn::element]
169/// fn greeting(name: syn::Ident) -> zyn::TokenStream {
170///     zyn::zyn!(pub fn {{ name }}() {})
171/// }
172///
173/// // Invoke inside a template
174/// zyn::zyn!(@greeting(name = format_ident!("hello")))
175/// // output: pub fn hello() {}
176/// ```
177///
178/// Element with an extractor and a children block:
179///
180/// ```ignore
181/// #[zyn::element]
182/// fn wrapper(
183///     #[zyn(input)] ident: zyn::Extract<syn::Ident>,
184///     children: zyn::TokenStream,
185/// ) -> zyn::TokenStream {
186///     zyn::zyn!(impl {{ ident }} { {{ children }} })
187/// }
188///
189/// zyn::zyn!(@wrapper { fn new() -> Self { Self } })
190/// // output: impl MyStruct { fn new() -> Self { Self } }
191/// ```
192///
193/// Optional custom name alias (defaults to function name):
194///
195/// ```ignore
196/// #[zyn::element("my_alias")]
197/// fn internal_name(label: syn::Ident) -> zyn::TokenStream { ... }
198///
199/// zyn::zyn!(@my_alias(label = format_ident!("x")))
200/// ```
201///
202/// # Debugging
203///
204/// Add `debug` to inspect the generated code as a compiler `note` diagnostic.
205/// Requires the `ZYN_DEBUG` environment variable to match the generated struct
206/// name (supports `*` wildcards, e.g., `ZYN_DEBUG="*"`).
207///
208/// ```ignore
209/// #[zyn::element(debug)]
210/// #[zyn::element(debug = "pretty")]   // requires `pretty` feature
211/// #[zyn::element("alias", debug)]
212/// ```
213///
214/// ```bash
215/// ZYN_DEBUG="Greeting" cargo build
216/// ```
217///
218/// Without `ZYN_DEBUG`, the `debug` argument is inert — safe to leave in source.
219///
220/// The `pretty` format uses `prettyplease` for formatted output. Enable it with:
221///
222/// ```toml
223/// zyn = { version = "0.3", features = ["pretty"] }
224/// ```
225#[proc_macro_attribute]
226pub fn element(
227    args: proc_macro::TokenStream,
228    input: proc_macro::TokenStream,
229) -> proc_macro::TokenStream {
230    macros::element::expand(args.into(), input.into()).into()
231}
232
233/// Defines a custom pipe transform used inside `{{ expr | pipe }}` interpolations.
234///
235/// Transforms a single-argument function into a struct implementing `Pipe`.
236/// The function receives a `String` (the stringified token) and returns any
237/// [`quote::ToTokens`] value.
238///
239/// # Examples
240///
241/// Custom pipe (name defaults to the function name):
242///
243/// ```ignore
244/// #[zyn::pipe]
245/// fn shout(input: String) -> syn::Ident {
246///     syn::Ident::new(&input.to_uppercase(), proc_macro2::Span::call_site())
247/// }
248///
249/// let name = format_ident!("hello");
250/// zyn::zyn!(static {{ name | shout }}: &str = "hi";)
251/// // output: static HELLO: &str = "hi";
252/// ```
253///
254/// With a custom name alias:
255///
256/// ```ignore
257/// #[zyn::pipe("yell")]
258/// fn make_loud(input: String) -> syn::Ident { ... }
259///
260/// zyn::zyn!(fn {{ name | yell }}() {})
261/// ```
262///
263/// Chaining with built-in pipes:
264///
265/// ```ignore
266/// zyn::zyn!(fn {{ name | snake | ident:"get_{}" }}() {})
267/// // name = "HelloWorld" → fn get_hello_world() {}
268/// ```
269///
270/// # Debugging
271///
272/// Add `debug` to inspect the generated code as a compiler `note` diagnostic.
273/// Requires the `ZYN_DEBUG` environment variable to match the generated struct
274/// name (supports `*` wildcards).
275///
276/// ```ignore
277/// #[zyn::pipe(debug)]
278/// #[zyn::pipe(debug = "pretty")]       // requires `pretty` feature
279/// #[zyn::pipe("alias", debug)]
280/// ```
281///
282/// Without `ZYN_DEBUG`, the `debug` argument is inert — safe to leave in source.
283/// See the [debugging guide](https://aacebo.github.io/zyn/05-reference/debugging.html) for details.
284#[proc_macro_attribute]
285pub fn pipe(
286    args: proc_macro::TokenStream,
287    input: proc_macro::TokenStream,
288) -> proc_macro::TokenStream {
289    macros::pipe::expand(args.into(), input.into()).into()
290}
291
292/// Defines a derive macro entry point that auto-parses `DeriveInput` into typed inputs.
293///
294/// All parameters must be marked `#[zyn(input)]`; they are resolved from the
295/// annotated type's `DeriveInput` via [`FromInput`](zyn::FromInput). The macro
296/// name defaults to the function name in PascalCase, or can be set explicitly.
297///
298/// # Examples
299///
300/// ```ignore
301/// // In your proc-macro crate (lib.rs):
302/// #[zyn::derive]
303/// fn my_derive(
304///     #[zyn(input)] ident: zyn::Extract<syn::Ident>,
305///     #[zyn(input)] fields: zyn::Fields,
306/// ) -> zyn::TokenStream {
307///     zyn::zyn! {
308///         impl MyTrait for {{ ident }} {
309///             fn field_count() -> usize { {{ fields.len() }} }
310///         }
311///     }
312/// }
313///
314/// // Consumers annotate their types:
315/// #[derive(MyDerive)]
316/// struct Point { x: f64, y: f64 }
317/// // output: impl MyTrait for Point { fn field_count() -> usize { 2 } }
318/// ```
319///
320/// With an explicit macro name:
321///
322/// ```ignore
323/// #[zyn::derive("DebugExtra")]
324/// fn my_fn(#[zyn(input)] ident: zyn::Extract<syn::Ident>) -> zyn::TokenStream { ... }
325/// // Registers as #[derive(DebugExtra)]
326/// ```
327///
328/// # Debugging
329///
330/// Add `debug` to inspect the generated code as a compiler `note` diagnostic.
331/// Requires the `ZYN_DEBUG` environment variable to match the derive name
332/// (supports `*` wildcards).
333///
334/// ```ignore
335/// #[zyn::derive("MyDerive", debug)]
336/// #[zyn::derive("MyDerive", debug = "pretty")]               // requires `pretty` feature
337/// #[zyn::derive("MyDerive", attributes(skip), debug)]
338/// ```
339///
340/// Without `ZYN_DEBUG`, the `debug` argument is inert — safe to leave in source.
341/// See the [debugging guide](https://aacebo.github.io/zyn/05-reference/debugging.html) for details.
342#[proc_macro_attribute]
343pub fn derive(
344    args: proc_macro::TokenStream,
345    input: proc_macro::TokenStream,
346) -> proc_macro::TokenStream {
347    macros::derive::expand(args.into(), input.into()).into()
348}
349
350/// Defines an attribute macro entry point that auto-parses the annotated item into typed inputs.
351///
352/// Extractors marked `#[zyn(input)]` are resolved from the annotated item via
353/// [`FromInput`](zyn::FromInput). An optional `args: zyn::Args` parameter receives
354/// the raw attribute arguments. The macro name defaults to the function name.
355///
356/// # Examples
357///
358/// ```ignore
359/// // In your proc-macro crate (lib.rs):
360/// #[zyn::attribute]
361/// fn log_call(
362///     #[zyn(input)] item: syn::ItemFn,
363///     args: zyn::Args,
364/// ) -> zyn::TokenStream {
365///     let prefix = args.get("prefix")
366///         .and_then(|a| a.value::<String>().ok())
367///         .unwrap_or_else(|| "CALL".into());
368///     zyn::zyn! {
369///         {{ item }}
370///     }
371/// }
372///
373/// // Applied to a function:
374/// #[log_call(prefix = "DEBUG")]
375/// fn my_fn() { ... }
376/// ```
377///
378/// With an explicit macro name:
379///
380/// ```ignore
381/// #[zyn::attribute("trace")]
382/// fn trace_impl(#[zyn(input)] item: syn::ItemFn) -> zyn::TokenStream { ... }
383/// // Registers as #[trace]
384/// ```
385///
386/// # Debugging
387///
388/// Add `debug` to inspect the generated code as a compiler `note` diagnostic.
389/// Requires the `ZYN_DEBUG` environment variable to match the function name
390/// (supports `*` wildcards).
391///
392/// ```ignore
393/// #[zyn::attribute(debug)]
394/// #[zyn::attribute(debug = "pretty")]  // requires `pretty` feature
395/// ```
396///
397/// Without `ZYN_DEBUG`, the `debug` argument is inert — safe to leave in source.
398/// See the [debugging guide](https://aacebo.github.io/zyn/05-reference/debugging.html) for details.
399#[proc_macro_attribute]
400pub fn attribute(
401    args: proc_macro::TokenStream,
402    input: proc_macro::TokenStream,
403) -> proc_macro::TokenStream {
404    macros::attribute::expand(args.into(), input.into()).into()
405}
406
407/// Derives typed attribute parsing from `#[attr(...)]` key-value syntax.
408///
409/// **Attribute mode** (`#[zyn("name")]`): also implements [`FromInput`](zyn::FromInput)
410/// so the struct can be used as an `Attr<T>` extractor in `#[element]` or `#[derive]`.
411///
412/// **Argument mode** (no `#[zyn("name")]`): implements `FromArgs` and `FromArg` only,
413/// suitable for nested argument structures.
414///
415/// # Field options
416///
417/// | Attribute | Effect |
418/// |-----------|--------|
419/// | `#[zyn(default)]` | Uses `Default::default()` when the field is absent |
420/// | `#[zyn(default = "val")]` | Uses `Into::into("val")` as the default |
421/// | `#[zyn(about = "...")]` | Doc string shown in `about()` output |
422///
423/// # Examples
424///
425/// Attribute mode (registers `FromInput` + `FromArgs`):
426///
427/// ```ignore
428/// #[derive(zyn::Attribute)]
429/// #[zyn("my_attr", about = "controls behaviour")]
430/// struct MyAttr {
431///     #[zyn(about = "the item name")]
432///     rename: Option<String>,
433///     #[zyn(default)]
434///     skip: bool,
435/// }
436///
437/// // Used as an extractor in an element:
438/// #[zyn::element]
439/// fn my_element(#[zyn(input)] attr: zyn::Attr<MyAttr>) -> zyn::TokenStream {
440///     zyn::zyn!(@if (attr.skip) { /* nothing */ } @else { /* generate */ })
441/// }
442///
443/// // Applied to a type:
444/// #[my_attr(rename = "other_name")]
445/// struct Foo;
446/// ```
447///
448/// Argument mode (no `#[zyn("name")]`):
449///
450/// ```ignore
451/// #[derive(zyn::Attribute)]
452/// struct Config { level: i64, tag: String }
453///
454/// let args: zyn::Args = zyn::parse!("level = 3, tag = \"v1\"").unwrap();
455/// let cfg = Config::from_args(&args).unwrap();
456/// ```
457///
458/// # Diagnostics
459///
460/// Errors are accumulated and returned together. Unknown arguments automatically
461/// produce "did you mean?" suggestions when a close match exists:
462///
463/// ```ignore
464/// #[derive(zyn::Attribute)]
465/// #[zyn("config")]
466/// struct Config {
467///     enabled: bool,
468///     format: String,
469/// }
470///
471/// // #[config(enabed, fromat = "json")]
472/// //
473/// // error: unknown argument `enabed`
474/// //   --> src/lib.rs:10:10
475/// //    |
476/// // 10 | #[config(enabed, fromat = "json")]
477/// //    |          ^^^^^^
478/// //    |
479/// //    = help: did you mean `enabled`?
480/// //
481/// // error: unknown argument `fromat`
482/// //   --> src/lib.rs:10:18
483/// //    |
484/// // 10 | #[config(enabed, fromat = "json")]
485/// //    |                  ^^^^^^
486/// //    |
487/// //    = help: did you mean `format`?
488/// ```
489///
490/// Enum derive for variant dispatch:
491///
492/// ```ignore
493/// #[derive(zyn::Attribute)]
494/// enum Mode {
495///     Fast,
496///     Slow,
497///     Custom { speed: i64 },
498/// }
499///
500/// let arg: zyn::Arg = zyn::parse!("custom(speed = 10)").unwrap();
501/// let mode = Mode::from_arg(&arg).unwrap();
502/// // mode == Mode::Custom { speed: 10 }
503/// ```
504#[proc_macro_derive(Attribute, attributes(zyn))]
505pub fn derive_attribute(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
506    attribute::expand(input.into()).into()
507}