wezterm-dynamic-derive-0.1.1/.cargo_vcs_info.json0000644000000001640000000000100154270ustar { "git": { "sha1": "4d3911b5e29b0a215bbfc94879de5f574b51e480" }, "path_in_vcs": "wezterm-dynamic/derive" }wezterm-dynamic-derive-0.1.1/Cargo.lock0000644000000021470000000000100134050ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "wezterm-dynamic-derive" version = "0.1.1" dependencies = [ "proc-macro2", "quote", "syn", ] wezterm-dynamic-derive-0.1.1/Cargo.toml0000644000000020170000000000100134240ustar # 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.1" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "config serialization for wezterm via dynamic json-like data values" readme = false license = "MIT" repository = "https://github.com/wezterm/wezterm" [lib] name = "wezterm_dynamic_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0.2" [dependencies.syn] version = "1.0" features = ["extra-traits"] wezterm-dynamic-derive-0.1.1/Cargo.toml.orig000064400000000000000000000005401046102023000171040ustar 00000000000000[package] name = "wezterm-dynamic-derive" version = "0.1.1" edition = "2021" repository = "https://github.com/wezterm/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 = {version="1.0", features=["extra-traits"]} wezterm-dynamic-derive-0.1.1/LICENSE.md000064400000000000000000000020541046102023000156230ustar 00000000000000MIT License Copyright (c) 2018 Wez Furlong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wezterm-dynamic-derive-0.1.1/src/attr.rs000064400000000000000000000325151046102023000163330ustar 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.1/src/bound.rs000064400000000000000000000010101046102023000164520ustar 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.1/src/fromdynamic.rs000064400000000000000000000310631046102023000176660ustar 00000000000000use crate::{attr, bound}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Error, Fields, FieldsNamed, 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 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! { 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 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! { 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 { pub fn variants() -> &'static [&'static str] { &[ #( #variant_names, )* ] } } }; if info.debug { eprintln!("{}", tokens); } Ok(tokens) } wezterm-dynamic-derive-0.1.1/src/lib.rs000064400000000000000000000011721046102023000161220ustar 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.1/src/todynamic.rs000064400000000000000000000172061046102023000173500ustar 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 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!( 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!( 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 info = attr::container_info(&input.attrs)?; let tokens = match info.into { Some(into) => { quote! { 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! { 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) }