1
1
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:
Wez Furlong 2022-05-11 08:19:00 -07:00
parent 9b5c887874
commit 24c6830345
20 changed files with 2570 additions and 0 deletions

19
Cargo.lock generated
View File

@ -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"

View File

@ -4,6 +4,7 @@ members = [
"bidi/generate",
"strip-ansi-escapes",
"wezterm",
"wezterm-dynamic",
"wezterm-gui",
"wezterm-mux-server",
"wezterm-ssh"

View 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"

View 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"

View 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,
})
}

View 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 = &param.ident;
parse_quote!(#param : #bound)
});
let mut generics = generics.clone();
generics
.make_where_clause()
.predicates
.extend(new_predicates);
generics.where_clause.unwrap()
}

View 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)
}

View 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()
}

View 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)
}

View 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),
}
}
}

View 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);
}
}
_ => {}
}
}
}

View 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)
}
}

View 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",
}),
}
}
}

View 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};

View 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),
}
}
}

View 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),
}
}
}

View 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()))
}
}

View 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",
}
}
}

View 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()
);
}

View 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())
);
}