From 989f83b05fe9f7eb843e73a404ff36830ad43d1f Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 22 Sep 2024 19:53:46 -0400 Subject: [PATCH] feat(Renderable) Add a new `Value` variant `Renderable` that supports `Display` and `Debug` values --- tests/tests/renderable.rs | 55 +++++++ valuable-serde/src/lib.rs | 11 ++ valuable/src/lib.rs | 3 + valuable/src/renderable.rs | 286 +++++++++++++++++++++++++++++++++++++ valuable/src/slice.rs | 14 ++ valuable/src/value.rs | 23 ++- 6 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 tests/tests/renderable.rs create mode 100644 valuable/src/renderable.rs diff --git a/tests/tests/renderable.rs b/tests/tests/renderable.rs new file mode 100644 index 0000000..43e28f9 --- /dev/null +++ b/tests/tests/renderable.rs @@ -0,0 +1,55 @@ +use valuable::{Renderable, Valuable}; + +#[derive(Debug)] +struct NotTuplable<'a> { + s: &'a str, + i: usize, +} + +impl<'a> core::fmt::Display for NotTuplable<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "The string is \"{}\", and the integer is {}.", + self.s, self.i + ) + } +} + +#[test] +fn test_renderable_struct_from_debug() { + let s_owned = "Hello, Valuable World".to_owned(); + let s = NotTuplable { s: &s_owned, i: 42 }; + + let r = Renderable::Debug(&s); + let v = r.as_value(); + + // Rendering should produce the debug output for the struct + assert_eq!(r.render_to_string(), format!("{s:?}")); + + // Writing the value itself as `Debug` should print the same debug output + // as the struct + assert_eq!(format!("{v:?}"), format!("{s:?}")); + assert_eq!(format!("{v:#?}"), format!("{s:#?}")); +} + +#[test] +fn test_renderable_struct_from_display() { + let s_owned = "Hello, Valuable World".to_owned(); + let s = NotTuplable { s: &s_owned, i: 42 }; + + let r = Renderable::Display(&s); + let v = r.as_value(); + + // Rendering should produce the display output for the struct + assert_eq!(r.render_to_string(), format!("{s}")); + + // Just to make sure, the display output should be different for the debug + // output + assert_ne!(r.render_to_string(), format!("{s:?}")); + + // Writing the value itself as `Debug` should print the same display output + // as the struct + assert_eq!(format!("{v:?}"), format!("{s}")); + assert_eq!(format!("{v:#?}"), format!("{s:#}")); +} diff --git a/valuable-serde/src/lib.rs b/valuable-serde/src/lib.rs index 6b53930..f1839d4 100644 --- a/valuable-serde/src/lib.rs +++ b/valuable-serde/src/lib.rs @@ -265,6 +265,17 @@ where Value::Path(p) => Serialize::serialize(p, serializer), #[cfg(feature = "std")] Value::Error(e) => SerializeError(e).serialize(serializer), + Value::Renderable(r) => { + #[cfg(feature = "alloc")] + { + serializer.serialize_str(&r.render_to_string()) + } + #[cfg(not(feature = "alloc"))] + { + // Can't serialize a renderable without allocating + Ok(S::Ok) + } + } v => unimplemented!("{:?}", v), } diff --git a/valuable/src/lib.rs b/valuable/src/lib.rs index 255ee5a..c16faba 100644 --- a/valuable/src/lib.rs +++ b/valuable/src/lib.rs @@ -135,6 +135,9 @@ pub use mappable::Mappable; mod named_values; pub use named_values::NamedValues; +mod renderable; +pub use renderable::Renderable; + mod slice; pub use slice::Slice; diff --git a/valuable/src/renderable.rs b/valuable/src/renderable.rs new file mode 100644 index 0000000..c835823 --- /dev/null +++ b/valuable/src/renderable.rs @@ -0,0 +1,286 @@ +use core::fmt::{self, Debug, Display, Formatter, Write}; + +use crate::{Slice, Valuable, Value}; + +/// A [`Valuable`] sub-type for using ordinarily non-`valuable` types by +/// rendering them to a string with [`Debug`] or [`Display`]. +/// +/// This is most useful when defining a [`Structable`] value that includes +/// fields of types where [`Valuable`] cannot be implemented like types +/// contained in external crates. +/// +/// ``` +/// use valuable::{Valuable, Value, Visit, Renderable}; +/// +/// #[derive(Debug)] +/// struct NotValuable { +/// foo: u32, +/// bar: u32, +/// } +/// +/// struct Render(String); +/// +/// impl Visit for Render { +/// fn visit_value(&mut self, value: Value<'_>) { +/// let Value::Renderable(v) = value else { return }; +/// self.0 = v.render_to_string(); +/// } +/// } +/// +/// let my_struct = NotValuable { +/// foo: 123, +/// bar: 456, +/// }; +/// +/// let mut renderer = Render(String::default()); +/// +/// // Render it plain +/// +/// valuable::visit(&Renderable::Debug(&my_struct), &mut renderer); +/// assert_eq!(renderer.0, "NotValuable { foo: 123, bar: 456 }"); +/// +/// // Or render it pretty +/// assert_eq!(Renderable::Debug(&my_struct).render_to_string_with_prettiness(true), +/// "NotValuable { +/// foo: 123, +/// bar: 456, +/// }"); +/// +/// ``` +#[derive(Clone, Copy)] +pub enum Renderable<'a> { + /// Renderable sub-type that is rendered via its [`Debug`] implementation + /// ``` + /// use valuable::{Valuable, Value, Visit, Renderable}; + /// + /// #[derive(Debug)] + /// struct NotValuable { + /// foo: u32, + /// bar: u32, + /// } + /// + /// struct Renderer(String); + /// + /// impl Visit for Renderer { + /// fn visit_value(&mut self, value: Value<'_>) { + /// let Value::Renderable(v) = value else { return }; + /// self.0 = v.render_to_string(); + /// } + /// } + /// + /// let my_struct = NotValuable { + /// foo: 123, + /// bar: 456, + /// }; + /// + /// let mut renderer = Renderer(String::default()); + /// + /// valuable::visit(&Renderable::Debug(&my_struct), &mut renderer); + /// assert_eq!(renderer.0, "NotValuable { foo: 123, bar: 456 }"); + /// ``` + Debug( + /// The actual type to be rendered with [`Debug::fmt`] + &'a dyn Debug, + ), + + /// Renderable sub-type that is rendered via its [`Display`] implementation + /// ``` + /// use valuable::{Valuable, Value, Visit, Renderable}; + /// use core::fmt; + /// + /// struct NotValuable { + /// foo: u32, + /// bar: u32, + /// } + /// + /// struct Renderer(String); + /// + /// impl Visit for Renderer { + /// fn visit_value(&mut self, value: Value<'_>) { + /// let Value::Renderable(v) = value else { return }; + /// self.0 = v.render_to_string(); + /// } + /// } + /// + /// let my_struct = NotValuable { + /// foo: 123, + /// bar: 456, + /// }; + /// + /// impl fmt::Display for NotValuable { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// write!(f, "[Foo: {}, Bar: {}]", &self.foo, &self.bar) + /// } + /// } + /// + /// let mut renderer = Renderer(String::default()); + /// + /// valuable::visit( + /// &Renderable::Display(&my_struct), + /// &mut renderer); + /// assert_eq!(renderer.0, "[Foo: 123, Bar: 456]"); + /// ``` + Display( + /// The actual type to be rendered with [`Display::fmt`] + &'a dyn Display, + ), +} + +impl<'a> Debug for Renderable<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Renderable::Debug(inner) => { + if f.alternate() { + write!(f, "{:#?}", inner) + } else { + write!(f, "{:?}", inner) + } + } + Renderable::Display(inner) => { + if f.alternate() { + write!(f, "{:#}", inner) + } else { + write!(f, "{}", inner) + } + } + } + } +} + +impl<'a> Renderable<'a> { + /// Render this [`Renderable`] to the given [`Write`] target + /// ``` + /// use valuable::{Valuable, Value, Visit, Renderable}; + /// use core::fmt; + /// + /// #[derive(Debug)] + /// struct NotValuable { + /// foo: u32, + /// bar: u32, + /// } + /// + /// let my_struct = NotValuable { + /// foo: 123, + /// bar: 456, + /// }; + /// + /// let mut buf = String::new(); + /// Renderable::Debug(&my_struct).render(&mut buf); + /// assert_eq!(buf, "NotValuable { foo: 123, bar: 456 }"); + /// ``` + #[inline] + pub fn render(&self, target: &mut dyn Write) -> fmt::Result { + write!(target, "{self:?}") + } + + /// Render this [`Renderable`] to the given [`Write`] target, but force the + /// prettiness/alternate to be the given value + /// ``` + /// use valuable::{Valuable, Value, Visit, Renderable}; + /// use core::fmt; + /// + /// #[derive(Debug)] + /// struct NotValuable { + /// foo: u32, + /// bar: u32, + /// } + /// + /// let my_struct = NotValuable { + /// foo: 123, + /// bar: 456, + /// }; + /// + /// let mut buf = String::new(); + /// Renderable::Debug(&my_struct).render_with_prettiness(&mut buf, true); + /// assert_eq!(buf, + /// "NotValuable { + /// foo: 123, + /// bar: 456, + /// }"); + /// ``` + #[inline] + pub fn render_with_prettiness(&self, target: &mut dyn Write, pretty: bool) -> fmt::Result { + if pretty { + write!(target, "{self:#?}") + } else { + write!(target, "{self:?}") + } + } + + /// Render this [`Renderable`] to an owned [`String`] + /// ``` + /// use valuable::{Valuable, Value, Visit, Renderable}; + /// use core::fmt; + /// + /// #[derive(Debug)] + /// struct NotValuable { + /// foo: u32, + /// bar: u32, + /// } + /// + /// let my_struct = NotValuable { + /// foo: 123, + /// bar: 456, + /// }; + /// + /// let rendered = Renderable::Debug(&my_struct).render_to_string(); + /// assert_eq!(rendered, "NotValuable { foo: 123, bar: 456 }"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + pub fn render_to_string(&self) -> alloc::string::String { + format!("{self:?}") + } + + /// Render this [`Renderable`] to an owned [`String`], but force the + /// prettiness/alternate to be the given value + /// ``` + /// use valuable::{Valuable, Value, Visit, Renderable}; + /// use core::fmt; + /// + /// #[derive(Debug)] + /// struct NotValuable { + /// foo: u32, + /// bar: u32, + /// } + /// + /// let my_struct = NotValuable { + /// foo: 123, + /// bar: 456, + /// }; + /// + /// let rendered = Renderable::Debug(&my_struct) + /// .render_to_string_with_prettiness(true); + /// assert_eq!(rendered, + /// "NotValuable { + /// foo: 123, + /// bar: 456, + /// }"); + /// ``` + #[cfg(feature = "alloc")] + #[inline] + pub fn render_to_string_with_prettiness(&self, pretty: bool) -> alloc::string::String { + if pretty { + format!("{self:#?}") + } else { + format!("{self:?}") + } + } +} + +impl<'a> Valuable for Renderable<'a> { + fn as_value(&self) -> crate::Value<'_> { + Value::Renderable(*self) + } + + fn visit(&self, visit: &mut dyn crate::Visit) { + visit.visit_value(self.as_value()); + } + + fn visit_slice(slice: &[Self], visit: &mut dyn crate::Visit) + where + Self: Sized, + { + visit.visit_primitive_slice(Slice::Renderable(slice)); + } +} diff --git a/valuable/src/slice.rs b/valuable/src/slice.rs index a6ada6e..5544625 100644 --- a/valuable/src/slice.rs +++ b/valuable/src/slice.rs @@ -326,6 +326,20 @@ slice! { /// ``` Isize(isize), + /// A slice containing `Renderable` values. + /// + /// # Examples + /// + /// ``` + /// use valuable::{Slice, Renderable}; + /// + /// let v = Slice::Renderable(&[ + /// Renderable::Debug(&"foo"), + /// Renderable::Display(&"bar") + /// ]); + /// ``` + Renderable(Renderable<'a>), + /// A slice containing `str` values. /// /// # Examples diff --git a/valuable/src/value.rs b/valuable/src/value.rs index d0ada87..13f0f35 100644 --- a/valuable/src/value.rs +++ b/valuable/src/value.rs @@ -1,4 +1,4 @@ -use crate::{Enumerable, Listable, Mappable, Structable, Tuplable, Valuable, Visit}; +use crate::{Enumerable, Listable, Mappable, Renderable, Structable, Tuplable, Valuable, Visit}; use core::fmt; @@ -404,6 +404,27 @@ value! { /// let v = Value::Tuplable(&my_tuple); /// ``` Tuplable(&'a dyn Tuplable), + + /// A value that can be rendered to a string + /// + /// # Examples + /// + /// ``` + /// use valuable::{Value, Renderable}; + /// + /// #[derive(Debug)] + /// struct NotValuable{ + /// inner: u32 + /// }; + /// + /// let nv = NotValuable { + /// inner: 42 + /// }; + /// + /// let v = Value::Renderable(Renderable::Debug(&nv)); + /// ``` + /// + Renderable(Renderable<'a>), } impl Valuable for Value<'_> {