zyn
A template engine for Rust procedural macros. Write code-generation templates with control flow, interpolation pipes, composable elements, and proc macro entry points (#[zyn::derive], #[zyn::attribute]).
Quick Start
use zyn::prelude::*;
zyn! {
@if (input.vis == syn::Visibility::Public(..)) { pub }
fn {{ input.ident | snake }}() {
println!("hello!");
}
}
Template Syntax
Interpolation
Insert any expression — field access, method calls, anything that implements ToTokens:
zyn! {
fn {{ input.ident }}() -> {{ field.ty }} {}
{{ input.ident | str }}
{{ fields.len() }}
}
Pipes
Transform values inline:
zyn! {
fn {{ name | snake }}() {} const {{ name | screaming }}: &str = ""; {{ name | upper }} {{ name | lower }} {{ name | camel }} {{ name | pascal }} {{ name | kebab }} {{ name | str }} {{ name | trim:"_" }} {{ name | plural }} {{ name | singular }} {{ name | snake | upper }} fn {{ name | ident:"get_{}" }}() {} const X: &str = {{ name | fmt:"{}!" }}; }
Control Flow
zyn! {
@if (input.is_pub) { pub }
@else if (input.is_crate) { pub(crate) }
struct {{ input.ident }} {
@for (field in fields.iter()) {
{{ field.ident }}: {{ field.ty }},
}
}
@match (input.kind) {
Kind::Struct => { impl {{ input.ident }} {} }
_ => {}
}
@for (fields.len()) {
,
}
}
Elements
Reusable template components:
#[zyn::element]
fn field_decl(
vis: syn::Visibility,
name: syn::Ident,
ty: syn::Type,
) -> zyn::TokenStream {
zyn::zyn! { {{ vis }} {{ name }}: {{ ty }}, }
}
zyn! {
struct {{ input.ident }} {
@for (field in fields.iter()) {
@field_decl(
vis = field.vis.clone(),
name = field.ident.clone().unwrap(),
ty = field.ty.clone(),
)
}
}
}
Children:
#[zyn::element]
fn wrapper(
vis: syn::Visibility,
children: zyn::TokenStream,
) -> zyn::TokenStream {
zyn::zyn! { {{ vis }} struct Foo { {{ children }} } }
}
zyn! {
@wrapper(vis = input.vis.clone()) {
name: String,
}
}
Zero parameters:
#[zyn::element]
fn divider() -> zyn::TokenStream {
zyn::zyn!(const DIVIDER: &str = "---";)
}
zyn! { @divider }
Custom Pipes
#[zyn::pipe]
fn prefix(input: String) -> syn::Ident {
syn::Ident::new(
&format!("pfx_{}", input),
zyn::Span::call_site(),
)
}
zyn! { {{ name | prefix }} }
Attribute Parsing
Derive typed attribute structs — FromInput handles extraction automatically:
#[derive(zyn::Attribute)]
#[zyn("builder", about = "Configure the builder derive")]
struct BuilderConfig {
#[zyn(default = "build".to_string())]
method: String,
skip: bool,
}
let input: zyn::Input = real_derive_input.into();
let cfg = BuilderConfig::from_input(&input)?;
For element params, use zyn::Attr<T> to auto-resolve from the input context:
#[zyn::element]
fn builder_method(
#[zyn(input)] cfg: zyn::Attr<BuilderConfig>, name: syn::Ident, ) -> zyn::TokenStream {
let method = zyn::format_ident!("{}", cfg.method);
zyn::zyn! { pub fn {{ method }}(self) -> Self { self } }
}
Proc Macro Entry Points
Replace #[proc_macro_derive] and #[proc_macro_attribute] with zyn equivalents that auto-parse input and provide diagnostics:
#[zyn::derive]
fn my_derive(
#[zyn(input)] fields: zyn::Fields,
#[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
) -> zyn::TokenStream {
zyn::zyn!(
impl {{ ident }} {
@for (field in fields.iter()) {
}
}
)
}
#[zyn::attribute]
fn my_attr(
#[zyn(input)] item: zyn::syn::ItemFn,
args: zyn::syn::LitStr,
) -> zyn::TokenStream {
let _name = args.value();
zyn::zyn!({ { item } })
}
Debugging
zyn::debug! is a drop-in replacement for zyn! that prints what the template produces:
zyn::debug! { struct {{ name }} {} } zyn::debug! { raw => struct {{ name }} {} } zyn::debug! { ast => struct {{ name }} {} }
Case Conversion
Available outside templates via the case module:
zyn::case::to_snake("HelloWorld") zyn::case::to_pascal("hello_world") zyn::case::to_camel("hello_world") zyn::case::to_screaming("HelloWorld") zyn::case::to_kebab("HelloWorld")
License
MIT