zyn/lib.rs
1//! 
2//!
3//! A proc macro framework with templates, composable elements, and built-in diagnostics.
4//!
5//! ```sh
6//! cargo add zyn
7//! ```
8//!
9//! # Table of Contents
10//!
11//! - [Templates](#templates)
12//! - [Elements](#elements)
13//! - [Pipes](#pipes)
14//! - [Attributes](#attributes)
15//! - [Auto-suggest](#auto-suggest)
16//! - [Testing](#testing)
17//! - [Assertions](#assertions)
18//! - [Debugging](#debugging)
19//! - [Features](#features)
20//! - [ext](#ext)
21//! - [pretty](#pretty)
22//! - [diagnostics](#diagnostics)
23//!
24//! ---
25//!
26//! # Templates
27//!
28//! The [`zyn!`] macro is the core of zyn. Write token output as if it were source code,
29//! with `{{ }}` interpolation and `@` control flow directives.
30//!
31//! **Interpolation** — any [`quote::ToTokens`] value:
32//!
33//! ```ignore
34//! let name = zyn::format_ident!("hello_world");
35//! zyn::zyn!(fn {{ name }}() {})
36//! // → fn hello_world() {}
37//! ```
38//!
39//! **Pipes** — transform values inline:
40//!
41//! ```ignore
42//! zyn::zyn!(fn {{ name | pascal }}() {})
43//! // name = "hello_world" → fn HelloWorld() {}
44//! ```
45//!
46//! **Control flow:**
47//!
48//! ```ignore
49//! zyn::zyn!(
50//! @if (is_pub) { pub }
51//! @for (field in fields.named.iter()) {
52//! fn {{ field.ident }}(&self) -> &{{ field.ty }} {
53//! &self.{{ field.ident }}
54//! }
55//! }
56//! )
57//! ```
58//!
59//! **Full template syntax:**
60//!
61//! | Syntax | Purpose |
62//! |--------|---------|
63//! | `{{ expr }}` | Interpolate any [`quote::ToTokens`] value |
64//! | `{{ expr \| pipe }}` | Transform value through a [pipe](#pipes) before inserting |
65//! | `@if (cond) { ... }` | Conditional token emission |
66//! | `@else { ... }` | Else branch |
67//! | `@else if (cond) { ... }` | Else-if branch |
68//! | `@for (x in iter) { ... }` | Loop over an iterator |
69//! | `@for (N) { ... }` | Repeat N times |
70//! | `@match (expr) { pat => { ... } }` | Pattern-based emission |
71//! | `@element_name(prop = val)` | Invoke a [`#[element]`](macro@element) component |
72//!
73//! See [`zyn!`] for the full syntax reference.
74//!
75//! > Templates are fully type-checked at compile time — errors appear inline, just like regular Rust code.
76//!
77//! 
78//!
79//! ---
80//!
81//! # Elements
82//!
83//! Elements are reusable template components defined with [`#[zyn::element]`](macro@element).
84//! They encapsulate a fragment of token output and accept typed props.
85//!
86//! **Define an element:**
87//!
88//! ```ignore
89//! #[zyn::element]
90//! fn getter(name: zyn::syn::Ident, ty: zyn::syn::Type) -> zyn::TokenStream {
91//! zyn::zyn! {
92//! pub fn {{ name | snake | ident:"get_{}" }}(&self) -> &{{ ty }} {
93//! &self.{{ name }}
94//! }
95//! }
96//! }
97//! ```
98//!
99//! **Invoke it inside any template with `@`:**
100//!
101//! ```ignore
102//! zyn::zyn! {
103//! impl {{ ident }} {
104//! @for (field in fields.named.iter()) {
105//! @getter(name = field.ident.clone().unwrap(), ty = field.ty.clone())
106//! }
107//! }
108//! }
109//! ```
110//!
111//! Elements can also receive **extractors** — values resolved automatically from proc macro
112//! input — by marking a param with `#[zyn(input)]`:
113//!
114//! ```ignore
115//! #[zyn::derive]
116//! fn my_getters(
117//! #[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
118//! #[zyn(input)] fields: zyn::Fields<zyn::syn::FieldsNamed>,
119//! ) -> zyn::TokenStream {
120//! zyn::zyn! {
121//! impl {{ ident }} {
122//! @for (field in fields.named.iter()) {
123//! pub fn {{ field.ident | snake | ident:"get_{}" }}(&self) -> &{{ field.ty }} {
124//! &self.{{ field.ident }}
125//! }
126//! }
127//! }
128//! }
129//! }
130//! // Applied to: struct User { first_name: String, age: u32 }
131//! // Generates:
132//! // impl User {
133//! // pub fn get_first_name(&self) -> &String { &self.first_name }
134//! // pub fn get_age(&self) -> &u32 { &self.age }
135//! // }
136//! ```
137//!
138//! See [`element`](macro@element) and [`derive`](macro@derive) for the full reference.
139//!
140//! ---
141//!
142//! # Pipes
143//!
144//! Pipes transform interpolated values: `{{ expr | pipe }}`. They chain left to right:
145//!
146//! ```ignore
147//! zyn::zyn!(fn {{ name | snake | ident:"get_{}" }}() {})
148//! // name = "HelloWorld" → fn get_hello_world() {}
149//! ```
150//!
151//! **Built-in pipes:**
152//!
153//! | Pipe | Input example | Output |
154//! |------|--------------|--------|
155//! | `snake` | `HelloWorld` | `hello_world` |
156//! | `pascal` | `hello_world` | `HelloWorld` |
157//! | `camel` | `hello_world` | `helloWorld` |
158//! | `screaming` | `HelloWorld` | `HELLO_WORLD` |
159//! | `kebab` | `HelloWorld` | `"hello-world"` |
160//! | `upper` | `hello` | `HELLO` |
161//! | `lower` | `HELLO` | `hello` |
162//! | `str` | `hello` | `"hello"` |
163//! | `trim` | `__foo__` | `foo` |
164//! | `plural` | `user` | `users` |
165//! | `singular` | `users` | `user` |
166//! | `ident:"pattern_{}"` | `hello` | `pattern_hello` (ident) |
167//! | `fmt:"pattern_{}"` | `hello` | `"pattern_hello"` (string) |
168//!
169//! All built-in pipes are in the [`pipes`] module and re-exported by [`prelude`].
170//!
171//! **Custom pipes** via [`#[zyn::pipe]`](macro@pipe):
172//!
173//! ```ignore
174//! #[zyn::pipe]
175//! fn shout(input: String) -> zyn::syn::Ident {
176//! zyn::syn::Ident::new(&format!("{}_BANG", input.to_uppercase()), zyn::Span::call_site())
177//! }
178//!
179//! zyn::zyn!(fn {{ name | shout }}() {})
180//! // name = "hello" → fn HELLO_BANG() {}
181//! ```
182//!
183//! See [`pipe`](macro@pipe) and the [`Pipe`] trait for the full reference.
184//!
185//! ---
186//!
187//! # Attributes
188//!
189//! zyn provides two tools for attribute handling: a derive macro for typed parsing and a
190//! proc macro attribute for writing attribute macros.
191//!
192//! **Typed attribute structs** via [`#[derive(Attribute)]`](derive@Attribute):
193//!
194//! ```ignore
195//! #[derive(zyn::Attribute)]
196//! #[zyn("builder")]
197//! struct BuilderConfig {
198//! #[zyn(default)]
199//! skip: bool,
200//! #[zyn(default = "build".to_string())]
201//! method: String,
202//! }
203//! // users write: #[builder(skip)] or #[builder(method = "create")]
204//! ```
205//!
206//! The derive generates `from_args`, `FromArg`, and `FromInput` implementations, as well as
207//! a human-readable `about()` string for error messages.
208//!
209//! **Auto-suggest** — when a user misspells an argument name, zyn automatically suggests
210//! the closest known field. No extra setup required:
211//!
212//! ```text
213//! error: unknown argument `skiip`
214//! --> src/main.rs:5:12
215//! |
216//! 5 | #[builder(skiip)]
217//! | ^^^^^
218//! |
219//! = help: did you mean `skip`?
220//! ```
221//!
222//! Suggestions are offered when the edit distance is ≤ 3 characters. Distant or completely
223//! unknown keys produce only the "unknown argument" error without a suggestion.
224//!
225//! **Attribute proc macros** via [`#[zyn::attribute]`](macro@attribute):
226//!
227//! ```ignore
228//! #[zyn::attribute]
229//! fn my_attr(#[zyn(input)] item: zyn::syn::ItemFn, args: zyn::Args) -> zyn::TokenStream {
230//! // args: parsed key=value arguments from the attribute invocation
231//! zyn::zyn!({ { item } })
232//! }
233//! ```
234//!
235//! See [`Attribute`](derive@Attribute) and [`attribute`](macro@attribute) for the full reference.
236//!
237//! ---
238//!
239//! # Testing
240//!
241//! ## Assertions
242//!
243//! `zyn!` returns [`Output`] — test both tokens and diagnostics directly:
244//!
245//! ```ignore
246//! #[test]
247//! fn generates_function() {
248//! let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
249//! let output = zyn::zyn!(fn hello() {});
250//! let expected = zyn::quote::quote!(fn hello() {});
251//! zyn::assert_tokens!(output, expected);
252//! }
253//! ```
254//!
255//! Diagnostic assertions check error messages from `error!`, `warn!`, `bail!`:
256//!
257//! ```ignore
258//! #[zyn::element]
259//! fn validated(name: zyn::syn::Ident) -> zyn::TokenStream {
260//! if name == "forbidden" {
261//! bail!("reserved identifier `{}`", name);
262//! }
263//! zyn::zyn!(fn {{ name }}() {})
264//! }
265//!
266//! #[test]
267//! fn rejects_forbidden_name() {
268//! let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
269//! let output = zyn::zyn!(@validated(name = zyn::format_ident!("forbidden")));
270//! zyn::assert_diagnostic_error!(output, "reserved identifier");
271//! zyn::assert_tokens_empty!(output);
272//! }
273//!
274//! #[test]
275//! fn accepts_valid_name() {
276//! let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
277//! let output = zyn::zyn!(@validated(name = zyn::format_ident!("hello")));
278//! zyn::assert_tokens_contain!(output, "fn hello");
279//! }
280//! ```
281//!
282//! | Macro | Purpose |
283//! |-------|---------|
284//! | `assert_tokens!` | Compare two token streams |
285//! | `assert_tokens_empty!` | Assert no tokens produced |
286//! | `assert_tokens_contain!` | Check for substring in output |
287//! | `assert_diagnostic_error!` | Assert error diagnostic with message |
288//! | `assert_diagnostic_warning!` | Assert warning diagnostic |
289//! | `assert_diagnostic_note!` | Assert note diagnostic |
290//! | `assert_diagnostic_help!` | Assert help diagnostic |
291//! | `assert_compile_error!` | Alias for `assert_diagnostic_error!` |
292//!
293//! With the `pretty` feature:
294//!
295//! | Macro | Purpose |
296//! |-------|---------|
297//! | `assert_tokens_pretty!` | Compare using `prettyplease`-formatted output |
298//! | `assert_tokens_contain_pretty!` | Substring check on pretty-printed output |
299//!
300//! ## Debugging
301//!
302//! Add `debug` (or `debug(pretty)`) to any zyn attribute macro and set `ZYN_DEBUG` to
303//! inspect generated code at compile time:
304//!
305//! ```ignore
306//! #[zyn::element(debug)]
307//! fn greeting(name: zyn::syn::Ident) -> zyn::TokenStream {
308//! zyn::zyn!(fn {{ name }}() {})
309//! }
310//! ```
311//!
312//! ```sh
313//! ZYN_DEBUG="*" cargo build
314//! ```
315//!
316//! ```text
317//! note: zyn::element ─── Greeting
318//!
319//! struct Greeting { pub name : zyn :: syn :: Ident , } impl :: zyn :: Render
320//! for Greeting { fn render (& self , input : & :: zyn :: Input) -> :: zyn ::
321//! proc_macro2 :: TokenStream { ... } }
322//! ```
323//!
324//! With the `pretty` feature, use `debug(pretty)` for formatted output:
325//!
326//! ```ignore
327//! #[zyn::element(debug(pretty))]
328//! fn greeting(name: zyn::syn::Ident) -> zyn::TokenStream {
329//! zyn::zyn!(fn {{ name }}() {})
330//! }
331//! ```
332//!
333//! ```text
334//! note: zyn::element ─── Greeting
335//!
336//! struct Greeting {
337//! pub name: zyn::syn::Ident,
338//! }
339//! impl ::zyn::Render for Greeting {
340//! fn render(&self, input: &::zyn::Input) -> ::zyn::Output { ... }
341//! }
342//! ```
343//!
344//! All macros support `debug`: `#[zyn::element]`, `#[zyn::pipe]`, `#[zyn::derive]`,
345//! `#[zyn::attribute]`.
346//!
347//! `ZYN_DEBUG` accepts comma-separated `*`-wildcard patterns matched against the generated
348//! PascalCase type name:
349//!
350//! ```sh
351//! ZYN_DEBUG="Greeting" cargo build # exact match
352//! ZYN_DEBUG="Greet*" cargo build # prefix wildcard
353//! ZYN_DEBUG="*Element" cargo build # suffix wildcard
354//! ZYN_DEBUG="Greeting,Shout" cargo build # multiple patterns
355//! ```
356//!
357//! See the [`test`] module for the full assertion macro reference and the [`debug`] module
358//! for programmatic access via `DebugExt`.
359//!
360//! ---
361//!
362//! # Features
363//!
364//! | Feature | Default | Description |
365//! |---------|:-------:|-------------|
366//! | `derive` | ✓ | All proc macro attributes: [`element`](macro@element), [`pipe`](macro@pipe), [`derive`](macro@derive), [`attribute`](macro@attribute), and [`Attribute`](derive@Attribute) |
367//! | `ext` | | Extension traits for common `syn` AST types (`AttrExt`, `FieldExt`, `TypeExt`, etc.) — see [`ext`] |
368//! | `pretty` | | Pretty-printed token output in debug mode — see [`debug`] |
369//! | `diagnostics` | | Error accumulation — collect multiple errors before aborting — see [`mark`] |
370//!
371//! ## ext
372//!
373//! The [`ext`] module adds ergonomic extension traits for navigating `syn` AST types.
374//!
375//! ```toml
376//! zyn = { features = ["ext"] }
377//! ```
378//!
379//! ```ignore
380//! use zyn::ext::{AttrExt, TypeExt};
381//!
382//! // check and read attribute arguments
383//! if attr.is("serde") {
384//! let rename: Option<_> = attr.get("rename"); // → Some(Meta::NameValue)
385//! let skip: bool = attr.exists("skip");
386//! }
387//!
388//! // inspect field types
389//! if field.is_option() {
390//! let inner = field.inner_type().unwrap();
391//! }
392//! ```
393//!
394//! See the [`ext`] module for all available traits.
395//!
396//! ## pretty
397//!
398//! The `pretty` feature enables pretty-printed output in [`debug`] mode, formatting
399//! generated token streams as readable Rust source code via `prettyplease`.
400//!
401//! ```toml
402//! zyn = { features = ["pretty"] }
403//! ```
404//!
405//! Enable debug output per-element with the `debug` or `debug(pretty)` argument,
406//! then set `ZYN_DEBUG="*"` at build time:
407//!
408//! ```ignore
409//! #[zyn::element(debug(pretty))]
410//! fn my_element(name: zyn::syn::Ident) -> zyn::TokenStream {
411//! zyn::zyn!(struct {{ name }} {})
412//! }
413//! ```
414//!
415//! ```sh
416//! ZYN_DEBUG="*" cargo build
417//! ```
418//!
419//! ```text
420//! note: zyn::element ─── my_element
421//!
422//! struct MyElement {
423//! pub name: zyn::syn::Ident,
424//! }
425//! impl ::zyn::Render for MyElement {
426//! fn render(&self, input: &::zyn::Input) -> ::zyn::Output {
427//! ...
428//! }
429//! }
430//! ```
431//!
432//! > An element annotated with `debug` — the argument is inert until `ZYN_DEBUG` is set.
433//!
434//! 
435//!
436//! > Raw debug output shown as an inline compiler diagnostic in the editor.
437//!
438//! 
439//!
440//! > The same raw output surfaced in the Problems panel for easy navigation.
441//!
442//! 
443//!
444//! > Pretty-printed debug output — formatted with `prettyplease` for readable, indented Rust code.
445//!
446//! 
447//!
448//! See the [`debug`] module for programmatic access via `DebugExt`.
449//!
450//! ## diagnostics
451//!
452//! The `diagnostics` feature enables error accumulation — collecting multiple compiler
453//! errors before aborting, so users see all problems at once instead of one at a time.
454//!
455//! ```toml
456//! zyn = { features = ["diagnostics"] }
457//! ```
458//!
459//! Inside any `#[zyn::element]`, `#[zyn::derive]`, or `#[zyn::attribute]` body, use the
460//! built-in diagnostic macros directly — no setup required:
461//!
462//! ```ignore
463//! #[zyn::element]
464//! fn my_element(name: zyn::syn::Ident) -> zyn::TokenStream {
465//! if name == "forbidden" {
466//! bail!("reserved identifier `{}`", name);
467//! }
468//!
469//! if name.to_string().starts_with('_') {
470//! warn!("identifiers starting with `_` are conventionally unused");
471//! }
472//!
473//! zyn::zyn!(fn {{ name }}() {})
474//! }
475//! ```
476//!
477//! | Macro | Level | Behaviour |
478//! |-------|-------|-----------|
479//! | `error!(msg)` | error | accumulates, does not stop execution |
480//! | `warn!(msg)` | warning | accumulates, does not stop execution |
481//! | `note!(msg)` | note | accumulates, does not stop execution |
482//! | `help!(msg)` | help | accumulates, does not stop execution |
483//! | `bail!(msg)` | error | accumulates and immediately returns |
484//!
485//! All accumulated diagnostics are emitted together at the end of the element or macro body,
486//! so users see every error at once instead of fixing them one by one.
487//!
488//! ```text
489//! error: reserved identifier `forbidden`
490//! --> src/main.rs:3:1
491//!
492//! error: reserved identifier `forbidden`
493//! --> src/main.rs:7:1
494//! ```
495//!
496//! See the [`mark`] module for the lower-level diagnostic builder API.
497
498pub use zyn_core::*;
499
500#[cfg(feature = "derive")]
501pub use zyn_derive::*;
502
503pub mod test;
504
505/// The zyn prelude. Re-exports all built-in pipes, core traits, and proc macros.
506pub mod prelude {
507 pub use crate::pipes::*;
508 pub use crate::{Diagnostic, Output, Pipe, Render};
509
510 #[cfg(feature = "derive")]
511 pub use zyn_derive::*;
512
513 #[cfg(feature = "ext")]
514 pub use zyn_core::ext::*;
515}