From 989f83b05fe9f7eb843e73a404ff36830ad43d1f Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 22 Sep 2024 19:53:46 -0400 Subject: [PATCH 1/6] 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<'_> { From 3a6111addf52cb2e5d4f5f73861349e434e19e0b Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 22 Sep 2024 21:00:20 -0400 Subject: [PATCH 2/6] Fixing ci build issues with docs, min-rust version, and features --- valuable/src/renderable.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/valuable/src/renderable.rs b/valuable/src/renderable.rs index c835823..b960c57 100644 --- a/valuable/src/renderable.rs +++ b/valuable/src/renderable.rs @@ -5,7 +5,7 @@ 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 +/// This is most useful when defining a [`Structable`](crate::Structable) value that includes /// fields of types where [`Valuable`] cannot be implemented like types /// contained in external crates. /// @@ -170,7 +170,7 @@ impl<'a> Renderable<'a> { /// ``` #[inline] pub fn render(&self, target: &mut dyn Write) -> fmt::Result { - write!(target, "{self:?}") + write!(target, "{:?}", self) } /// Render this [`Renderable`] to the given [`Write`] target, but force the @@ -201,9 +201,9 @@ impl<'a> Renderable<'a> { #[inline] pub fn render_with_prettiness(&self, target: &mut dyn Write, pretty: bool) -> fmt::Result { if pretty { - write!(target, "{self:#?}") + write!(target, "{:#?}", self) } else { - write!(target, "{self:?}") + write!(target, "{:?}", self) } } @@ -229,7 +229,7 @@ impl<'a> Renderable<'a> { #[cfg(feature = "alloc")] #[inline] pub fn render_to_string(&self) -> alloc::string::String { - format!("{self:?}") + alloc::format!("{:?}", self) } /// Render this [`Renderable`] to an owned [`String`], but force the @@ -261,9 +261,9 @@ impl<'a> Renderable<'a> { #[inline] pub fn render_to_string_with_prettiness(&self, pretty: bool) -> alloc::string::String { if pretty { - format!("{self:#?}") + alloc::format!("{:#?}", self) } else { - format!("{self:?}") + alloc::format!("{:?}", self) } } } From 763efbc6de522b43239fb5ec1d0190477c7ed809 Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 22 Sep 2024 21:08:34 -0400 Subject: [PATCH 3/6] Fixing no-std serialization error --- valuable-serde/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valuable-serde/src/lib.rs b/valuable-serde/src/lib.rs index f1839d4..33767d8 100644 --- a/valuable-serde/src/lib.rs +++ b/valuable-serde/src/lib.rs @@ -273,7 +273,7 @@ where #[cfg(not(feature = "alloc"))] { // Can't serialize a renderable without allocating - Ok(S::Ok) + serializer.serialize_none() } } From eca6a6e1c31d914d541786c859c8ed6c81374173 Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 22 Sep 2024 21:13:33 -0400 Subject: [PATCH 4/6] Fixing unused variable in no-std serialization --- valuable-serde/src/lib.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/valuable-serde/src/lib.rs b/valuable-serde/src/lib.rs index 33767d8..e4028b4 100644 --- a/valuable-serde/src/lib.rs +++ b/valuable-serde/src/lib.rs @@ -265,16 +265,12 @@ 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 - serializer.serialize_none() - } + #[cfg(feature = "alloc")] + Value::Renderable(r) => serializer.serialize_str(&r.render_to_string()), + #[cfg(not(feature = "alloc"))] + Value::Renderable(_) => { + // Can't serialize a renderable without allocating + serializer.serialize_none() } v => unimplemented!("{:?}", v), From 447a8497294b0bfd2a42ef7969e61b608568de04 Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 26 Jan 2025 13:11:03 -0500 Subject: [PATCH 5/6] Fix `format` calls in tests not compatible with rust 1.56 --- tests/tests/renderable.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/tests/renderable.rs b/tests/tests/renderable.rs index 43e28f9..2b69b23 100644 --- a/tests/tests/renderable.rs +++ b/tests/tests/renderable.rs @@ -6,7 +6,7 @@ struct NotTuplable<'a> { i: usize, } -impl<'a> core::fmt::Display for NotTuplable<'a> { +impl core::fmt::Display for NotTuplable<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -25,12 +25,12 @@ fn test_renderable_struct_from_debug() { let v = r.as_value(); // Rendering should produce the debug output for the struct - assert_eq!(r.render_to_string(), format!("{s:?}")); + 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:#?}")); + assert_eq!(format!("{:?}", v), format!("{:?}", s)); + assert_eq!(format!("{:#?}", v), format!("{:#?}", s)); } #[test] @@ -42,14 +42,14 @@ fn test_renderable_struct_from_display() { let v = r.as_value(); // Rendering should produce the display output for the struct - assert_eq!(r.render_to_string(), format!("{s}")); + assert_eq!(r.render_to_string(), s.to_string()); // Just to make sure, the display output should be different for the debug // output - assert_ne!(r.render_to_string(), format!("{s:?}")); + 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:#}")); + assert_eq!(format!("{:?}", v), format!("{}", s)); + assert_eq!(format!("{:#?}", v), format!("{:#}", s)); } From c24937208324628ab4472f151bcd42f1366c2d1c Mon Sep 17 00:00:00 2001 From: Kevin Guthrie Date: Sun, 26 Jan 2025 14:52:10 -0500 Subject: [PATCH 6/6] Add derive-macro attributes for `Renderable::Debug` and `Renderable::Display` --- tests/tests/derive.rs | 70 +++++++++++++++++++++++++++++++++++ valuable-derive/src/attr.rs | 22 +++++++++++ valuable-derive/src/expand.rs | 32 +++++++++++++--- valuable-derive/src/lib.rs | 10 +++++ 4 files changed, 129 insertions(+), 5 deletions(-) diff --git a/tests/tests/derive.rs b/tests/tests/derive.rs index 996881b..f755fa9 100644 --- a/tests/tests/derive.rs +++ b/tests/tests/derive.rs @@ -5,6 +5,7 @@ use valuable::*; use std::collections::HashMap; use std::env; +use std::fmt::{Debug, Display}; #[test] fn test_derive_struct() { @@ -205,3 +206,72 @@ fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); } + +struct NotValuable; + +impl Display for NotValuable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Display NotValuable") + } +} + +impl Debug for NotValuable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Debug NotValuable") + } +} + +#[test] +fn test_rendering_not_valuable_named() { + #[derive(Valuable)] + struct S { + #[valuable(debug)] + debug_struct: NotValuable, + #[valuable(display)] + display_struct: NotValuable, + } + + let s = S { + debug_struct: NotValuable, + display_struct: NotValuable, + }; + + let v_s = Structable::definition(&s); + + let fields = match v_s.fields() { + Fields::Named(fields) => fields, + _ => unreachable!(), + }; + assert_eq!(fields.len(), 2); + assert_eq!( + fields.iter().map(|f| f.name()).collect::>(), + ["debug_struct", "display_struct"] + ); + + let v = Valuable::as_value(&s); + assert_eq!( + format!("{:?}", v), + "S { debug_struct: Debug NotValuable, display_struct: Display NotValuable }" + ); +} + +#[test] +fn test_rendering_not_valuable_unnamed() { + #[derive(Valuable)] + struct S( + #[valuable(debug)] NotValuable, + #[valuable(display)] NotValuable, + ); + + let s = S(NotValuable, NotValuable); + + let v_s = Structable::definition(&s); + + assert!(matches!(v_s.fields(), Fields::Unnamed(2))); + + let v = Valuable::as_value(&s); + assert_eq!( + format!("{:?}", v), + "S(Debug NotValuable, Display NotValuable)" + ); +} diff --git a/valuable-derive/src/attr.rs b/valuable-derive/src/attr.rs index 45a0e0b..947fe45 100644 --- a/valuable-derive/src/attr.rs +++ b/valuable-derive/src/attr.rs @@ -42,12 +42,28 @@ static ATTRS: &[AttrDef] = &[ ], style: &[MetaStyle::Ident], }, + // #[valuable(debug)] + AttrDef { + name: "debug", + conflicts_with: &["skip", "rename", "display"], + position: &[Position::NamedField, Position::UnnamedField], + style: &[MetaStyle::Ident], + }, + // #[valuable(display)] + AttrDef { + name: "display", + conflicts_with: &["skip", "rename", "debug"], + position: &[Position::NamedField, Position::UnnamedField], + style: &[MetaStyle::Ident], + }, ]; pub(crate) struct Attrs { rename: Option<(syn::MetaNameValue, syn::LitStr)>, transparent: Option, skip: Option, + pub(crate) debug: bool, + pub(crate) display: bool, } impl Attrs { @@ -71,6 +87,8 @@ pub(crate) fn parse_attrs(cx: &Context, attrs: &[syn::Attribute], pos: Position) let mut rename = None; let mut transparent = None; let mut skip = None; + let mut debug = false; + let mut display = false; let attrs = filter_attrs(cx, attrs, pos); for (def, meta) in &attrs { @@ -104,6 +122,8 @@ pub(crate) fn parse_attrs(cx: &Context, attrs: &[syn::Attribute], pos: Position) "transparent" => transparent = Some(meta.span()), // #[valuable(skip)] "skip" => skip = Some(meta.span()), + "debug" => debug = true, + "display" => display = true, _ => unreachable!("{}", def.name), } @@ -113,6 +133,8 @@ pub(crate) fn parse_attrs(cx: &Context, attrs: &[syn::Attribute], pos: Position) rename, transparent, skip, + display, + debug, } } diff --git a/valuable-derive/src/expand.rs b/valuable-derive/src/expand.rs index 25fd30c..45816d7 100644 --- a/valuable-derive/src/expand.rs +++ b/valuable-derive/src/expand.rs @@ -101,11 +101,22 @@ fn derive_struct( .iter() .enumerate() .filter(|(i, _)| !field_attrs[*i].skip()) - .map(|(_, field)| { + .map(|(i, field)| { let f = field.ident.as_ref(); - let tokens = quote! { - &self.#f + let tokens = if field_attrs[i].debug { + quote! { + &valuable::Renderable::Debug(&self.#f) + } + } else if field_attrs[i].display { + quote! { + &valuable::Renderable::Display(&self.#f) + } + } else { + quote! { + &self.#f + } }; + respan(tokens, &field.ty) }); visit_fields = quote! { @@ -125,9 +136,20 @@ fn derive_struct( .filter(|(i, _)| !field_attrs[*i].skip()) .map(|(i, field)| { let index = syn::Index::from(i); - let tokens = quote! { - &self.#index + let tokens = if field_attrs[i].debug { + quote! { + &valuable::Renderable::Debug(&self.#index) + } + } else if field_attrs[i].display { + quote! { + &valuable::Renderable::Display(&self.#index) + } + } else { + quote! { + &self.#index + } }; + respan(tokens, &field.ty) }) .collect(); diff --git a/valuable-derive/src/lib.rs b/valuable-derive/src/lib.rs index 7da1257..1f8d833 100644 --- a/valuable-derive/src/lib.rs +++ b/valuable-derive/src/lib.rs @@ -27,6 +27,16 @@ use syn::parse_macro_input; /// /// Skip the field. /// +/// ## `#[valuable(debug)]` +/// +/// Include the debug representation of a field instead of interpreting it as +/// a complete `Valuable`. +/// +/// ## `#[valuable(display)]` +/// +/// Include the display representation of a field instead of interpreting it as +/// a complete `Valuable`. +/// /// # Examples /// /// ```