Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Renderable) Add a new Value variant: Renderable #133

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions tests/tests/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use valuable::*;

use std::collections::HashMap;
use std::env;
use std::fmt::{Debug, Display};

#[test]
fn test_derive_struct() {
Expand Down Expand Up @@ -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::<Vec<_>>(),
["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)"
);
}
55 changes: 55 additions & 0 deletions tests/tests/renderable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use valuable::{Renderable, Valuable};

#[derive(Debug)]
struct NotTuplable<'a> {
s: &'a str,
i: usize,
}

impl core::fmt::Display for NotTuplable<'_> {
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(), 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));

// 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));
}
22 changes: 22 additions & 0 deletions valuable-derive/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Span>,
skip: Option<Span>,
pub(crate) debug: bool,
pub(crate) display: bool,
}

impl Attrs {
Expand All @@ -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 {
Expand Down Expand Up @@ -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),
}
Expand All @@ -113,6 +133,8 @@ pub(crate) fn parse_attrs(cx: &Context, attrs: &[syn::Attribute], pos: Position)
rename,
transparent,
skip,
display,
debug,
}
}

Expand Down
32 changes: 27 additions & 5 deletions valuable-derive/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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();
Expand Down
10 changes: 10 additions & 0 deletions valuable-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
/// ```
Expand Down
7 changes: 7 additions & 0 deletions valuable-serde/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,13 @@ where
Value::Path(p) => Serialize::serialize(p, serializer),
#[cfg(feature = "std")]
Value::Error(e) => SerializeError(e).serialize(serializer),
#[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),
}
Expand Down
3 changes: 3 additions & 0 deletions valuable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading
Loading