wezterm-dynamic-derive-0.1.0/.cargo_vcs_info.json0000644000000001640000000000100154260ustar { "git": { "sha1": "355b6d835bccb2548f5fda4a81dfdaa8e7eaa568" }, "path_in_vcs": "wezterm-dynamic/derive" }wezterm-dynamic-derive-0.1.0/Cargo.toml0000644000000015210000000000100134220ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "wezterm-dynamic-derive" version = "0.1.0" description = "config serialization for wezterm via dynamic json-like data values" license = "MIT" repository = "https://github.com/wez/wezterm" resolver = "2" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0.2" [dependencies.syn] version = "1.0" wezterm-dynamic-derive-0.1.0/Cargo.toml.orig000064400000000000000000000004670072674642500171430ustar 00000000000000[package] name = "wezterm-dynamic-derive" version = "0.1.0" edition = "2021" repository = "https://github.com/wez/wezterm" description = "config serialization for wezterm via dynamic json-like data values" license = "MIT" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0.2" syn = "1.0" wezterm-dynamic-derive-0.1.0/src/attr.rs000064400000000000000000000325140072674642500163610ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; use syn::{Attribute, Error, Field, Lit, Meta, NestedMeta, Path, Result}; pub struct ContainerInfo { pub into: Option, pub try_from: Option, pub debug: bool, } pub fn container_info(attrs: &[Attribute]) -> Result { let mut into = None; let mut try_from = None; let mut debug = false; for attr in attrs { if !attr.path.is_ident("dynamic") { continue; } let list = match attr.parse_meta()? { Meta::List(list) => list, other => return Err(Error::new_spanned(other, "unsupported attribute")), }; for meta in &list.nested { match meta { NestedMeta::Meta(Meta::Path(path)) => { if path.is_ident("debug") { debug = true; continue; } } NestedMeta::Meta(Meta::NameValue(value)) => { if value.path.is_ident("into") { if let Lit::Str(s) = &value.lit { into = Some(s.parse()?); continue; } } if value.path.is_ident("try_from") { if let Lit::Str(s) = &value.lit { try_from = Some(s.parse()?); continue; } } } _ => {} } return Err(Error::new_spanned(meta, "unsupported attribute")); } } Ok(ContainerInfo { into, try_from, debug, }) } pub enum DefValue { None, Default, Path(Path), } pub struct FieldInfo<'a> { pub field: &'a Field, pub name: String, pub skip: bool, pub flatten: bool, pub allow_default: DefValue, pub into: Option, pub try_from: Option, pub deprecated: Option, pub validate: Option, } impl<'a> FieldInfo<'a> { pub fn to_dynamic(&self) -> TokenStream { let name = &self.name; let ident = &self.field.ident; if self.skip { quote!() } else if self.flatten { quote!( self.#ident.place_dynamic(place); ) } else if let Some(into) = &self.into { quote!( let target : #into = (&self.#ident).into(); place.insert(#name.to_dynamic(), target.to_dynamic()); ) } else { quote!( place.insert(#name.to_dynamic(), self.#ident.to_dynamic()); ) } } pub fn from_dynamic(&self, struct_name: &str) -> TokenStream { let name = &self.name; let ident = &self.field.ident; let ty = &self.field.ty; let check_deprecated = if let Some(reason) = &self.deprecated { quote!( wezterm_dynamic::Error::raise_deprecated_fields(options, #struct_name, #name, #reason)?; ) } else { quote!() }; let validate_value = if let Some(validator) = &self.validate { quote!( #validator(value).map_err(|msg| { wezterm_dynamic::Error::ErrorInField{ type_name: #struct_name, field_name: #name, error: msg, } })?; ) } else { quote!() }; if self.skip { quote!() } else if self.flatten { quote!( #ident: <#ty>::from_dynamic(value, options) .map_err(|source| source.field_context( #struct_name, #name, obj))?, ) } else if let Some(try_from) = &self.try_from { match &self.allow_default { DefValue::Default => { quote!( #ident: match obj.get_by_str(#name) { Some(v) => { use std::convert::TryFrom; #check_deprecated let target = <#try_from>::from_dynamic(v, options) .map_err(|source| source.field_context( #struct_name, #name, obj, ))?; let value = <#ty>::try_from(target) .map_err(|source| wezterm_dynamic::Error::ErrorInField{ type_name:#struct_name, field_name:#name, error: format!("{:#}", source) })?; #validate_value value } None => { <#ty>::default() } }, ) } DefValue::Path(default) => { quote!( #ident: match obj.get_by_str(&#name) { Some(v) => { use std::convert::TryFrom; #check_deprecated let target = <#try_from>::from_dynamic(v, options) .map_err(|source| source.field_context( #struct_name, #name, obj, ))?; let value = <#ty>::try_from(target) .map_err(|source| wezterm_dynamic::Error::ErrorInField{ type_name:#struct_name, field_name:#name, error: format!("{:#}", source), })?; #validate_value value } None => { #default() } }, ) } DefValue::None => { quote!( #ident: { use std::convert::TryFrom; let target = <#try_from>::from_dynamic(obj.get_by_str(#name).map(|v| { #check_deprecated v }).unwrap_or(&Value::Null), options) .map_err(|source| source.field_context( #struct_name, #name, obj, ))?; let value = <#ty>::try_from(target) .map_err(|source| wezterm_dynamic::Error::ErrorInField{ type_name:#struct_name, field_name:#name, error: format!("{:#}", source), })?; #validate_value value }, ) } } } else { match &self.allow_default { DefValue::Default => { quote!( #ident: match obj.get_by_str(#name) { Some(v) => { #check_deprecated let value = <#ty>::from_dynamic(v, options) .map_err(|source| source.field_context( #struct_name, #name, obj, ))?; #validate_value value } None => { <#ty>::default() } }, ) } DefValue::Path(default) => { quote!( #ident: match obj.get_by_str(#name) { Some(v) => { #check_deprecated let value = <#ty>::from_dynamic(v, options) .map_err(|source| source.field_context( #struct_name, #name, obj, ))?; #validate_value value } None => { #default() } }, ) } DefValue::None => { quote!( #ident: { let value = <#ty>::from_dynamic( obj.get_by_str(#name).map(|v| { #check_deprecated v }). unwrap_or(&Value::Null), options ) .map_err(|source| source.field_context(#struct_name, #name, obj))?; #validate_value value }, ) } } } } } pub fn field_info(field: &Field) -> Result { let mut name = field.ident.as_ref().unwrap().to_string(); let mut skip = false; let mut flatten = false; let mut allow_default = DefValue::None; let mut try_from = None; let mut validate = None; let mut into = None; let mut deprecated = None; for attr in &field.attrs { if !attr.path.is_ident("dynamic") { continue; } let list = match attr.parse_meta()? { Meta::List(list) => list, other => return Err(Error::new_spanned(other, "unsupported attribute")), }; for meta in &list.nested { match meta { NestedMeta::Meta(Meta::NameValue(value)) => { if value.path.is_ident("rename") { if let Lit::Str(s) = &value.lit { name = s.value(); continue; } } if value.path.is_ident("default") { if let Lit::Str(s) = &value.lit { allow_default = DefValue::Path(s.parse()?); continue; } } if value.path.is_ident("deprecated") { if let Lit::Str(s) = &value.lit { deprecated.replace(s.value()); continue; } } if value.path.is_ident("into") { if let Lit::Str(s) = &value.lit { into = Some(s.parse()?); continue; } } if value.path.is_ident("try_from") { if let Lit::Str(s) = &value.lit { try_from = Some(s.parse()?); continue; } } if value.path.is_ident("validate") { if let Lit::Str(s) = &value.lit { validate = Some(s.parse()?); continue; } } } NestedMeta::Meta(Meta::Path(path)) => { if path.is_ident("skip") { skip = true; continue; } if path.is_ident("flatten") { flatten = true; continue; } if path.is_ident("default") { allow_default = DefValue::Default; continue; } } _ => {} } return Err(Error::new_spanned(meta, "unsupported attribute")); } } Ok(FieldInfo { field, name, skip, flatten, allow_default, try_from, into, deprecated, validate, }) } wezterm-dynamic-derive-0.1.0/src/bound.rs000064400000000000000000000010100072674642500165010ustar 00000000000000use proc_macro2::TokenStream; use syn::{parse_quote, Generics, WhereClause, WherePredicate}; pub fn where_clause_with_bound(generics: &Generics, bound: TokenStream) -> WhereClause { let new_predicates = generics.type_params().map::(|param| { let param = ¶m.ident; parse_quote!(#param : #bound) }); let mut generics = generics.clone(); generics .make_where_clause() .predicates .extend(new_predicates); generics.where_clause.unwrap() } wezterm-dynamic-derive-0.1.0/src/fromdynamic.rs000064400000000000000000000320420072674642500177130ustar 00000000000000use crate::{attr, bound}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, FieldsNamed, Ident, Result, }; pub fn derive(input: DeriveInput) -> Result { match &input.data { Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => derive_struct(&input, fields), Data::Enum(enumeration) => derive_enum(&input, enumeration), Data::Struct(_) => Err(Error::new( Span::call_site(), "currently only structs with named fields are supported", )), Data::Union(_) => Err(Error::new( Span::call_site(), "currently only structs and enums are supported by this derive", )), } } fn derive_struct(input: &DeriveInput, fields: &FieldsNamed) -> Result { let info = attr::container_info(&input.attrs)?; let ident = &input.ident; let literal = ident.to_string(); let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl(); let dummy = Ident::new( &format!("_IMPL_FROMDYNAMIC_FOR_{}", ident), Span::call_site(), ); let placements = fields .named .iter() .map(attr::field_info) .collect::>>()?; let needs_default = placements.iter().any(|f| f.skip); let field_names = placements .iter() .filter_map(|f| { if f.skip || f.flatten { None } else { Some(f.name.to_string()) } }) .collect::>(); // If any of the fields are flattened, then we don't have enough // structure in the FromDynamic interface to know precisely which // fields were legitimately used by any recursively flattened item, // or, in the recursive item, to know which of the fields were used // by the parent. // We need to disable warning or raising errors for unknown fields // in that case to avoid false positives. let adjust_options = if placements.iter().any(|f| f.flatten) { quote!(let options = options.flatten();) } else { quote!() }; let field_names = quote!( &[ #( #field_names, )* ] ); let placements = placements .into_iter() .map(|f| f.from_dynamic(&literal)) .collect::>(); let bound = parse_quote!(wezterm_dynamic::FromDynamic); let bounded_where_clause = bound::where_clause_with_bound(&input.generics, bound); let obj = if needs_default { quote!( Ok(Self { #( #placements )* .. Self::default() }) ) } else { quote!( Ok(Self { #( #placements )* }) ) }; let from_dynamic = match info.try_from { Some(try_from) => { quote!( use std::convert::TryFrom; let target = <#try_from>::from_dynamic(value, options)?; <#ident>::try_from(target).map_err(|e| wezterm_dynamic::Error::Message(format!("{:#}", e))) ) } None => { quote!( match value { Value::Object(obj) => { wezterm_dynamic::Error::raise_unknown_fields(options, #literal, &obj, Self::possible_field_names())?; #obj } other => Err(wezterm_dynamic::Error::NoConversion { source_type: other.variant_name().to_string(), dest_type: #literal }), } ) } }; let tokens = quote! { #[allow(non_upper_case_globals)] const #dummy: () = { impl #impl_generics wezterm_dynamic::FromDynamic for #ident #ty_generics #bounded_where_clause { fn from_dynamic(value: &wezterm_dynamic::Value, options: wezterm_dynamic::FromDynamicOptions) -> std::result::Result { use wezterm_dynamic::{Value, BorrowedKey, ObjectKeyTrait}; #adjust_options #from_dynamic } } impl #impl_generics #ident #ty_generics #bounded_where_clause { pub const fn possible_field_names() -> &'static [&'static str] { #field_names } } }; }; if info.debug { eprintln!("{}", tokens); } Ok(tokens) } fn derive_enum(input: &DeriveInput, enumeration: &DataEnum) -> Result { if input.generics.lt_token.is_some() || input.generics.where_clause.is_some() { return Err(Error::new( Span::call_site(), "Enums with generics are not supported", )); } let info = attr::container_info(&input.attrs)?; let ident = &input.ident; let literal = ident.to_string(); let dummy = Ident::new( &format!("_IMPL_FROMDYNAMIC_FOR_{}", ident), Span::call_site(), ); let variant_names = enumeration .variants .iter() .map(|variant| variant.ident.to_string()) .collect::>(); let from_dynamic = match info.try_from { Some(try_from) => { quote!( use std::convert::TryFrom; let target = <#try_from>::from_dynamic(value, options)?; <#ident>::try_from(target).map_err(|e| wezterm_dynamic::Error::Message(format!("{:#}", e))) ) } None => { let units = enumeration .variants .iter() .filter_map(|variant| match &variant.fields { Fields::Unit => { let ident = &variant.ident; let literal = ident.to_string(); Some(quote!( #literal => { return Ok(Self::#ident); } )) } _ => None, }) .collect::>(); let variants = enumeration.variants.iter().map(|variant| { let ident = &variant.ident; let literal = ident.to_string(); match &variant.fields { Fields::Unit => { // Already handled separately quote!() } Fields::Named(fields) => { let var_fields = fields .named .iter() .map(|f| { let info = attr::field_info(f).unwrap(); info.from_dynamic(&literal) }) .collect::>(); quote!( #literal => { match value { Value::Object(obj) => { Ok(Self::#ident { #( #var_fields )* }) } other => return Err(wezterm_dynamic::Error::NoConversion { source_type: other.variant_name().to_string(), dest_type: "Object", }), } } ) } Fields::Unnamed(fields) => { if fields.unnamed.len() == 1 { let ty = fields.unnamed.iter().map(|f| &f.ty).next().unwrap(); quote!( #literal => { Ok(Self::#ident(<#ty>::from_dynamic(value, options)?)) } ) } else { let var_fields = fields .unnamed .iter() .enumerate() .map(|(idx, f)| { let ty = &f.ty; quote!( <#ty>::from_dynamic( arr.get(#idx) .ok_or_else(|| wezterm_dynamic::Error::Message( format!("missing idx {} of enum struct {}", #idx, #literal)))?, options )?, ) }) .collect::>(); quote!( #literal => { match value { Value::Array(arr) => { Ok(Self::#ident ( #( #var_fields )* )) } other => return Err(wezterm_dynamic::Error::NoConversion { source_type: other.variant_name().to_string(), dest_type: "Array", }), } } ) } } } }).collect::>(); quote!( match value { Value::String(s) => { match s.as_str() { #( #units )* _ => Err(wezterm_dynamic::Error::InvalidVariantForType { variant_name: s.clone(), type_name: #literal, possible: #ident::variants(), }) } } Value::Object(place) => { if place.len() == 1 { let (name, value) : (&Value, &Value) = place.iter().next().unwrap(); match name { Value::String(name) => { match name.as_str() { #( #variants )* _ => Err(wezterm_dynamic::Error::InvalidVariantForType { variant_name: name.to_string(), type_name: #literal, possible: #ident::variants(), }) } } _ => Err(wezterm_dynamic::Error::InvalidVariantForType { variant_name: name.variant_name().to_string(), type_name: #literal, possible: #ident::variants(), }) } } else { Err(wezterm_dynamic::Error::IncorrectNumberOfEnumKeys { type_name: #literal, num_keys: place.len(), }) } } other => Err(wezterm_dynamic::Error::NoConversion { source_type: other.variant_name().to_string(), dest_type: #literal }), } ) } }; let tokens = quote! { #[allow(non_upper_case_globals)] const #dummy: () = { impl wezterm_dynamic::FromDynamic for #ident { fn from_dynamic(value: &wezterm_dynamic::Value, options: wezterm_dynamic::FromDynamicOptions) -> std::result::Result { use wezterm_dynamic::{Value, BorrowedKey, ObjectKeyTrait}; #from_dynamic } } impl #ident { fn variants() -> &'static [&'static str] { &[ #( #variant_names, )* ] } } }; }; if info.debug { eprintln!("{}", tokens); } Ok(tokens) } wezterm-dynamic-derive-0.1.0/src/lib.rs000064400000000000000000000011720072674642500161510ustar 00000000000000use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; mod attr; mod bound; mod fromdynamic; mod todynamic; #[proc_macro_derive(ToDynamic, attributes(dynamic))] pub fn derive_todynamic(input: TokenStream) -> TokenStream { todynamic::derive(parse_macro_input!(input as DeriveInput)) .unwrap_or_else(|err| err.to_compile_error()) .into() } #[proc_macro_derive(FromDynamic, attributes(dynamic))] pub fn derive_fromdynamic(input: TokenStream) -> TokenStream { fromdynamic::derive(parse_macro_input!(input as DeriveInput)) .unwrap_or_else(|err| err.to_compile_error()) .into() } wezterm-dynamic-derive-0.1.0/src/todynamic.rs000064400000000000000000000205760072674642500174030ustar 00000000000000use crate::{attr, bound}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, FieldsNamed, Ident, Result, }; pub fn derive(input: DeriveInput) -> Result { match &input.data { Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => derive_struct(&input, fields), Data::Struct(_) => Err(Error::new( Span::call_site(), "currently only structs with named fields are supported", )), Data::Enum(enumeration) => derive_enum(&input, enumeration), Data::Union(_) => Err(Error::new( Span::call_site(), "currently only structs and enums are supported by this derive", )), } } fn derive_struct(input: &DeriveInput, fields: &FieldsNamed) -> Result { let ident = &input.ident; let info = attr::container_info(&input.attrs)?; let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl(); let dummy = Ident::new( &format!("_IMPL_PLACEDYNAMIC_FOR_{}", ident), Span::call_site(), ); let placements = fields .named .iter() .map(attr::field_info) .collect::>>()?; let placements = placements .into_iter() .map(|f| f.to_dynamic()) .collect::>(); let bound = parse_quote!(wezterm_dynamic::PlaceDynamic); let bounded_where_clause = bound::where_clause_with_bound(&input.generics, bound); let tokens = match info.into { Some(into) => { quote!( #[allow(non_upper_case_globals)] const #dummy: () = { impl #impl_generics wezterm_dynamic::ToDynamic for #ident #ty_generics #bounded_where_clause { fn to_dynamic(&self) -> wezterm_dynamic::Value { let target: #into = self.into(); target.to_dynamic() } } }; ) } None => { quote!( #[allow(non_upper_case_globals)] const #dummy: () = { impl #impl_generics wezterm_dynamic::PlaceDynamic for #ident #ty_generics #bounded_where_clause { fn place_dynamic(&self, place: &mut wezterm_dynamic::Object) { #( #placements )* } } impl #impl_generics wezterm_dynamic::ToDynamic for #ident #ty_generics #bounded_where_clause { fn to_dynamic(&self) -> wezterm_dynamic::Value { use wezterm_dynamic::PlaceDynamic; let mut object = wezterm_dynamic::Object::default(); self.place_dynamic(&mut object); wezterm_dynamic::Value::Object(object) } } }; ) } }; if info.debug { eprintln!("{}", tokens); } Ok(tokens) } fn derive_enum(input: &DeriveInput, enumeration: &DataEnum) -> Result { if input.generics.lt_token.is_some() || input.generics.where_clause.is_some() { return Err(Error::new( Span::call_site(), "Enums with generics are not supported", )); } let ident = &input.ident; let dummy = Ident::new(&format!("_IMPL_TODYNAMIC_FOR_{}", ident), Span::call_site()); let info = attr::container_info(&input.attrs)?; let tokens = match info.into { Some(into) => { quote! { #[allow(non_upper_case_globals)] const #dummy: () = { impl wezterm_dynamic::ToDynamic for #ident { fn to_dynamic(&self) -> wezterm_dynamic::Value { let target : #into = self.into(); target.to_dynamic() } } }; } } None => { let variants = enumeration.variants .iter() .map(|variant| { let ident = &variant.ident; let literal = ident.to_string(); match &variant.fields { Fields::Unit => Ok(quote!( Self::#ident => Value::String(#literal.to_string()), )), Fields::Named(fields) => { let var_fields = fields .named .iter() .map(|f| f.ident.as_ref().unwrap()) .collect::>(); let placements = fields .named .iter() .map(|f| { let ident = f.ident.as_ref().unwrap(); let name = ident.to_string(); quote!( place.insert(#name.to_dynamic(), #ident.to_dynamic()); ) }) .collect::>(); Ok(quote!( Self::#ident { #( #var_fields, )* } => { let mut place = wezterm_dynamic::Object::default(); #( #placements )* let mut obj = wezterm_dynamic::Object::default(); obj.insert(#literal.to_dynamic(), Value::Object(place)); Value::Object(obj) } )) } Fields::Unnamed(fields) => { let var_fields = fields .unnamed .iter() .enumerate() .map(|(idx, _f)| Ident::new(&format!("f{}", idx), Span::call_site())) .collect::>(); let hint = var_fields.len(); if hint == 1 { Ok(quote!( Self::#ident(f) => { let mut obj = wezterm_dynamic::Object::default(); obj.insert(#literal.to_dynamic(), f.to_dynamic()); Value::Object(obj) } )) } else { let placements = fields .unnamed .iter() .zip(var_fields.iter()) .map(|(_f, ident)| { quote!( place.push(#ident.to_dynamic()); ) }) .collect::>(); Ok(quote!( Self::#ident ( #( #var_fields, )* ) => { let mut place = Vec::with_capacity(#hint); #( #placements )* let mut obj = wezterm_dynamic::Object::default(); obj.insert(#literal.to_dynamic(), Value::Array(place.into())); Value::Object(obj) } )) } } } }) .collect::>>()?; quote! { #[allow(non_upper_case_globals)] const #dummy: () = { impl wezterm_dynamic::ToDynamic for #ident { fn to_dynamic(&self) -> wezterm_dynamic::Value { use wezterm_dynamic::Value; match self { #( #variants )* } } } }; } } }; if info.debug { eprintln!("{}", tokens); } Ok(tokens) }