metalos/lib/systemd/macros.rs (162 lines of code) (raw):

use convert_case::{Case, Casing}; use proc_macro::TokenStream; use quote::quote; use syn::spanned::Spanned; use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Type}; fn expand_systemd_enum(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> { let name = input.ident.clone(); match input.data { Data::Enum(e) => { let to_strs = e.variants.iter().map(|v| { let i = v.ident.clone(); let name = i.to_string().to_case(Case::Kebab); match name.as_str() { "unknown" => match v.fields { Fields::Unit => quote! { Self::Unknown => "unknown" }, _ => quote! { Self::Unknown(v) => v, }, }, _ => quote! { Self::#i => #name, }, } }); let from_strs = e.variants.iter().map(|v| { let i = v.ident.clone(); let name = i.to_string().to_case(Case::Kebab); match name.as_str() { "unknown" => match v.fields { Fields::Unit => quote! { _ => Self::Unknown, }, _ => quote! { value => Ok(Self::Unknown(value.to_owned())), }, }, _ => quote! { #name => Ok(Self::#i), }, } }); Ok(quote! { impl ::zvariant::Type for #name { fn signature() -> ::zvariant::Signature<'static> { <String as ::zvariant::Type>::signature() } } impl ::std::str::FromStr for #name { type Err = ::zvariant::Error; fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> { match s { #(#from_strs)* _ => Err(::zvariant::Error::Message(format!("Unknown variant {}", s))), } } } impl ::std::fmt::Display for #name { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { let s = match self { #(#to_strs)* }; write!(f, "{}", s) } } impl<'de> ::serde::Deserialize<'de> for #name { fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> where D: ::serde::Deserializer<'de> { let s: String = ::serde::Deserialize::deserialize(deserializer)?; s.parse().map_err(::serde::de::Error::custom) } } impl ::serde::Serialize for #name { fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> where S: ::serde::Serializer { serializer.serialize_str(&self.to_string()) } } impl<'v> ::std::convert::TryFrom<::zvariant::Value<'v>> for #name { type Error = ::zvariant::Error; fn try_from(v: ::zvariant::Value<'v>) -> ::zvariant::Result<Self> { String::try_from(v).and_then(|v| v.parse()) } } impl ::std::convert::TryFrom<::zvariant::OwnedValue> for #name { type Error = ::zvariant::Error; fn try_from(v: ::zvariant::OwnedValue) -> ::zvariant::Result<Self> { String::try_from(v).and_then(|v| v.parse()) } } }) } _ => Err(Error::new(input.span(), "not enum")), } } #[proc_macro_derive(SystemdEnum)] pub fn derive_systemd_enum(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); expand_systemd_enum(input) .unwrap_or_else(|err| err.to_compile_error()) .into() } fn expand_transparent_zvariant(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> { let name = input.ident.clone(); let input_span = input.span(); match input.data { Data::Struct(s) => match s.fields { Fields::Unnamed(fields) => match fields.unnamed.iter().collect::<Vec<_>>().as_slice() { [f] => { let ty = &f.ty; // I can't figure out / believe that it's impossible to // implement a generic From<T> for #name where T: Into<#ty>, // so for practical usage I'm going to special case where ty // is String so that we have a From<&str> impl let s: Type = syn::parse_str("String").unwrap(); let fqs: Type = syn::parse_str("std::string::String").unwrap(); let mut maybe_str_special = quote! {}; if ty == &s || ty == &fqs { maybe_str_special = quote! { impl ::std::convert::From<&str> for #name { #[inline] fn from(s: &str) -> Self { Self(s.to_owned()) } } impl ::std::cmp::PartialEq<&str> for #name { #[inline] fn eq(&self, s: &&str) -> bool { self.0 == *s } } impl ::std::cmp::PartialEq<#name> for &str { #[inline] fn eq(&self, o: &#name) -> bool { *self == o.0 } } impl ::std::convert::AsRef<str> for #name { fn as_ref(&self) -> &str { self.0.as_str() } } }; } Ok(quote! { impl ::zvariant::Type for #name { fn signature() -> ::zvariant::Signature<'static> { <#ty as ::zvariant::Type>::signature() } } impl<'v> ::std::convert::TryFrom<::zvariant::Value<'v>> for #name { type Error = ::zvariant::Error; fn try_from(v: ::zvariant::Value<'v>) -> ::zvariant::Result<Self> { #ty::try_from(v).map(Self) } } impl ::std::convert::TryFrom<::zvariant::OwnedValue> for #name { type Error = ::zvariant::Error; fn try_from(v: ::zvariant::OwnedValue) -> ::zvariant::Result<Self> { #ty::try_from(v).map(Self) } } impl<'de> ::serde::Deserialize<'de> for #name { fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> where D: ::serde::Deserializer<'de> { ::serde::Deserialize::deserialize(deserializer).map(Self) } } impl ::serde::Serialize for #name { fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error> where S: ::serde::Serializer { self.0.serialize(serializer) } } impl ::std::convert::From<#ty> for #name { fn from(x: #ty) -> Self { Self(x) } } impl ::std::convert::From<#name> for #ty { fn from(x: #name) -> #ty { x.0 } } impl ::std::fmt::Display for #name { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { self.0.fmt(f) } } impl ::std::cmp::PartialEq<#ty> for #name { fn eq(&self, o: &#ty) -> bool { self.0 == *o } } impl ::std::cmp::PartialEq<#name> for #ty { fn eq(&self, o: &#name) -> bool { *self == o.0 } } impl ::std::convert::AsRef<#ty> for #name { fn as_ref(&self) -> &#ty { &self.0 } } #maybe_str_special }) } _ => Err(Error::new(input_span, "struct must have one unnamed field")), }, _ => Err(Error::new(input_span, "struct must have one unnamed field")), }, _ => Err(Error::new(input_span, "not struct")), } } #[proc_macro_derive(TransparentZvariant)] pub fn derive_transparent_zvariant(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); expand_transparent_zvariant(input) .unwrap_or_else(|err| err.to_compile_error()) .into() }