mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 04:56:12 +03:00
add wezterm-dynamic crate
This commit is contained in:
parent
9b5c887874
commit
24c6830345
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -4699,6 +4699,25 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wezterm-dynamic"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"maplit",
|
||||
"ordered-float",
|
||||
"thiserror",
|
||||
"wezterm-dynamic-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wezterm-dynamic-derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wezterm-font"
|
||||
version = "0.1.0"
|
||||
|
@ -4,6 +4,7 @@ members = [
|
||||
"bidi/generate",
|
||||
"strip-ansi-escapes",
|
||||
"wezterm",
|
||||
"wezterm-dynamic",
|
||||
"wezterm-gui",
|
||||
"wezterm-mux-server",
|
||||
"wezterm-ssh"
|
||||
|
14
wezterm-dynamic/Cargo.toml
Normal file
14
wezterm-dynamic/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "wezterm-dynamic"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
wezterm-dynamic-derive = { version="0.1", path="derive" }
|
||||
ordered-float = "3.0"
|
||||
thiserror = "1.0"
|
||||
strsim = "0.10"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
maplit = "1.0"
|
12
wezterm-dynamic/derive/Cargo.toml
Normal file
12
wezterm-dynamic/derive/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "wezterm-dynamic-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0.2"
|
||||
syn = "1.0"
|
315
wezterm-dynamic/derive/src/attr.rs
Normal file
315
wezterm-dynamic/derive/src/attr.rs
Normal file
@ -0,0 +1,315 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Attribute, Error, Field, Lit, Meta, NestedMeta, Path, Result};
|
||||
|
||||
pub struct ContainerInfo {
|
||||
pub into: Option<Path>,
|
||||
pub try_from: Option<Path>,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
pub fn container_info(attrs: &[Attribute]) -> Result<ContainerInfo> {
|
||||
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<Path>,
|
||||
pub try_from: Option<Path>,
|
||||
}
|
||||
|
||||
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;
|
||||
if self.skip {
|
||||
quote!()
|
||||
} else if self.flatten {
|
||||
quote!(
|
||||
#ident:
|
||||
<#ty>::from_dynamic(value, options)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?,
|
||||
)
|
||||
} 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;
|
||||
let target = <#try_from>::from_dynamic(v, options)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?;
|
||||
<#ty>::try_from(target)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?
|
||||
}
|
||||
None => {
|
||||
<#ty>::default()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
DefValue::Path(default) => {
|
||||
quote!(
|
||||
#ident: match obj.get_by_str(&#name) {
|
||||
Some(v) => {
|
||||
use std::convert::TryFrom;
|
||||
let target = <#try_from>::from_dynamic(v, options)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?;
|
||||
<#ty>::try_from(target)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?
|
||||
}
|
||||
None => {
|
||||
#default()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
DefValue::None => {
|
||||
quote!(
|
||||
#ident: {
|
||||
use std::convert::TryFrom;
|
||||
let target = <#try_from>::from_dynamic(obj.get_by_str(#name).unwrap_or(&Value::Null), options)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?;
|
||||
<#ty>::try_from(target)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match &self.allow_default {
|
||||
DefValue::Default => {
|
||||
quote!(
|
||||
#ident: match obj.get_by_str(#name) {
|
||||
Some(v) => {
|
||||
<#ty>::from_dynamic(v, options)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?
|
||||
}
|
||||
None => {
|
||||
<#ty>::default()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
DefValue::Path(default) => {
|
||||
quote!(
|
||||
#ident: match obj.get_by_str(#name) {
|
||||
Some(v) => {
|
||||
<#ty>::from_dynamic(v, options)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?
|
||||
}
|
||||
None => {
|
||||
#default()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
DefValue::None => {
|
||||
quote!(
|
||||
#ident:
|
||||
<#ty>::from_dynamic(
|
||||
obj.get_by_str(#name).unwrap_or(&Value::Null),
|
||||
options
|
||||
)
|
||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #struct_name,
|
||||
field_name: #name,
|
||||
error: source.to_string(),
|
||||
})?,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field_info(field: &Field) -> Result<FieldInfo> {
|
||||
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 into = 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("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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
16
wezterm-dynamic/derive/src/bound.rs
Normal file
16
wezterm-dynamic/derive/src/bound.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use 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::<WherePredicate, _>(|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()
|
||||
}
|
359
wezterm-dynamic/derive/src/fromdynamic.rs
Normal file
359
wezterm-dynamic/derive/src/fromdynamic.rs
Normal file
@ -0,0 +1,359 @@
|
||||
use 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<TokenStream> {
|
||||
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<TokenStream> {
|
||||
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::<Result<Vec<_>>>()?;
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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::<Vec<_>>();
|
||||
|
||||
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<Self, wezterm_dynamic::Error> {
|
||||
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<TokenStream> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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 ident = f.ident.as_ref().unwrap();
|
||||
let name = ident.to_string();
|
||||
let ty = &f.ty;
|
||||
quote!(
|
||||
#ident: <#ty>::from_dynamic(
|
||||
obj.get_by_str(#name)
|
||||
.ok_or_else(|| wezterm_dynamic::Error::ErrorInField {
|
||||
type_name: #literal,
|
||||
field_name: #name,
|
||||
error: "missing field".to_string(),
|
||||
})?,
|
||||
options
|
||||
)?,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<Self, wezterm_dynamic::Error> {
|
||||
use wezterm_dynamic::{Value, BorrowedKey, ObjectKeyTrait};
|
||||
#from_dynamic
|
||||
}
|
||||
}
|
||||
|
||||
impl #ident {
|
||||
fn variants() -> &'static [&'static str] {
|
||||
&[
|
||||
#( #variant_names, )*
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if info.debug {
|
||||
eprintln!("{}", tokens);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
21
wezterm-dynamic/derive/src/lib.rs
Normal file
21
wezterm-dynamic/derive/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use 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()
|
||||
}
|
231
wezterm-dynamic/derive/src/todynamic.rs
Normal file
231
wezterm-dynamic/derive/src/todynamic.rs
Normal file
@ -0,0 +1,231 @@
|
||||
use 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<TokenStream> {
|
||||
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<TokenStream> {
|
||||
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::<Result<Vec<_>>>()?;
|
||||
let placements = placements
|
||||
.into_iter()
|
||||
.map(|f| f.to_dynamic())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<TokenStream> {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<Result<Vec<_>>>()?;
|
||||
|
||||
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)
|
||||
}
|
98
wezterm-dynamic/src/array.rs
Normal file
98
wezterm-dynamic/src/array.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use crate::Value;
|
||||
use core::iter::FromIterator;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Array {
|
||||
inner: Vec<Value>,
|
||||
}
|
||||
|
||||
impl Ord for Array {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let self_ptr = self as *const Self;
|
||||
let other_ptr = other as *const Self;
|
||||
self_ptr.cmp(&other_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Array {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Value>> for Array {
|
||||
fn from(inner: Vec<Value>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Array {
|
||||
fn drop(&mut self) {
|
||||
self.inner.drain(..).for_each(crate::drop::safely);
|
||||
}
|
||||
}
|
||||
|
||||
fn take(array: Array) -> Vec<Value> {
|
||||
let array = core::mem::ManuallyDrop::new(array);
|
||||
unsafe { core::ptr::read(&array.inner) }
|
||||
}
|
||||
|
||||
impl Array {
|
||||
pub fn new() -> Self {
|
||||
Array { inner: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Array {
|
||||
type Target = Vec<Value>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Array {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Array {
|
||||
type Item = Value;
|
||||
type IntoIter = <Vec<Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
take(self).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Array {
|
||||
type Item = &'a Value;
|
||||
type IntoIter = <&'a Vec<Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Array {
|
||||
type Item = &'a mut Value;
|
||||
type IntoIter = <&'a mut Vec<Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Value> for Array {
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Value>,
|
||||
{
|
||||
Array {
|
||||
inner: Vec::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
30
wezterm-dynamic/src/drop.rs
Normal file
30
wezterm-dynamic/src/drop.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::Value;
|
||||
|
||||
/// Non-recursive drop implementation.
|
||||
/// This is taken from dtolnay's miniserde library
|
||||
/// and is reproduced here under the terms of its
|
||||
/// MIT license
|
||||
pub fn safely(value: Value) {
|
||||
match value {
|
||||
Value::Array(_) | Value::Object(_) => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
let mut stack = Vec::new();
|
||||
stack.push(value);
|
||||
while let Some(value) = stack.pop() {
|
||||
match value {
|
||||
Value::Array(vec) => {
|
||||
for child in vec {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
for (_, child) in map {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
181
wezterm-dynamic/src/error.rs
Normal file
181
wezterm-dynamic/src/error.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction};
|
||||
use crate::value::Value;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("`{}` is not a valid {} variant. {}", .variant_name, .type_name, Self::possible_matches(.variant_name, &.possible))]
|
||||
InvalidVariantForType {
|
||||
variant_name: String,
|
||||
type_name: &'static str,
|
||||
possible: &'static [&'static str],
|
||||
},
|
||||
#[error("`{}` is not a valid {} field. {}", .field_name, .type_name, Self::possible_matches(.field_name, &.possible))]
|
||||
UnknownFieldForStruct {
|
||||
field_name: String,
|
||||
type_name: &'static str,
|
||||
possible: &'static [&'static str],
|
||||
},
|
||||
#[error("{}", .0)]
|
||||
Message(String),
|
||||
#[error("Cannot coerce vec of size {} to array of size {}", .vec_size, .array_size)]
|
||||
ArraySizeMismatch { vec_size: usize, array_size: usize },
|
||||
#[error("Cannot convert `{}` to `{}`", .source_type, .dest_type)]
|
||||
NoConversion {
|
||||
source_type: String,
|
||||
dest_type: &'static str,
|
||||
},
|
||||
#[error("Expected char to be a string with a single character")]
|
||||
CharFromWrongSizedString,
|
||||
#[error("Expected a valid `{}` variant name as single key in object, but there are {} keys", .type_name, .num_keys)]
|
||||
IncorrectNumberOfEnumKeys {
|
||||
type_name: &'static str,
|
||||
num_keys: usize,
|
||||
},
|
||||
#[error("Error in {}::{}: {:#}", .type_name, .field_name, .error)]
|
||||
ErrorInField {
|
||||
type_name: &'static str,
|
||||
field_name: &'static str,
|
||||
error: String,
|
||||
},
|
||||
#[error("`{}` is not a valid type to use as a field name in `{}`", .key_type, .type_name)]
|
||||
InvalidFieldType {
|
||||
type_name: &'static str,
|
||||
key_type: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn compute_unknown_fields(
|
||||
type_name: &'static str,
|
||||
object: &crate::Object,
|
||||
possible: &'static [&'static str],
|
||||
) -> Vec<Self> {
|
||||
let mut errors = vec![];
|
||||
|
||||
for key in object.keys() {
|
||||
match key {
|
||||
Value::String(s) => {
|
||||
if !possible.contains(&s.as_str()) {
|
||||
errors.push(Self::UnknownFieldForStruct {
|
||||
field_name: s.to_string(),
|
||||
type_name,
|
||||
possible: possible.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
other => {
|
||||
errors.push(Self::InvalidFieldType {
|
||||
type_name,
|
||||
key_type: other.variant_name().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
pub fn raise_unknown_fields(
|
||||
options: FromDynamicOptions,
|
||||
type_name: &'static str,
|
||||
object: &crate::Object,
|
||||
possible: &'static [&'static str],
|
||||
) -> Result<(), Self> {
|
||||
if options.unknown_fields == UnknownFieldAction::Ignore {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let errors = Self::compute_unknown_fields(type_name, object, possible);
|
||||
if errors.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let show_warning = options.unknown_fields == UnknownFieldAction::Warn || errors.len() > 1;
|
||||
|
||||
if show_warning {
|
||||
for err in &errors {
|
||||
log::warn!("{:#}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if options.unknown_fields == UnknownFieldAction::Deny {
|
||||
for err in errors {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn possible_matches(used: &str, possible: &'static [&'static str]) -> String {
|
||||
// Produce similar field name list
|
||||
let mut candidates: Vec<(f64, &str)> = possible
|
||||
.iter()
|
||||
.map(|&name| (strsim::jaro_winkler(used, name), name))
|
||||
.filter(|(confidence, _)| *confidence > 0.8)
|
||||
.collect();
|
||||
candidates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
|
||||
let suggestions: Vec<&str> = candidates.into_iter().map(|(_, name)| name).collect();
|
||||
|
||||
// Filter the suggestions out of the allowed field names
|
||||
// and sort what remains.
|
||||
let mut fields: Vec<&str> = possible
|
||||
.iter()
|
||||
.filter(|&name| !suggestions.iter().any(|candidate| candidate == name))
|
||||
.copied()
|
||||
.collect();
|
||||
fields.sort_unstable();
|
||||
|
||||
let mut message = String::new();
|
||||
|
||||
match suggestions.len() {
|
||||
0 => {}
|
||||
1 => message.push_str(&format!("Did you mean `{}`?", suggestions[0])),
|
||||
_ => {
|
||||
message.push_str("Did you mean one of ");
|
||||
for (idx, candidate) in suggestions.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
message.push_str(", ");
|
||||
}
|
||||
message.push('`');
|
||||
message.push_str(candidate);
|
||||
message.push('`');
|
||||
}
|
||||
message.push('?');
|
||||
}
|
||||
}
|
||||
if !fields.is_empty() {
|
||||
if suggestions.is_empty() {
|
||||
message.push_str("Possible items are ");
|
||||
} else {
|
||||
message.push_str(" Other possible items are ");
|
||||
}
|
||||
let limit = 5;
|
||||
for (idx, candidate) in fields.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
message.push_str(", ");
|
||||
}
|
||||
message.push('`');
|
||||
message.push_str(candidate);
|
||||
message.push('`');
|
||||
|
||||
if idx > limit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if fields.len() > limit {
|
||||
message.push_str(&format!(" and {} others", fields.len() - limit));
|
||||
}
|
||||
message.push('.');
|
||||
}
|
||||
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Error {
|
||||
Error::Message(s)
|
||||
}
|
||||
}
|
259
wezterm-dynamic/src/fromdynamic.rs
Normal file
259
wezterm-dynamic/src/fromdynamic.rs
Normal file
@ -0,0 +1,259 @@
|
||||
use crate::error::Error;
|
||||
use crate::value::Value;
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum UnknownFieldAction {
|
||||
Ignore,
|
||||
Warn,
|
||||
Deny,
|
||||
}
|
||||
|
||||
impl Default for UnknownFieldAction {
|
||||
fn default() -> UnknownFieldAction {
|
||||
UnknownFieldAction::Warn
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct FromDynamicOptions {
|
||||
pub unknown_fields: UnknownFieldAction,
|
||||
}
|
||||
|
||||
impl FromDynamicOptions {
|
||||
pub fn flatten(self) -> Self {
|
||||
Self {
|
||||
unknown_fields: UnknownFieldAction::Ignore,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromDynamic {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl FromDynamic for Value {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
Ok(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for ordered_float::NotNan<f64> {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
let f = f64::from_dynamic(value, options)?;
|
||||
Ok(ordered_float::NotNan::new(f).map_err(|e| Error::Message(e.to_string()))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for std::time::Duration {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
let f = f64::from_dynamic(value, options)?;
|
||||
Ok(std::time::Duration::from_secs_f64(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromDynamic> FromDynamic for Box<T> {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
let value = T::from_dynamic(value, options)?;
|
||||
Ok(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromDynamic> FromDynamic for std::sync::Arc<T> {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
let value = T::from_dynamic(value, options)?;
|
||||
Ok(std::sync::Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromDynamic> FromDynamic for Option<T> {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::Null => Ok(None),
|
||||
value => Ok(Some(T::from_dynamic(value, options)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromDynamic, const N: usize> FromDynamic for [T; N] {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::Array(arr) => {
|
||||
let v = arr
|
||||
.iter()
|
||||
.map(|v| T::from_dynamic(v, options))
|
||||
.collect::<Result<Vec<T>, Error>>()?;
|
||||
v.try_into().map_err(|v: Vec<T>| Error::ArraySizeMismatch {
|
||||
vec_size: v.len(),
|
||||
array_size: N,
|
||||
})
|
||||
}
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "array",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: FromDynamic + Eq + Hash, T: FromDynamic> FromDynamic for HashMap<K, T> {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::Object(obj) => {
|
||||
let mut map = HashMap::with_capacity(obj.len());
|
||||
for (k, v) in obj.iter() {
|
||||
map.insert(K::from_dynamic(k, options)?, T::from_dynamic(v, options)?);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "HashMap",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromDynamic> FromDynamic for Vec<T> {
|
||||
fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::Array(arr) => Ok(arr
|
||||
.iter()
|
||||
.map(|v| T::from_dynamic(v, options))
|
||||
.collect::<Result<Vec<T>, Error>>()?),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "Vec",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for () {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::Null => Ok(()),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "()",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for bool {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::Bool(b) => Ok(*b),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "bool",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for std::path::PathBuf {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::String(s) => Ok(s.into()),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "PathBuf",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for char {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::String(s) => {
|
||||
let mut iter = s.chars();
|
||||
let c = iter.next().ok_or_else(|| Error::CharFromWrongSizedString)?;
|
||||
if iter.next().is_some() {
|
||||
Err(Error::CharFromWrongSizedString)
|
||||
} else {
|
||||
Ok(c)
|
||||
}
|
||||
}
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "char",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for String {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::String(s) => Ok(s.to_string()),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "String",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! int {
|
||||
($($ty:ty),* $(,)?) => {
|
||||
$(
|
||||
impl FromDynamic for $ty {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::I64(n) => match (*n).try_into() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(err) => Err(Error::Message(err.to_string())),
|
||||
},
|
||||
Value::U64(n) => match (*n).try_into() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(err) => Err(Error::Message(err.to_string())),
|
||||
},
|
||||
other => Err(Error::NoConversion{
|
||||
source_type:other.variant_name().to_string(),
|
||||
dest_type: stringify!($ty),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
|
||||
|
||||
impl FromDynamic for f32 {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::F64(OrderedFloat(n)) => Ok((*n) as f32),
|
||||
Value::I64(n) => Ok((*n) as f32),
|
||||
Value::U64(n) => Ok((*n) as f32),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "f32",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDynamic for f64 {
|
||||
fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result<Self, Error> {
|
||||
match value {
|
||||
Value::F64(OrderedFloat(n)) => Ok(*n),
|
||||
Value::I64(n) => Ok((*n) as f64),
|
||||
Value::U64(n) => Ok((*n) as f64),
|
||||
other => Err(Error::NoConversion {
|
||||
source_type: other.variant_name().to_string(),
|
||||
dest_type: "f64",
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
18
wezterm-dynamic/src/lib.rs
Normal file
18
wezterm-dynamic/src/lib.rs
Normal file
@ -0,0 +1,18 @@
|
||||
//! Types for representing Rust types in a more dynamic form
|
||||
//! that is similar to JSON or Lua values.
|
||||
|
||||
mod array;
|
||||
mod drop;
|
||||
mod error;
|
||||
mod fromdynamic;
|
||||
mod object;
|
||||
mod todynamic;
|
||||
mod value;
|
||||
|
||||
pub use array::Array;
|
||||
pub use error::Error;
|
||||
pub use fromdynamic::{FromDynamic, FromDynamicOptions, UnknownFieldAction};
|
||||
pub use object::{BorrowedKey, Object, ObjectKeyTrait};
|
||||
pub use todynamic::{PlaceDynamic, ToDynamic};
|
||||
pub use value::Value;
|
||||
pub use wezterm_dynamic_derive::{FromDynamic, ToDynamic};
|
94
wezterm-dynamic/src/map.rs
Normal file
94
wezterm-dynamic/src/map.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use crate::Value;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Map {
|
||||
inner: BTreeMap<Value, Value>,
|
||||
}
|
||||
|
||||
impl Ord for Map {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let self_ptr = self as *const Self;
|
||||
let other_ptr = other as *const Self;
|
||||
self_ptr.cmp(&other_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Map {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Map {
|
||||
fn drop(&mut self) {
|
||||
for (_, child) in std::mem::replace(&mut self.inner, BTreeMap::new()) {
|
||||
crate::drop::safely(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<Value, Value>> for Map {
|
||||
fn from(inner: BTreeMap<Value, Value>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Map {
|
||||
type Target = BTreeMap<Value, Value>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Map {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
fn take(object: Map) -> BTreeMap<Value, Value> {
|
||||
let object = core::mem::ManuallyDrop::new(object);
|
||||
unsafe { core::ptr::read(&object.inner) }
|
||||
}
|
||||
|
||||
impl IntoIterator for Map {
|
||||
type Item = (Value, Value);
|
||||
type IntoIter = <BTreeMap<Value, Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
take(self).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Map {
|
||||
type Item = (&'a Value, &'a Value);
|
||||
type IntoIter = <&'a BTreeMap<Value, Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Map {
|
||||
type Item = (&'a Value, &'a mut Value);
|
||||
type IntoIter = <&'a mut BTreeMap<Value, Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Value, Value)> for Map {
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (Value, Value)>,
|
||||
{
|
||||
Map {
|
||||
inner: BTreeMap::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
163
wezterm-dynamic/src/object.rs
Normal file
163
wezterm-dynamic/src/object.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use crate::Value;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// We'd like to avoid allocating when resolving struct fields,
|
||||
/// so this is the borrowed version of Value.
|
||||
/// It's a bit involved to make this work; more details can be
|
||||
/// found in the excellent guide here:
|
||||
/// <https://github.com/sunshowers/borrow-complex-key-example/blob/master/src/lib.rs>
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)]
|
||||
pub enum BorrowedKey<'a> {
|
||||
Value(&'a Value),
|
||||
Str(&'a str),
|
||||
}
|
||||
|
||||
pub trait ObjectKeyTrait {
|
||||
fn key<'k>(&'k self) -> BorrowedKey<'k>;
|
||||
}
|
||||
|
||||
impl ObjectKeyTrait for Value {
|
||||
fn key<'k>(&'k self) -> BorrowedKey<'k> {
|
||||
match self {
|
||||
Value::String(s) => BorrowedKey::Str(s.as_str()),
|
||||
v => BorrowedKey::Value(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ObjectKeyTrait for BorrowedKey<'a> {
|
||||
fn key<'k>(&'k self) -> BorrowedKey<'k> {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::borrow::Borrow<dyn ObjectKeyTrait + 'a> for Value {
|
||||
fn borrow(&self) -> &(dyn ObjectKeyTrait + 'a) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for (dyn ObjectKeyTrait + 'a) {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key().eq(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eq for (dyn ObjectKeyTrait + 'a) {}
|
||||
|
||||
impl<'a> PartialOrd for (dyn ObjectKeyTrait + 'a) {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.key().partial_cmp(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Ord for (dyn ObjectKeyTrait + 'a) {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.key().cmp(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::hash::Hash for (dyn ObjectKeyTrait + 'a) {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Object {
|
||||
inner: BTreeMap<Value, Value>,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn get_by_str(&self, field_name: &str) -> Option<&Value> {
|
||||
self.inner
|
||||
.get(&BorrowedKey::Str(field_name) as &dyn ObjectKeyTrait)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Object {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let self_ptr = self as *const Self;
|
||||
let other_ptr = other as *const Self;
|
||||
self_ptr.cmp(&other_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Object {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Object {
|
||||
fn drop(&mut self) {
|
||||
for (_, child) in std::mem::replace(&mut self.inner, BTreeMap::new()) {
|
||||
crate::drop::safely(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<Value, Value>> for Object {
|
||||
fn from(inner: BTreeMap<Value, Value>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Object {
|
||||
type Target = BTreeMap<Value, Value>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Object {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
fn take(object: Object) -> BTreeMap<Value, Value> {
|
||||
let object = core::mem::ManuallyDrop::new(object);
|
||||
unsafe { core::ptr::read(&object.inner) }
|
||||
}
|
||||
|
||||
impl IntoIterator for Object {
|
||||
type Item = (Value, Value);
|
||||
type IntoIter = <BTreeMap<Value, Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
take(self).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Object {
|
||||
type Item = (&'a Value, &'a Value);
|
||||
type IntoIter = <&'a BTreeMap<Value, Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Object {
|
||||
type Item = (&'a Value, &'a mut Value);
|
||||
type IntoIter = <&'a mut BTreeMap<Value, Value> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Value, Value)> for Object {
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (Value, Value)>,
|
||||
{
|
||||
Object {
|
||||
inner: BTreeMap::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
192
wezterm-dynamic/src/todynamic.rs
Normal file
192
wezterm-dynamic/src/todynamic.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use crate::object::Object;
|
||||
use crate::value::Value;
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
pub trait ToDynamic {
|
||||
fn to_dynamic(&self) -> Value;
|
||||
}
|
||||
|
||||
pub trait PlaceDynamic {
|
||||
fn place_dynamic(&self, place: &mut Object);
|
||||
}
|
||||
|
||||
impl ToDynamic for Value {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for ordered_float::NotNan<f64> {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::F64(OrderedFloat::from(**self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for std::time::Duration {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::F64(OrderedFloat(self.as_secs_f64()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: ToDynamic + ToString + 'static, T: ToDynamic> ToDynamic for HashMap<K, T> {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::Object(
|
||||
self.iter()
|
||||
.map(|(k, v)| (k.to_dynamic(), v.to_dynamic()))
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToDynamic> ToDynamic for std::sync::Arc<T> {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
self.as_ref().to_dynamic()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToDynamic> ToDynamic for Box<T> {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
self.as_ref().to_dynamic()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToDynamic> ToDynamic for Option<T> {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
match self {
|
||||
None => Value::Null,
|
||||
Some(t) => t.to_dynamic(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToDynamic, const N: usize> ToDynamic for [T; N] {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::Array(
|
||||
self.iter()
|
||||
.map(T::to_dynamic)
|
||||
.collect::<Vec<Value>>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToDynamic> ToDynamic for Vec<T> {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::Array(
|
||||
self.iter()
|
||||
.map(T::to_dynamic)
|
||||
.collect::<Vec<Value>>()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for () {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for bool {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::Bool(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for str {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for std::path::PathBuf {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::String(self.to_string_lossy().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for String {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for char {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for isize {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::I64((*self).try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for i8 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::I64((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for i16 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::I64((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for i32 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::I64((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for i64 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::I64(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for usize {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::U64((*self).try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for u8 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::U64((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for u16 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::U64((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for u32 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::U64((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for u64 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::U64(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for f64 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::F64(OrderedFloat(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDynamic for f32 {
|
||||
fn to_dynamic(&self) -> Value {
|
||||
Value::F64(OrderedFloat((*self).into()))
|
||||
}
|
||||
}
|
36
wezterm-dynamic/src/value.rs
Normal file
36
wezterm-dynamic/src/value.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::array::Array;
|
||||
use crate::object::Object;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)]
|
||||
pub enum Value {
|
||||
Null,
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Array(Array),
|
||||
Object(Object),
|
||||
U64(u64),
|
||||
I64(i64),
|
||||
F64(OrderedFloat<f64>),
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn variant_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Null => "Null",
|
||||
Self::Bool(_) => "Bool",
|
||||
Self::String(_) => "String",
|
||||
Self::Array(_) => "Array",
|
||||
Self::Object(_) => "Object",
|
||||
Self::U64(_) => "U64",
|
||||
Self::I64(_) => "I64",
|
||||
Self::F64(_) => "F64",
|
||||
}
|
||||
}
|
||||
}
|
290
wezterm-dynamic/tests/fromdynamic.rs
Normal file
290
wezterm-dynamic/tests/fromdynamic.rs
Normal file
@ -0,0 +1,290 @@
|
||||
use maplit::btreemap;
|
||||
use ordered_float::OrderedFloat;
|
||||
use wezterm_dynamic::{FromDynamic, Object, ToDynamic, Value};
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
struct SimpleStruct {
|
||||
age: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_struct() {
|
||||
let s = SimpleStruct::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"age".to_dynamic() => Value::U64(42))
|
||||
.into(),
|
||||
),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(s, SimpleStruct { age: 42 });
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq, Default)]
|
||||
struct StructWithSkippedField {
|
||||
#[dynamic(skip)]
|
||||
admin: bool,
|
||||
age: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skipped_field() {
|
||||
let s = StructWithSkippedField::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"age".to_dynamic() => Value::U64(42))
|
||||
.into(),
|
||||
),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s,
|
||||
StructWithSkippedField {
|
||||
age: 42,
|
||||
admin: false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
struct StructWithFlattenedStruct {
|
||||
top: bool,
|
||||
#[dynamic(flatten)]
|
||||
simple: SimpleStruct,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flattened() {
|
||||
let s = StructWithFlattenedStruct::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"top".to_dynamic() =>Value::Bool(true),
|
||||
"age".to_dynamic() => Value::U64(42))
|
||||
.into(),
|
||||
),
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s,
|
||||
StructWithFlattenedStruct {
|
||||
top: true,
|
||||
simple: SimpleStruct { age: 42 },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
enum Units {
|
||||
A,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_variants() {
|
||||
assert_eq!(
|
||||
Units::A,
|
||||
Units::from_dynamic(&Value::String("A".to_string()), Default::default()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
enum Named {
|
||||
A { foo: bool, bar: bool },
|
||||
B { bar: bool },
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_variants() {
|
||||
assert_eq!(
|
||||
Named::A {
|
||||
foo: true,
|
||||
bar: false
|
||||
},
|
||||
Named::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"A".to_dynamic() => Value::Object(
|
||||
btreemap!(
|
||||
"foo".to_dynamic() => Value::Bool(true),
|
||||
"bar".to_dynamic() => Value::Bool(false),
|
||||
).into())
|
||||
)
|
||||
.into()
|
||||
),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Named::B { bar: true },
|
||||
Named::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"B".to_dynamic() => Value::Object(
|
||||
btreemap!(
|
||||
"bar".to_dynamic() => Value::Bool(true),
|
||||
).into())
|
||||
)
|
||||
.into()
|
||||
),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
enum UnNamed {
|
||||
A(f32, f32, f32, f32),
|
||||
Single(bool),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed_variants() {
|
||||
assert_eq!(
|
||||
UnNamed::A(0., 1., 2., 3.),
|
||||
UnNamed::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"A".to_dynamic() => Value::Array(vec![
|
||||
Value::F64(OrderedFloat(0.)),
|
||||
Value::F64(OrderedFloat(1.)),
|
||||
Value::F64(OrderedFloat(2.)),
|
||||
Value::F64(OrderedFloat(3.)),
|
||||
].into()),
|
||||
)
|
||||
.into()
|
||||
),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
UnNamed::Single(true),
|
||||
UnNamed::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap!(
|
||||
"Single".to_dynamic() => Value::Bool(true),
|
||||
)
|
||||
.into()
|
||||
),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
struct OptField {
|
||||
foo: Option<bool>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional() {
|
||||
assert_eq!(
|
||||
OptField { foo: None },
|
||||
OptField::from_dynamic(&Value::Object(Object::default()), Default::default()).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
OptField { foo: Some(true) },
|
||||
OptField::from_dynamic(
|
||||
&Value::Object(
|
||||
btreemap! {
|
||||
"foo".to_dynamic() => Value::Bool(true),
|
||||
}
|
||||
.into()
|
||||
),
|
||||
Default::default()
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
struct Defaults {
|
||||
#[dynamic(default)]
|
||||
s: String,
|
||||
#[dynamic(default = "woot_string")]
|
||||
w: String,
|
||||
}
|
||||
|
||||
fn woot_string() -> String {
|
||||
"woot".to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults() {
|
||||
assert_eq!(
|
||||
Defaults {
|
||||
s: "".to_string(),
|
||||
w: "woot".to_string()
|
||||
},
|
||||
Defaults::from_dynamic(&Value::Object(Object::default()), Default::default()).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
#[dynamic(try_from = "String")]
|
||||
struct StructInto {
|
||||
age: u8,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for StructInto {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<StructInto, Self::Error> {
|
||||
if let [label, value] = &s.split(':').collect::<Vec<_>>()[..] {
|
||||
if *label == "age" {
|
||||
return Ok(StructInto {
|
||||
age: value
|
||||
.parse()
|
||||
.map_err(|e: std::num::ParseIntError| e.to_string())?,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err("bad".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_into() {
|
||||
assert_eq!(
|
||||
StructInto { age: 42 },
|
||||
StructInto::from_dynamic(&Value::String("age:42".to_string()), Default::default()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(FromDynamic, Debug, PartialEq)]
|
||||
#[dynamic(try_from = "String")]
|
||||
enum EnumInto {
|
||||
Age(u8),
|
||||
}
|
||||
|
||||
impl TryFrom<String> for EnumInto {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<EnumInto, Self::Error> {
|
||||
if let [label, value] = &s.split(':').collect::<Vec<_>>()[..] {
|
||||
if *label == "age" {
|
||||
return Ok(EnumInto::Age(
|
||||
value
|
||||
.parse()
|
||||
.map_err(|e: std::num::ParseIntError| e.to_string())?,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err("bad".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_into() {
|
||||
assert_eq!(
|
||||
EnumInto::Age(42),
|
||||
EnumInto::from_dynamic(&Value::String("age:42".to_string()), Default::default()).unwrap()
|
||||
);
|
||||
}
|
221
wezterm-dynamic/tests/todynamic.rs
Normal file
221
wezterm-dynamic/tests/todynamic.rs
Normal file
@ -0,0 +1,221 @@
|
||||
use maplit::btreemap;
|
||||
use ordered_float::OrderedFloat;
|
||||
use wezterm_dynamic::{ToDynamic, Value};
|
||||
|
||||
#[test]
|
||||
fn intrinsics() {
|
||||
assert_eq!(23u8.to_dynamic(), Value::U64(23));
|
||||
assert_eq!(23i8.to_dynamic(), Value::I64(23));
|
||||
assert_eq!(23f32.to_dynamic(), Value::F64(OrderedFloat(23.)));
|
||||
assert_eq!("hello".to_dynamic(), Value::String("hello".to_string()));
|
||||
assert_eq!(false.to_dynamic(), Value::Bool(false));
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
struct SimpleStruct {
|
||||
age: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_struct() {
|
||||
assert_eq!(
|
||||
SimpleStruct { age: 42 }.to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"age".to_dynamic() => Value::U64(42))
|
||||
.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
struct SimpleStructWithRenamedField {
|
||||
#[dynamic(rename = "how_old")]
|
||||
age: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_struct_with_renamed_field() {
|
||||
assert_eq!(
|
||||
SimpleStructWithRenamedField { age: 42 }.to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"how_old".to_dynamic() => Value::U64(42))
|
||||
.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
struct StructWithSkippedField {
|
||||
#[dynamic(skip)]
|
||||
admin: bool,
|
||||
age: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skipped_field() {
|
||||
assert_eq!(
|
||||
StructWithSkippedField {
|
||||
admin: true,
|
||||
age: 42
|
||||
}
|
||||
.to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"age".to_dynamic() => Value::U64(42))
|
||||
.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
struct StructWithFlattenedStruct {
|
||||
top: bool,
|
||||
#[dynamic(flatten)]
|
||||
simple: SimpleStruct,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flattened() {
|
||||
assert_eq!(
|
||||
StructWithFlattenedStruct {
|
||||
top: true,
|
||||
simple: SimpleStruct { age: 42 }
|
||||
}
|
||||
.to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"top".to_dynamic() => Value::Bool(true),
|
||||
"age".to_dynamic() => Value::U64(42))
|
||||
.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
enum Units {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_variants() {
|
||||
assert_eq!(Units::A.to_dynamic(), Value::String("A".to_string()));
|
||||
assert_eq!(Units::B.to_dynamic(), Value::String("B".to_string()));
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
enum Named {
|
||||
A { foo: bool, bar: bool },
|
||||
B { bar: bool },
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_variants() {
|
||||
assert_eq!(
|
||||
Named::A {
|
||||
foo: true,
|
||||
bar: false
|
||||
}
|
||||
.to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"A".to_dynamic() => Value::Object(
|
||||
btreemap!(
|
||||
"foo".to_dynamic() => Value::Bool(true),
|
||||
"bar".to_dynamic() => Value::Bool(false),
|
||||
).into())
|
||||
)
|
||||
.into()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
Named::B { bar: true }.to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"B".to_dynamic() => Value::Object(
|
||||
btreemap!(
|
||||
"bar".to_dynamic() => Value::Bool(true),
|
||||
).into())
|
||||
)
|
||||
.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
enum UnNamed {
|
||||
A(f32, f32, f32, f32),
|
||||
Single(bool),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed_variants() {
|
||||
assert_eq!(
|
||||
UnNamed::A(0., 1., 2., 3.).to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"A".to_dynamic() => Value::Array(vec![
|
||||
Value::F64(OrderedFloat(0.)),
|
||||
Value::F64(OrderedFloat(1.)),
|
||||
Value::F64(OrderedFloat(2.)),
|
||||
Value::F64(OrderedFloat(3.)),
|
||||
].into()),
|
||||
)
|
||||
.into()
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
UnNamed::Single(true).to_dynamic(),
|
||||
Value::Object(
|
||||
btreemap!(
|
||||
"Single".to_dynamic() => Value::Bool(true),
|
||||
)
|
||||
.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
#[dynamic(into = "String")]
|
||||
struct StructInto {
|
||||
age: u8,
|
||||
}
|
||||
|
||||
impl Into<String> for &StructInto {
|
||||
fn into(self) -> String {
|
||||
format!("age:{}", self.age)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_into() {
|
||||
assert_eq!(
|
||||
StructInto { age: 42 }.to_dynamic(),
|
||||
Value::String("age:42".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(ToDynamic, Debug, PartialEq)]
|
||||
#[dynamic(into = "String")]
|
||||
enum EnumInto {
|
||||
Age(u8),
|
||||
}
|
||||
|
||||
impl Into<String> for &EnumInto {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
EnumInto::Age(age) => format!("age:{}", age),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_into() {
|
||||
assert_eq!(
|
||||
EnumInto::Age(42).to_dynamic(),
|
||||
Value::String("age:42".to_string())
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user