176 lines
5.0 KiB
Rust
176 lines
5.0 KiB
Rust
//! This example shows how to do struct and field parsing using darling.
|
|
|
|
#[macro_use]
|
|
extern crate darling;
|
|
extern crate proc_macro2;
|
|
#[macro_use]
|
|
extern crate quote;
|
|
extern crate syn;
|
|
|
|
use darling::ast;
|
|
use darling::FromDeriveInput;
|
|
use proc_macro2::TokenStream;
|
|
use quote::ToTokens;
|
|
use syn::parse_str;
|
|
|
|
/// A speaking volume. Deriving `FromMeta` will cause this to be usable
|
|
/// as a string value for a meta-item key.
|
|
#[derive(Debug, Clone, Copy, FromMeta)]
|
|
#[darling(default)]
|
|
enum Volume {
|
|
Normal,
|
|
Whisper,
|
|
Shout,
|
|
}
|
|
|
|
impl Default for Volume {
|
|
fn default() -> Self {
|
|
Volume::Normal
|
|
}
|
|
}
|
|
|
|
/// Support parsing from a full derive input. Unlike FromMeta, this isn't
|
|
/// composable; each darling-dependent crate should have its own struct to handle
|
|
/// when its trait is derived.
|
|
#[derive(Debug, FromDeriveInput)]
|
|
// This line says that we want to process all attributes declared with `my_trait`,
|
|
// and that darling should panic if this receiver is given an enum.
|
|
#[darling(attributes(my_trait), supports(struct_any))]
|
|
struct MyInputReceiver {
|
|
/// The struct ident.
|
|
ident: syn::Ident,
|
|
|
|
/// The type's generics. You'll need these any time your trait is expected
|
|
/// to work with types that declare generics.
|
|
generics: syn::Generics,
|
|
|
|
/// Receives the body of the struct or enum. We don't care about
|
|
/// struct fields because we previously told darling we only accept structs.
|
|
data: ast::Data<(), MyFieldReceiver>,
|
|
|
|
/// The Input Receiver demands a volume, so use `Volume::Normal` if the
|
|
/// caller doesn't provide one.
|
|
#[darling(default)]
|
|
volume: Volume,
|
|
}
|
|
|
|
impl ToTokens for MyInputReceiver {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
let MyInputReceiver {
|
|
ref ident,
|
|
ref generics,
|
|
ref data,
|
|
volume,
|
|
} = *self;
|
|
|
|
let (imp, ty, wher) = generics.split_for_impl();
|
|
let fields = data
|
|
.as_ref()
|
|
.take_struct()
|
|
.expect("Should never be enum")
|
|
.fields;
|
|
|
|
// Generate the format string which shows each field and its name
|
|
let fmt_string = fields
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, f)| {
|
|
// We have to preformat the ident in this case so we can fall back
|
|
// to the field index for unnamed fields. It's not easy to read,
|
|
// unfortunately.
|
|
format!(
|
|
"{} = {{}}",
|
|
f.ident
|
|
.as_ref()
|
|
.map(|v| format!("{}", v))
|
|
.unwrap_or_else(|| format!("{}", i))
|
|
)
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
|
|
// Generate the actual values to fill the format string.
|
|
let field_list = fields
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(i, f)| {
|
|
let field_volume = f.volume.unwrap_or(volume);
|
|
|
|
// This works with named or indexed fields, so we'll fall back to the index so we can
|
|
// write the output as a key-value pair.
|
|
let field_ident = f.ident
|
|
.as_ref()
|
|
.map(|v| quote!(#v))
|
|
.unwrap_or_else(|| quote!(#i));
|
|
|
|
match field_volume {
|
|
Volume::Normal => quote!(self.#field_ident),
|
|
Volume::Shout => {
|
|
quote!(::std::string::ToString::to_string(&self.#field_ident).to_uppercase())
|
|
}
|
|
Volume::Whisper => {
|
|
quote!(::std::string::ToString::to_string(&self.#field_ident).to_lowercase())
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
tokens.extend(quote! {
|
|
impl #imp Speak for #ident #ty #wher {
|
|
fn speak(&self, writer: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
|
write!(#fmt_string, #(#field_list),*)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, FromField)]
|
|
#[darling(attributes(my_trait))]
|
|
struct MyFieldReceiver {
|
|
/// Get the ident of the field. For fields in tuple or newtype structs or
|
|
/// enum bodies, this can be `None`.
|
|
ident: Option<syn::Ident>,
|
|
|
|
/// This magic field name pulls the type from the input.
|
|
ty: syn::Type,
|
|
|
|
/// We declare this as an `Option` so that during tokenization we can write
|
|
/// `field.volume.unwrap_or(derive_input.volume)` to facilitate field-level
|
|
/// overrides of struct-level settings.
|
|
#[darling(default)]
|
|
volume: Option<Volume>,
|
|
}
|
|
|
|
fn main() {
|
|
let input = r#"#[derive(MyTrait)]
|
|
#[my_trait(volume = "shout")]
|
|
pub struct Foo {
|
|
#[my_trait(volume = "whisper")]
|
|
bar: bool,
|
|
|
|
baz: i64,
|
|
}"#;
|
|
|
|
let parsed = parse_str(input).unwrap();
|
|
let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap();
|
|
let tokens = quote!(#receiver);
|
|
|
|
println!(
|
|
r#"
|
|
INPUT:
|
|
|
|
{}
|
|
|
|
PARSED AS:
|
|
|
|
{:?}
|
|
|
|
EMITS:
|
|
|
|
{}
|
|
"#,
|
|
input, receiver, tokens
|
|
);
|
|
}
|