diff --git a/Cargo.toml b/Cargo.toml index b320fbc..cfe1947 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,27 @@ documentation = "https://docs.rs/struct_iterable" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = [ + "struct_iterable_derive", + "struct_iterable_internal", +] + [dependencies] -struct_iterable_derive = "0.1.0" -struct_iterable_internal = "0.1.1" +struct_iterable_derive = { path="struct_iterable_derive" } +struct_iterable_internal = { path="struct_iterable_internal" } [lib] name = "struct_iterable" path = "src/lib.rs" +[[example]] +name = "custom-trait" +path = "examples/custom_trait.rs" + +[[example]] +name = "any" +path = "examples/any.rs" + [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/examples/any.rs b/examples/any.rs new file mode 100644 index 0000000..733d354 --- /dev/null +++ b/examples/any.rs @@ -0,0 +1,24 @@ +use struct_iterable::Iterable; + +// Use iterable attribut without custom item +// defaults to std::any::Any +#[derive(Iterable)] +struct MyStruct { + print_me: Option, + do_not_print_me: Vec, +} + +fn main() { + let my_struct = MyStruct { + print_me: Some("the test works".to_string()), + do_not_print_me: vec![4,2], + }; + + for (key, value) in my_struct.iter() { + if let Some(string_opt) = value.downcast_ref::>() { + if let Some(string) = string_opt.as_deref() { + println!("{key}=\"{string}\""); + } + } + } +} diff --git a/examples/custom_trait.rs b/examples/custom_trait.rs new file mode 100644 index 0000000..4e00f92 --- /dev/null +++ b/examples/custom_trait.rs @@ -0,0 +1,20 @@ +use struct_iterable::Iterable; + +// Use the iterable attribute to derive a custom trait instead +#[derive(Iterable)] +#[iterable(ToString)] +struct MyStruct { + a: u32, + b: String, +} + +fn main() { + let my_struct = MyStruct { + a: 42, + b: String::from("foobar"), + }; + + for (key, value) in my_struct.iter() { + println!("{key}={}", value.to_string()); + } +} diff --git a/struct_iterable_derive/Cargo.toml b/struct_iterable_derive/Cargo.toml index 1b25272..203f0fd 100644 --- a/struct_iterable_derive/Cargo.toml +++ b/struct_iterable_derive/Cargo.toml @@ -13,6 +13,6 @@ proc-macro = true [dependencies] syn = "2.0.13" quote = "1.0.26" -proc-macro2 = "1.0.56" -erased-serde = "0.3.7" -struct_iterable_internal = "0.1.1" \ No newline at end of file +proc-macro2 = "1.0.79" +erased-serde = "0.4.4" +struct_iterable_internal = "0.1.1" diff --git a/struct_iterable_derive/src/lib.rs b/struct_iterable_derive/src/lib.rs index 5aeab33..dda0107 100644 --- a/struct_iterable_derive/src/lib.rs +++ b/struct_iterable_derive/src/lib.rs @@ -2,8 +2,7 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; -use struct_iterable_internal::Iterable; +use syn::{Meta, parse_macro_input, Data, DeriveInput, Fields}; /// The `Iterable` proc macro. /// @@ -37,13 +36,28 @@ use struct_iterable_internal::Iterable; /// }; /// /// for (field_name, field_value) in my_instance.iter() { -/// println!("{}: {:?}", field_name, field_value); +/// println!("{field_name}: {field_value:?}"); /// } /// ``` -#[proc_macro_derive(Iterable)] +#[proc_macro_derive(Iterable, attributes(iterable))] pub fn derive_iterable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); + let trait_name = match input.attrs.iter().find( + |a| a.path().segments.len() == 1 && a.path().segments[0].ident == "iterable" + ) { + Some(attr) => { + if let Meta::List(meta) = &attr.meta { + meta.tokens.clone() + } else { + panic!("Invalid format of \"iterable\" attribute"); + } + }, + None => { + quote!(std::any::Any) + }, + }; + let struct_name = input.ident; let fields = match input.data { Data::Struct(data_struct) => match data_struct.fields { @@ -57,19 +71,19 @@ pub fn derive_iterable(input: TokenStream) -> TokenStream { let field_ident = &field.ident; let field_name = field_ident.as_ref().unwrap().to_string(); quote! { - (#field_name, &(self.#field_ident) as &dyn std::any::Any) + (#field_name, &(self.#field_ident) as &dyn #trait_name) } }); - let expanded = quote! { - impl Iterable for #struct_name { - fn iter<'a>(&'a self) -> std::vec::IntoIter<(&'static str, &'a dyn std::any::Any)> { + TokenStream::from(quote! { + impl<'a> struct_iterable::Iterable for &'a #struct_name { + type Item = &'a dyn #trait_name; + + fn iter(self) -> std::vec::IntoIter<(&'static str, Self::Item)> { vec![ #(#fields_iter),* ].into_iter() } } - }; - - TokenStream::from(expanded) -} \ No newline at end of file + }) +} diff --git a/struct_iterable_internal/src/lib.rs b/struct_iterable_internal/src/lib.rs index ac25269..1417373 100644 --- a/struct_iterable_internal/src/lib.rs +++ b/struct_iterable_internal/src/lib.rs @@ -27,6 +27,8 @@ /// } /// ``` pub trait Iterable { + type Item; + /// Returns an iterator over the struct's fields as tuples. /// /// Each tuple contains a field's name as a static string and a reference to the field's value as `dyn Any`. @@ -52,5 +54,5 @@ pub trait Iterable { /// println!("{}: {:?}", field_name, field_value); /// } /// ``` - fn iter(&self) -> std::vec::IntoIter<(&'static str, &'_ dyn std::any::Any)>; -} \ No newline at end of file + fn iter(self) -> std::vec::IntoIter<(&'static str, Self::Item)>; +}