diff --git a/fmt/src/lib.rs b/fmt/src/lib.rs index fb5b574e..2904751b 100644 --- a/fmt/src/lib.rs +++ b/fmt/src/lib.rs @@ -18,7 +18,7 @@ mod to_fmt; mod to_value; mod to_write; -pub use self::{to_fmt::*, to_value::*, to_write::*}; +pub use self::{to_fmt::*, to_value::*, to_write::*, writer::*}; #[cfg(feature = "alloc")] mod to_string; diff --git a/fmt/src/to_write.rs b/fmt/src/to_write.rs index e5c5caf9..12a8d3aa 100644 --- a/fmt/src/to_write.rs +++ b/fmt/src/to_write.rs @@ -1,4 +1,4 @@ -use crate::writer::{GenericWriter, Writer}; +use crate::writer::{GenericWriter, TokenWrite, Writer}; use core::fmt::{self, Write}; /** @@ -8,3 +8,10 @@ pub fn stream_to_write(fmt: impl Write, v: impl sval::Value) -> fmt::Result { v.stream(&mut Writer::new(GenericWriter(fmt))) .map_err(|_| fmt::Error) } + +/** +Format a value into an underlying token-aware formatter. +*/ +pub fn stream_to_token_write(fmt: impl TokenWrite, v: impl sval::Value) -> fmt::Result { + v.stream(&mut Writer::new(fmt)).map_err(|_| fmt::Error) +} diff --git a/fmt/src/writer.rs b/fmt/src/writer.rs index dd2a1a88..612ddf63 100644 --- a/fmt/src/writer.rs +++ b/fmt/src/writer.rs @@ -2,32 +2,259 @@ use core::fmt::{self, Debug, Write}; pub(crate) struct Writer { is_current_depth_empty: bool, - is_text_quoted: bool, + is_text_number: bool, out: W, } /** -A trait that abstracts over a generic implementation of `fmt::Write` -and `fmt::Formatter`. +A token-aware [`fmt::Write`]. -It makes sure that formatting flags will be preserved. +This trait can be used to customize the way various tokens are written, such +as colorizing numbers and booleans differently. */ -pub(crate) trait Fmt: Write { - fn write_u8(&mut self, value: u8) -> fmt::Result; - fn write_u16(&mut self, value: u16) -> fmt::Result; - fn write_u32(&mut self, value: u32) -> fmt::Result; - fn write_u64(&mut self, value: u64) -> fmt::Result; - fn write_u128(&mut self, value: u128) -> fmt::Result; - fn write_i8(&mut self, value: i8) -> fmt::Result; - fn write_i16(&mut self, value: i16) -> fmt::Result; - fn write_i32(&mut self, value: i32) -> fmt::Result; - fn write_i64(&mut self, value: i64) -> fmt::Result; - fn write_i128(&mut self, value: i128) -> fmt::Result; - fn write_f32(&mut self, value: f32) -> fmt::Result; - fn write_f64(&mut self, value: f64) -> fmt::Result; +pub trait TokenWrite: Write { + /** + Write a number. + */ + fn write_u8(&mut self, value: u8) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_u16(&mut self, value: u16) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_u32(&mut self, value: u32) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_u64(&mut self, value: u64) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_u128(&mut self, value: u128) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_i8(&mut self, value: i8) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_i16(&mut self, value: i16) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_i32(&mut self, value: i32) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_i64(&mut self, value: i64) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_i128(&mut self, value: i128) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_f32(&mut self, value: f32) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_f64(&mut self, value: f64) -> fmt::Result { + self.write_number(value) + } + + /** + Write a number. + */ + fn write_number(&mut self, num: N) -> fmt::Result { + self.write_fmt(format_args!("{}", num)) + } + + /** + Write null or unit. + */ + fn write_null(&mut self) -> fmt::Result { + self.write_atom("()") + } + + /** + Write a boolean. + */ + fn write_bool(&mut self, value: bool) -> fmt::Result { + self.write_atom(value) + } + + /** + Write an atom, like `true` or `()`. + */ + fn write_atom(&mut self, atom: A) -> fmt::Result { + self.write_fmt(format_args!("{}", atom)) + } + + /** + Write a fragment of punctuation, like `:` or `,`. + */ + fn write_punct(&mut self, punct: &str) -> fmt::Result { + self.write_str(punct) + } + + /** + Write a type name. + */ + fn write_type(&mut self, ty: &str) -> fmt::Result { + self.write_ident(ty) + } + + /** + Write an identifier. + */ + fn write_ident(&mut self, ident: &str) -> fmt::Result { + self.write_str(ident) + } + + /** + Write a fragment of text. + */ + fn write_text(&mut self, text: &str) -> fmt::Result { + self.write_str(text) + } + + /** + Write a field name. + */ + fn write_field(&mut self, field: &str) -> fmt::Result { + self.write_ident(field) + } + + /** + Write whitespace. + */ + fn write_ws(&mut self, ws: &str) -> fmt::Result { + self.write_str(ws) + } +} + +impl<'a, W: TokenWrite + ?Sized> TokenWrite for &'a mut W { + fn write_u8(&mut self, value: u8) -> fmt::Result { + (**self).write_u8(value) + } + + fn write_u16(&mut self, value: u16) -> fmt::Result { + (**self).write_u16(value) + } + + fn write_u32(&mut self, value: u32) -> fmt::Result { + (**self).write_u32(value) + } + + fn write_u64(&mut self, value: u64) -> fmt::Result { + (**self).write_u64(value) + } + + fn write_u128(&mut self, value: u128) -> fmt::Result { + (**self).write_u128(value) + } + + fn write_i8(&mut self, value: i8) -> fmt::Result { + (**self).write_i8(value) + } + + fn write_i16(&mut self, value: i16) -> fmt::Result { + (**self).write_i16(value) + } + + fn write_i32(&mut self, value: i32) -> fmt::Result { + (**self).write_i32(value) + } + + fn write_i64(&mut self, value: i64) -> fmt::Result { + (**self).write_i64(value) + } + + fn write_i128(&mut self, value: i128) -> fmt::Result { + (**self).write_i128(value) + } + + fn write_f32(&mut self, value: f32) -> fmt::Result { + (**self).write_f32(value) + } + + fn write_f64(&mut self, value: f64) -> fmt::Result { + (**self).write_f64(value) + } + + fn write_number(&mut self, num: N) -> fmt::Result { + (**self).write_number(num) + } + + fn write_null(&mut self) -> fmt::Result { + (**self).write_null() + } + + fn write_bool(&mut self, value: bool) -> fmt::Result { + (**self).write_bool(value) + } + + fn write_atom(&mut self, atom: A) -> fmt::Result { + (**self).write_atom(atom) + } + + fn write_punct(&mut self, punct: &str) -> fmt::Result { + (**self).write_punct(punct) + } + + fn write_type(&mut self, ty: &str) -> fmt::Result { + (**self).write_type(ty) + } + + fn write_ident(&mut self, ident: &str) -> fmt::Result { + (**self).write_ident(ident) + } + + fn write_text(&mut self, text: &str) -> fmt::Result { + (**self).write_text(text) + } + + fn write_field(&mut self, field: &str) -> fmt::Result { + (**self).write_field(field) + } } -impl<'a, 'b> Fmt for &'a mut fmt::Formatter<'b> { +impl<'a> TokenWrite for fmt::Formatter<'a> { fn write_u8(&mut self, value: u8) -> fmt::Result { value.fmt(self) } @@ -93,7 +320,7 @@ impl Write for GenericWriter { } } -impl Fmt for GenericWriter { +impl TokenWrite for GenericWriter { fn write_u8(&mut self, value: u8) -> fmt::Result { self.write_str(itoa::Buffer::new().format(value)) } @@ -147,50 +374,35 @@ impl Writer { pub fn new(out: W) -> Self { Writer { is_current_depth_empty: true, - is_text_quoted: true, + is_text_number: false, out, } } } -impl Write for Writer { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.out.write_str(s) - } - - fn write_char(&mut self, c: char) -> fmt::Result { - self.out.write_char(c) - } - - fn write_fmt(self: &mut Self, args: fmt::Arguments<'_>) -> fmt::Result { - self.out.write_fmt(args) - } -} - -impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { +impl<'sval, W: TokenWrite> sval::Stream<'sval> for Writer { fn null(&mut self) -> sval::Result { - self.write_str("()").map_err(|_| sval::Error::new())?; + self.out.write_null().map_err(|_| sval::Error::new())?; Ok(()) } fn bool(&mut self, value: bool) -> sval::Result { - self.write_str(if value { "true" } else { "false" }) - .map_err(|_| sval::Error::new())?; + self.out.write_bool(value).map_err(|_| sval::Error::new())?; Ok(()) } fn text_begin(&mut self, _: Option) -> sval::Result { - if self.is_text_quoted { - self.write_char('"').map_err(|_| sval::Error::new())?; + if !self.is_text_number { + self.out.write_text("\"").map_err(|_| sval::Error::new())?; } Ok(()) } fn text_fragment_computed(&mut self, fragment: &str) -> sval::Result { - if self.is_text_quoted { + if !self.is_text_number { // Inlined from `impl Debug for str` // This avoids writing the outer quotes for the string // and handles the `'` case @@ -204,28 +416,33 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { // If char needs escaping, flush backlog so far and write, else skip if c != '\'' && esc.len() != 1 { self.out - .write_str(&fragment[from..i]) + .write_text(&fragment[from..i]) .map_err(|_| sval::Error::new())?; for c in esc { - self.out.write_char(c).map_err(|_| sval::Error::new())?; + let mut buf = [0; 4]; + self.out + .write_text(c.encode_utf8(&mut buf)) + .map_err(|_| sval::Error::new())?; } from = i + c.len_utf8(); } } self.out - .write_str(&fragment[from..]) + .write_text(&fragment[from..]) .map_err(|_| sval::Error::new())?; } else { - self.write_str(fragment).map_err(|_| sval::Error::new())?; + self.out + .write_number(fragment) + .map_err(|_| sval::Error::new())?; } Ok(()) } fn text_end(&mut self) -> sval::Result { - if self.is_text_quoted { - self.write_char('"').map_err(|_| sval::Error::new())?; + if !self.is_text_number { + self.out.write_text("\"").map_err(|_| sval::Error::new())?; } Ok(()) @@ -322,26 +539,27 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { } fn map_begin(&mut self, _: Option) -> sval::Result { - self.is_text_quoted = true; + self.is_text_number = false; self.is_current_depth_empty = true; - self.write_char('{').map_err(|_| sval::Error::new())?; + self.out.write_punct("{").map_err(|_| sval::Error::new())?; Ok(()) } fn map_key_begin(&mut self) -> sval::Result { if !self.is_current_depth_empty { - self.write_str(", ").map_err(|_| sval::Error::new())?; - } else { - self.write_char(' ').map_err(|_| sval::Error::new())?; + self.out.write_punct(",").map_err(|_| sval::Error::new())?; } + self.out.write_ws(" ").map_err(|_| sval::Error::new())?; + Ok(()) } fn map_key_end(&mut self) -> sval::Result { - self.write_str(": ").map_err(|_| sval::Error::new())?; + self.out.write_punct(":").map_err(|_| sval::Error::new())?; + self.out.write_ws(" ").map_err(|_| sval::Error::new())?; Ok(()) } @@ -358,26 +576,27 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { fn map_end(&mut self) -> sval::Result { if !self.is_current_depth_empty { - self.write_str(" }").map_err(|_| sval::Error::new())?; - } else { - self.write_char('}').map_err(|_| sval::Error::new())?; + self.out.write_ws(" ").map_err(|_| sval::Error::new())?; } + self.out.write_punct("}").map_err(|_| sval::Error::new())?; + Ok(()) } fn seq_begin(&mut self, _: Option) -> sval::Result { - self.is_text_quoted = true; + self.is_text_number = false; self.is_current_depth_empty = true; - self.write_char('[').map_err(|_| sval::Error::new())?; + self.out.write_punct("[").map_err(|_| sval::Error::new())?; Ok(()) } fn seq_value_begin(&mut self) -> sval::Result { if !self.is_current_depth_empty { - self.write_str(", ").map_err(|_| sval::Error::new())?; + self.out.write_punct(",").map_err(|_| sval::Error::new())?; + self.out.write_ws(" ").map_err(|_| sval::Error::new())?; } Ok(()) @@ -390,7 +609,7 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { } fn seq_end(&mut self) -> sval::Result { - self.write_char(']').map_err(|_| sval::Error::new())?; + self.out.write_punct("]").map_err(|_| sval::Error::new())?; Ok(()) } @@ -419,19 +638,20 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { label: Option<&sval::Label>, _: Option<&sval::Index>, ) -> sval::Result { - self.is_text_quoted = true; + self.is_text_number = false; match tag { Some(&sval::tags::NUMBER) => { - self.is_text_quoted = false; + self.is_text_number = true; Ok(()) } _ => { if let Some(label) = label { - self.write_str(label.as_str()) + self.out + .write_type(label.as_str()) .map_err(|_| sval::Error::new())?; - self.write_char('(').map_err(|_| sval::Error::new())?; + self.out.write_punct("(").map_err(|_| sval::Error::new())?; } Ok(()) @@ -447,13 +667,13 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { ) -> sval::Result { match tag { Some(&sval::tags::NUMBER) => { - self.is_text_quoted = true; + self.is_text_number = false; Ok(()) } _ => { if label.is_some() { - self.write_char(')').map_err(|_| sval::Error::new())?; + self.out.write_punct(")").map_err(|_| sval::Error::new())?; } Ok(()) @@ -468,7 +688,8 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { _: Option<&sval::Index>, ) -> sval::Result { if let Some(label) = label { - self.write_str(label.as_str()) + self.out + .write_type(label.as_str()) .map_err(|_| sval::Error::new())?; } else { self.null()?; @@ -485,20 +706,21 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { num_entries_hint: Option, ) -> sval::Result { if let Some(label) = label { - self.write_str(label.as_str()) + self.out + .write_type(label.as_str()) .map_err(|_| sval::Error::new())?; - self.write_char(' ').map_err(|_| sval::Error::new())?; + self.out.write_ws(" ").map_err(|_| sval::Error::new())?; } self.map_begin(num_entries_hint) } fn record_value_begin(&mut self, _: Option<&sval::Tag>, label: &sval::Label) -> sval::Result { - self.is_text_quoted = false; self.map_key_begin()?; - sval::stream(&mut *self, label.as_str())?; + self.out + .write_field(label.as_str()) + .map_err(|_| sval::Error::new())?; self.map_key_end()?; - self.is_text_quoted = true; self.map_value_begin() } @@ -523,15 +745,16 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { _: Option<&sval::Index>, _: Option, ) -> sval::Result { - self.is_text_quoted = true; + self.is_text_number = false; self.is_current_depth_empty = true; if let Some(label) = label { - self.write_str(label.as_str()) + self.out + .write_type(label.as_str()) .map_err(|_| sval::Error::new())?; } - self.write_char('(').map_err(|_| sval::Error::new())?; + self.out.write_punct("(").map_err(|_| sval::Error::new())?; Ok(()) } @@ -550,7 +773,7 @@ impl<'sval, W: Fmt> sval::Stream<'sval> for Writer { _: Option<&sval::Label>, _: Option<&sval::Index>, ) -> sval::Result { - self.write_char(')').map_err(|_| sval::Error::new())?; + self.out.write_punct(")").map_err(|_| sval::Error::new())?; Ok(()) } diff --git a/fmt/test/lib.rs b/fmt/test/lib.rs index a057b102..179123b9 100644 --- a/fmt/test/lib.rs +++ b/fmt/test/lib.rs @@ -19,6 +19,10 @@ fn assert_fmt(v: impl sval::Value + fmt::Debug) { assert_eq!(expected, buffered.as_str()); } +#[derive(Value, Debug)] +#[sval(tag = "sval::tags::NUMBER")] +struct Number(&'static str); + #[derive(Value, Debug)] struct MapStruct { field_0: i32, @@ -221,7 +225,66 @@ fn debug_exotic_unnamed_enum() { } #[test] - fn failing_value_does_not_panic_to_string() { +fn token_write() { + #[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] + struct Writer { + null: bool, + bool: bool, + text: bool, + number: bool, + } + + impl fmt::Write for Writer { + fn write_str(&mut self, _: &str) -> fmt::Result { + Ok(()) + } + } + + impl sval_fmt::TokenWrite for Writer { + fn write_null(&mut self) -> core::fmt::Result { + self.null = true; + Ok(()) + } + + fn write_text(&mut self, _: &str) -> core::fmt::Result { + self.text = true; + Ok(()) + } + + fn write_number(&mut self, _: N) -> fmt::Result { + self.number = true; + Ok(()) + } + + fn write_bool(&mut self, _: bool) -> core::fmt::Result { + self.bool = true; + Ok(()) + } + } + + let mut writer = Writer::default(); + sval_fmt::stream_to_token_write(&mut writer, 42).unwrap(); + assert!(writer.number); + + let mut writer = Writer::default(); + sval_fmt::stream_to_token_write(&mut writer, Number("436543.457656765")).unwrap(); + assert!(writer.number); + + let mut writer = Writer::default(); + sval_fmt::stream_to_token_write(&mut writer, true).unwrap(); + assert!(writer.bool); + + let mut writer = Writer::default(); + sval_fmt::stream_to_token_write(&mut writer, "a string").unwrap(); + assert!(writer.text); + + let mut writer = Writer::default(); + sval_fmt::stream_to_token_write(&mut writer, ()).unwrap(); + assert!(writer.null); +} + +#[test] +fn failing_value_does_not_panic_to_string() { struct Kaboom; impl sval::Value for Kaboom { @@ -231,12 +294,23 @@ fn debug_exotic_unnamed_enum() { } #[derive(Value)] - struct NestedKaboom { + struct NestedKaboom { a: i32, b: Kaboom, c: i32, } - assert_eq!("", sval_fmt::ToFmt::new(Kaboom).to_string()); - assert_eq!("NestedKaboom { a: 1, b: ", sval_fmt::ToFmt::new(NestedKaboom { a: 1, b: Kaboom, c: 2 }).to_string()); + assert_eq!( + "", + sval_fmt::ToFmt::new(Kaboom).to_string() + ); + assert_eq!( + "NestedKaboom { a: 1, b: ", + sval_fmt::ToFmt::new(NestedKaboom { + a: 1, + b: Kaboom, + c: 2 + }) + .to_string() + ); } diff --git a/src/data.rs b/src/data.rs index 13ba27d4..1790b533 100644 --- a/src/data.rs +++ b/src/data.rs @@ -41,9 +41,9 @@ pub struct Label<'computed> { } // SAFETY: Label doesn't mutate or synchronize: it acts just like a `&str` -unsafe impl<'computed> Send for Label<'computed> { } +unsafe impl<'computed> Send for Label<'computed> {} // SAFETY: Label doesn't mutate or synchronize: it acts just like a `&str` -unsafe impl<'computed> Sync for Label<'computed> { } +unsafe impl<'computed> Sync for Label<'computed> {} #[cfg(not(feature = "alloc"))] impl<'computed> Clone for Label<'computed> {