use proc_macro::TokenStream; use quote::quote; use syn::{Data, DeriveInput, Fields, Visibility, parse_macro_input}; #[proc_macro_derive(OmeXML)] pub fn ome_metadata_derive_xml(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let expanded = quote! { impl #ident { pub fn from_xml>(s: T) -> Result { Ok(from_str(s.as_ref())?) } pub fn to_xml(&self) -> Result { Ok(to_string(self)?) } } }; TokenStream::from(expanded) } #[proc_macro_derive(PyOmeComplexEnum)] pub fn ome_metadata_derive_complex_enum(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let mut variants = Vec::new(); match input.data { Data::Enum(s) => { for variant in &s.variants { let variant_ident = &variant.ident; variants.push(quote! { Self::#variant_ident(v) => v.clone().into_bound_py_any(py)?, }); } } _ => { return syn::Error::new_spanned(ident, "PyOmeComplexEnum only supports structs") .to_compile_error() .into(); } }; let expanded = quote! { impl PyDisplay for #ident { fn py_str(&self) -> String { format!("{:?}", self) } } #[pyo3::prelude::pymethods] impl #ident { /// all possible variants of this enum that can be constructed or converted into #[staticmethod] fn variants() -> Vec { Self::VARIANTS.iter().map(|v| v.trim_matches('"').to_string()).collect() } fn __repr__(&self) -> String { self.py_str() } fn __str__(&self) -> String { self.py_str() } fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<(Bound<'py, PyAny>,)> { Ok((match self { #(#variants)* },)) } #[staticmethod] #[pyo3(name = "from_xml")] fn py_from_xml(xml: &str) -> PyResult { Ok(Self::from_xml(xml)?) } #[pyo3(name = "to_xml")] fn py_to_xml(&self) -> PyResult { Ok(self.to_xml()?) } } }; TokenStream::from(expanded) } #[proc_macro_derive(PyOmeEnum)] pub fn ome_metadata_derive_enum(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; let expanded = quote! { impl PyDisplay for #name { fn py_str(&self) -> String { format!("{}", self) } } #[pyo3::prelude::pymethods] impl #name { #[new] fn new(unit: &str) -> PyResult { unit.parse().map_err(|_| PyErr::new::(format!("could not parse {}", unit))) } /// all possible variants of this enum that can be constructed or converted into #[staticmethod] fn variants() -> Vec { Self::VARIANTS.iter().map(|v| v.trim_matches('"').to_string()).collect() } fn __repr__(&self) -> String { self.py_str() } fn __str__(&self) -> String { self.py_str() } fn __getnewargs__(&self) -> (String,) { (format!("{}", self),) } #[staticmethod] #[pyo3(name = "from_xml")] fn py_from_xml(xml: &str) -> PyResult { Ok(Self::from_xml(xml)?) } #[pyo3(name = "to_xml")] fn py_to_xml(&self) -> PyResult { Ok(self.to_xml()?) } } }; TokenStream::from(expanded) } #[proc_macro_derive(PyOmeUnit)] pub fn ome_metadata_derive_unit(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; let expanded = quote! { impl PyDisplay for #name { fn py_str(&self) -> String { format!("{}", self) } } #[pyo3::prelude::pymethods] impl #name { #[new] fn new(unit: &str) -> PyResult { unit.parse().map_err(|_| PyErr::new::(format!("could not parse {}", unit))) } /// convert a value between units #[pyo3(name = "convert")] fn py_convert(&self, unit: &Self, value: f64) -> PyResult { Ok(self.convert(unit, value)?) } /// all possible variants of this enum that can be constructed or converted into #[staticmethod] fn variants() -> Vec { Self::VARIANTS.iter().map(|v| v.trim_matches('"').to_string()).collect() } fn __repr__(&self) -> String { self.py_str() } fn __str__(&self) -> String { self.py_str() } fn __getnewargs__(&self) -> (String,) { (format!("{}", self),) } #[staticmethod] #[pyo3(name = "from_xml")] fn py_from_xml(xml: &str) -> PyResult { Ok(Self::from_xml(xml)?) } #[pyo3(name = "to_xml")] fn py_to_xml(&self) -> PyResult { Ok(self.to_xml()?) } } }; TokenStream::from(expanded) } #[proc_macro_derive(PyOme)] pub fn ome_metadata_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; // Only support named structs let fields = match input.data { Data::Struct(s) => match s.fields { Fields::Named(named) => named.named, _ => { return syn::Error::new_spanned(name, "PyOme only supports named structs") .to_compile_error() .into(); } }, _ => { return syn::Error::new_spanned(name, "PyOme only supports structs") .to_compile_error() .into(); } }; let mut idents = Vec::new(); let mut types = Vec::new(); let mut f = Vec::new(); let mut ps = Vec::new(); let mut new_kwargs = Vec::new(); let mut s = Vec::new(); for field in fields.iter() { let ty = &field.ty; if let Some(ident) = &field.ident && let Visibility::Public(_) = field.vis { let tmp = ident.to_string(); let field_name = tmp.trim_start_matches("r#"); idents.push(quote! { self.#ident }); types.push(quote! { #ty }); f.push(quote! { pub #ident: #ty }); ps.push(quote! { self.#ident.py_str() }); new_kwargs.push(quote! { (#field_name, self.#ident.clone().into_bound_py_any(py)?) }); s.push(format!("{}: {{}}", field_name)); } else { return syn::Error::new_spanned(field, "PyOme type only supports public named fields") .to_compile_error() .into(); } } let fmt = format!("{}({})", name, s.join(", ")); // Generate the token stream let expanded = quote! { impl PyDisplay for #name { fn py_str(&self) -> String { format!(#fmt, #(#ps,)*) } } #[pyo3::prelude::pymethods] impl #name { fn __getnewargs_ex__<'py>(&self, py: Python<'py>) -> PyResult<((), std::collections::HashMap<&str, Bound<'py, PyAny>>)> { Ok(((), std::collections::HashMap::from_iter([#(#new_kwargs,)*]))) } fn __repr__(&self) -> String { self.py_str() } fn __str__(&self) -> String { self.py_str() } #[staticmethod] #[pyo3(name = "from_xml")] fn py_from_xml(xml: &str) -> PyResult { Ok(Self::from_xml(xml)?) } #[pyo3(name = "to_xml")] fn py_to_xml(&self) -> PyResult { Ok(self.to_xml()?) } } }; TokenStream::from(expanded) }