wezterm-dynamic-0.2.0/.cargo_vcs_info.json0000644000000001550000000000100141530ustar { "git": { "sha1": "b956a6ac304c707fbab6534effbaa7d5dd154114" }, "path_in_vcs": "wezterm-dynamic" }wezterm-dynamic-0.2.0/Cargo.toml0000644000000016600000000000100121530ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "wezterm-dynamic" version = "0.2.0" description = "config serialization for wezterm via dynamic json-like data values" license = "MIT" repository = "https://github.com/wez/wezterm" [dependencies.log] version = "0.4" [dependencies.ordered-float] version = "4.1" [dependencies.strsim] version = "0.10" [dependencies.thiserror] version = "1.0" [dependencies.wezterm-dynamic-derive] version = "0.1" [dev-dependencies.maplit] version = "1.0" wezterm-dynamic-0.2.0/Cargo.toml.orig000064400000000000000000000006101046102023000156260ustar 00000000000000[package] name = "wezterm-dynamic" version = "0.2.0" edition = "2021" repository = "https://github.com/wez/wezterm" description = "config serialization for wezterm via dynamic json-like data values" license = "MIT" [dependencies] wezterm-dynamic-derive = { version="0.1", path="derive" } ordered-float = "4.1" thiserror = "1.0" strsim = "0.10" log = "0.4" [dev-dependencies] maplit = "1.0" wezterm-dynamic-0.2.0/LICENSE.md000064400000000000000000000020541046102023000143470ustar 00000000000000MIT License Copyright (c) 2018 Wez Furlong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wezterm-dynamic-0.2.0/src/array.rs000064400000000000000000000037621046102023000152250ustar 00000000000000use 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, } 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 { Some(self.cmp(other)) } } impl From> for Array { fn from(inner: Vec) -> Self { Self { inner } } } impl Drop for Array { fn drop(&mut self) { self.inner.drain(..).for_each(crate::drop::safely); } } fn take(array: Array) -> Vec { 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; 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 = 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 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 as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl FromIterator for Array { fn from_iter(iter: I) -> Self where I: IntoIterator, { Array { inner: Vec::from_iter(iter), } } } wezterm-dynamic-0.2.0/src/drop.rs000064400000000000000000000013511046102023000150430ustar 00000000000000use 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); } } _ => {} } } } wezterm-dynamic-0.2.0/src/error.rs000064400000000000000000000262341046102023000152370ustar 00000000000000use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction}; use crate::object::Object; use crate::value::Value; use std::cell::RefCell; use std::rc::Rc; use thiserror::Error; pub trait WarningCollector { fn warn(&self, message: String); } thread_local! { static WARNING_COLLECTOR: RefCell>> = RefCell::new(None); } #[derive(Error, Debug)] #[non_exhaustive] 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 processing {}::{}: {:#}", .type_name, .field_name, .error)] ErrorInField { type_name: &'static str, field_name: &'static str, error: String, }, #[error("Error processing {} (types: {}) {:#}", .field_name.join("."), .type_name.join(", "), .error)] ErrorInNestedField { type_name: Vec<&'static str>, field_name: Vec<&'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, }, #[error("{}::{} is deprecated: {}", .type_name, .field_name, .reason)] DeprecatedField { type_name: &'static str, field_name: &'static str, reason: &'static str, }, } impl Error { /// Log a warning; if a warning collector is set for the current thread, /// use it, otherwise, log a regular warning message. pub fn warn(message: String) { WARNING_COLLECTOR.with(|collector| { let collector = collector.borrow(); if let Some(collector) = collector.as_ref() { collector.warn(message); } else { log::warn!("{message}"); } }); } pub fn capture_warnings T, T>(f: F) -> (T, Vec) { let warnings = Rc::new(RefCell::new(vec![])); struct Collector { warnings: Rc>>, } impl WarningCollector for Collector { fn warn(&self, message: String) { self.warnings.borrow_mut().push(message); } } Self::set_warning_collector(Collector { warnings: Rc::clone(&warnings), }); let result = f(); Self::clear_warning_collector(); let warnings = match Rc::try_unwrap(warnings) { Ok(warnings) => warnings.into_inner(), Err(warnings) => (*warnings).clone().into_inner(), }; (result, warnings) } /// Replace the warning collector for the current thread fn set_warning_collector(c: T) { WARNING_COLLECTOR.with(|collector| { collector.borrow_mut().replace(Box::new(c)); }); } /// Clear the warning collector for the current thread fn clear_warning_collector() { WARNING_COLLECTOR.with(|collector| { collector.borrow_mut().take(); }); } fn compute_unknown_fields( type_name: &'static str, object: &crate::Object, possible: &'static [&'static str], ) -> Vec { 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, }); } } other => { errors.push(Self::InvalidFieldType { type_name, key_type: other.variant_name().to_string(), }); } } } errors } pub fn raise_deprecated_fields( options: FromDynamicOptions, type_name: &'static str, field_name: &'static str, reason: &'static str, ) -> Result<(), Self> { if options.deprecated_fields == UnknownFieldAction::Ignore { return Ok(()); } let err = Self::DeprecatedField { type_name, field_name, reason, }; match options.deprecated_fields { UnknownFieldAction::Deny => Err(err), UnknownFieldAction::Warn => { Self::warn(format!("{:#}", err)); Ok(()) } UnknownFieldAction::Ignore => unreachable!(), } } 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 { Self::warn(format!("{:#}", 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() { let limit = 5; if fields.len() > limit { message.push_str( " There are too many alternatives to list here; consult the documentation!", ); } else { if suggestions.is_empty() { message.push_str("Possible alternatives are "); } else if suggestions.len() == 1 { message.push_str(" The other option is "); } else { message.push_str(" Other alternatives are "); } for (idx, candidate) in fields.iter().enumerate() { if idx > 0 { message.push_str(", "); } message.push('`'); message.push_str(candidate); message.push('`'); } } } message } pub fn field_context( self, type_name: &'static str, field_name: &'static str, obj: &Object, ) -> Self { let is_leaf = !matches!(self, Self::ErrorInField { .. }); fn add_obj_context(is_leaf: bool, obj: &Object, message: String) -> String { if is_leaf { // Show the object as context. // However, some objects, like the main config, are very large and // it isn't helpful to show that, so only include it when the context // is more reasonable. let obj_str = format!("{:#?}", obj); if obj_str.len() > 128 || obj_str.lines().count() > 10 { message } else { format!("{}.\n{}", message, obj_str) } } else { message } } match self { Self::NoConversion { source_type, .. } if source_type == "Null" => Self::ErrorInField { type_name, field_name, error: add_obj_context(is_leaf, obj, format!("missing field `{}`", field_name)), }, Self::ErrorInField { type_name: child_type, field_name: child_field, error, } => Self::ErrorInNestedField { type_name: vec![type_name, child_type], field_name: vec![field_name, child_field], error, }, Self::ErrorInNestedField { type_name: mut child_type, field_name: mut child_field, error, } => Self::ErrorInNestedField { type_name: { child_type.insert(0, type_name); child_type }, field_name: { child_field.insert(0, field_name); child_field }, error, }, _ => Self::ErrorInField { type_name, field_name, error: add_obj_context(is_leaf, obj, format!("{:#}", self)), }, } } } impl From for Error { fn from(s: String) -> Error { Error::Message(s) } } wezterm-dynamic-0.2.0/src/fromdynamic.rs000064400000000000000000000206611046102023000164140ustar 00000000000000use crate::error::Error; use crate::value::Value; use ordered_float::OrderedFloat; use std::collections::HashMap; use std::convert::TryInto; use std::hash::Hash; /// Specify how FromDynamic will treat unknown fields /// when converting from Value to a given target type #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] pub enum UnknownFieldAction { /// Don't check, don't warn, don't raise an error Ignore, /// Emit a log::warn log #[default] Warn, /// Return an Error Deny, } /// Specify various options for FromDynamic::from_dynamic #[derive(Copy, Clone, Debug, Default)] pub struct FromDynamicOptions { pub unknown_fields: UnknownFieldAction, pub deprecated_fields: UnknownFieldAction, } impl FromDynamicOptions { pub fn flatten(self) -> Self { Self { unknown_fields: UnknownFieldAction::Ignore, ..self } } } /// The FromDynamic trait allows a type to construct itself from a Value. /// This trait can be derived. pub trait FromDynamic { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result where Self: Sized; } impl FromDynamic for Value { fn from_dynamic(value: &Value, _options: FromDynamicOptions) -> Result { Ok(value.clone()) } } impl FromDynamic for ordered_float::NotNan { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { 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 { let f = f64::from_dynamic(value, options)?; Ok(std::time::Duration::from_secs_f64(f)) } } impl FromDynamic for Box { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { let value = T::from_dynamic(value, options)?; Ok(Box::new(value)) } } impl FromDynamic for std::sync::Arc { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { let value = T::from_dynamic(value, options)?; Ok(std::sync::Arc::new(value)) } } impl FromDynamic for Option { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { match value { Value::Null => Ok(None), value => Ok(Some(T::from_dynamic(value, options)?)), } } } impl FromDynamic for [T; N] { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { match value { Value::Array(arr) => { let v = arr .iter() .map(|v| T::from_dynamic(v, options)) .collect::, Error>>()?; v.try_into().map_err(|v: Vec| Error::ArraySizeMismatch { vec_size: v.len(), array_size: N, }) } other => Err(Error::NoConversion { source_type: other.variant_name().to_string(), dest_type: "array", }), } } } impl FromDynamic for HashMap { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { 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 FromDynamic for Vec { fn from_dynamic(value: &Value, options: FromDynamicOptions) -> Result { match value { Value::Array(arr) => Ok(arr .iter() .map(|v| T::from_dynamic(v, options)) .collect::, Error>>()?), // lua uses tables for everything; we can end up here if we got an empty // table and treated it as an object. Allow that to stand-in for an empty // array instead. Value::Object(obj) if obj.is_empty() => Ok(vec![]), 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 { 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 { 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 { 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 { match value { Value::String(s) => { let mut iter = s.chars(); let c = iter.next().ok_or(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 { 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 { 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 { 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 { 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", }), } } } wezterm-dynamic-0.2.0/src/lib.rs000064400000000000000000000007561046102023000146550ustar 00000000000000//! 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}; wezterm-dynamic-0.2.0/src/map.rs000064400000000000000000000041501046102023000146540ustar 00000000000000use 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, } 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 { 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> for Map { fn from(inner: BTreeMap) -> Self { Self { inner } } } impl Deref for Map { type Target = BTreeMap; 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 { let object = core::mem::ManuallyDrop::new(object); unsafe { core::ptr::read(&object.inner) } } impl IntoIterator for Map { type Item = (Value, Value); type IntoIter = 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 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 as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl FromIterator<(Value, Value)> for Map { fn from_iter(iter: I) -> Self where I: IntoIterator, { Map { inner: BTreeMap::from_iter(iter), } } } wezterm-dynamic-0.2.0/src/object.rs000064400000000000000000000100241046102023000153420ustar 00000000000000use 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: /// #[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 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 { 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(&self, state: &mut H) { self.key().hash(state) } } #[derive(Clone, Default, PartialEq, Eq, Hash)] pub struct Object { inner: BTreeMap, } impl Object { pub fn get_by_str(&self, field_name: &str) -> Option<&Value> { self.inner .get(&BorrowedKey::Str(field_name) as &dyn ObjectKeyTrait) } } impl std::fmt::Debug for Object { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { self.inner.fmt(fmt) } } 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 { Some(self.cmp(other)) } } impl Drop for Object { fn drop(&mut self) { for (_, child) in std::mem::take(&mut self.inner) { crate::drop::safely(child); } } } impl From> for Object { fn from(inner: BTreeMap) -> Self { Self { inner } } } impl Deref for Object { type Target = BTreeMap; 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 { let object = core::mem::ManuallyDrop::new(object); unsafe { core::ptr::read(&object.inner) } } impl IntoIterator for Object { type Item = (Value, Value); type IntoIter = 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 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 as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl FromIterator<(Value, Value)> for Object { fn from_iter(iter: I) -> Self where I: IntoIterator, { Object { inner: BTreeMap::from_iter(iter), } } } wezterm-dynamic-0.2.0/src/todynamic.rs000064400000000000000000000104011046102023000160620ustar 00000000000000use crate::object::Object; use crate::value::Value; use ordered_float::OrderedFloat; use std::collections::{BTreeMap, HashMap}; /// The ToDynamic trait allows a type to emit a representation of itself /// as the Value type. /// This trait can be derived. pub trait ToDynamic { fn to_dynamic(&self) -> Value; } /// The PlaceDynamic trait is used by derived implementations of FromDynamic /// to implement flattened conversions. /// Deriving FromDynamic for a struct will usually also derive /// PlaceDynamic for the same struct. /// You do not typically consume PlaceDynamic directly. pub trait PlaceDynamic { /// Convert from Self to Value, by storing directly into the /// target Object. fn place_dynamic(&self, place: &mut Object); } impl ToDynamic for Value { fn to_dynamic(&self) -> Value { self.clone() } } impl ToDynamic for ordered_float::NotNan { 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 ToDynamic for HashMap { fn to_dynamic(&self) -> Value { Value::Object( self.iter() .map(|(k, v)| (k.to_dynamic(), v.to_dynamic())) .collect::>() .into(), ) } } impl ToDynamic for std::sync::Arc { fn to_dynamic(&self) -> Value { self.as_ref().to_dynamic() } } impl ToDynamic for Box { fn to_dynamic(&self) -> Value { self.as_ref().to_dynamic() } } impl ToDynamic for Option { fn to_dynamic(&self) -> Value { match self { None => Value::Null, Some(t) => t.to_dynamic(), } } } impl ToDynamic for [T; N] { fn to_dynamic(&self) -> Value { Value::Array( self.iter() .map(T::to_dynamic) .collect::>() .into(), ) } } impl ToDynamic for Vec { fn to_dynamic(&self) -> Value { Value::Array( self.iter() .map(T::to_dynamic) .collect::>() .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())) } } wezterm-dynamic-0.2.0/src/value.rs000064400000000000000000000047021046102023000152160ustar 00000000000000use crate::array::Array; use crate::object::Object; use ordered_float::OrderedFloat; /// Represents values of various possible other types. /// Value is intended to be convertible to the same set /// of types as Lua and is a superset of the types possible /// in TOML and JSON. #[derive(Clone, PartialEq, Hash, Eq, Ord, PartialOrd)] pub enum Value { Null, Bool(bool), String(String), Array(Array), Object(Object), U64(u64), I64(i64), F64(OrderedFloat), } impl Default for Value { fn default() -> Self { Self::Null } } impl std::fmt::Debug for Value { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::String(s) => fmt.write_fmt(format_args!("{:?}", s)), Self::Null => fmt.write_str("nil"), Self::Bool(i) => i.fmt(fmt), Self::I64(i) => i.fmt(fmt), Self::U64(i) => i.fmt(fmt), Self::F64(i) => i.fmt(fmt), Self::Array(a) => a.fmt(fmt), Self::Object(o) => o.fmt(fmt), } } } 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", } } pub fn coerce_unsigned(&self) -> Option { match self { Self::U64(u) => Some(*u), Self::I64(i) => (*i).try_into().ok(), Self::F64(OrderedFloat(f)) if f.fract() == 0.0 && *f >= u64::MIN as f64 && *f <= u64::MAX as f64 => { Some(*f as u64) } _ => None, } } pub fn coerce_signed(&self) -> Option { match self { Self::I64(u) => Some(*u), Self::U64(i) => (*i).try_into().ok(), Self::F64(OrderedFloat(f)) if f.fract() == 0.0 && *f >= i64::MIN as f64 && *f <= i64::MAX as f64 => { Some(*f as i64) } _ => None, } } pub fn coerce_float(&self) -> Option { match self { Self::I64(u) => Some(*u as f64), Self::U64(i) => Some(*i as f64), Self::F64(OrderedFloat(f)) => Some(*f), _ => None, } } } wezterm-dynamic-0.2.0/tests/fromdynamic.rs000064400000000000000000000147561046102023000167770ustar 00000000000000use 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, } #[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 for StructInto { type Error = String; fn try_from(s: String) -> Result { if let [label, value] = &s.split(':').collect::>()[..] { 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 for EnumInto { type Error = String; fn try_from(s: String) -> Result { if let [label, value] = &s.split(':').collect::>()[..] { 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() ); } wezterm-dynamic-0.2.0/tests/todynamic.rs000064400000000000000000000112721046102023000164440ustar 00000000000000use 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 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 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()) ); }