diff --git a/src/stream.rs b/src/stream.rs index 946ddb8..adcf6b6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -7,5 +7,6 @@ pub mod reader; pub mod writer; pub use event::{Event, QuoteStyle, Scalar, Tag}; +pub use read::Read; pub use reader::Reader; pub use writer::Writer; diff --git a/src/stream/event.rs b/src/stream/event.rs index fcf59eb..4c876a2 100644 --- a/src/stream/event.rs +++ b/src/stream/event.rs @@ -27,7 +27,7 @@ pub enum Event<'a> { } /// A string. This is used for both keys and values. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Scalar<'a> { tag: Option>, value: Cow<'a, str>, @@ -35,7 +35,7 @@ pub struct Scalar<'a> { } /// A conditional tag, which may be present in a [`Scalar`] key. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Tag<'a> { value: Cow<'a, str>, quote_style: QuoteStyle, @@ -88,21 +88,50 @@ impl<'a> Scalar<'a> { } /// Returns the scalar's tag. This only makes sense for keys; values completely ignore this field. + #[inline] pub fn tag(&self) -> Option<&Tag<'a>> { self.tag.as_ref() } /// Returns the scalar's value. + #[inline] pub fn value(&self) -> &str { &self.value } /// Returns the scalar's quote style. + #[inline] pub fn quote_style(&self) -> QuoteStyle { self.quote_style } } +impl<'a> PartialEq for Scalar<'a> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.tag == other.tag && self.value == other.value + } +} + +impl<'a> From> for Scalar<'a> { + fn from(value: Cow<'a, str>) -> Self { + // Safety: Cannot panic unless quote_style is Unquoted. + Scalar::new(None, value, QuoteStyle::Unspecified).unwrap() + } +} + +impl<'a> From<&'a str> for Scalar<'a> { + fn from(value: &'a str) -> Self { + Cow::Borrowed(value).into() + } +} + +impl<'a> From for Scalar<'a> { + fn from(value: String) -> Self { + Cow::<'a, str>::Owned(value).into() + } +} + impl<'a> Tag<'a> { /// Creates a new tag. Tags values must begin with `[`, end with `]`, and be at least three /// characters long. @@ -125,16 +154,49 @@ impl<'a> Tag<'a> { } /// Returns the tag's value. This includes the enclosing square brackets. + #[inline] pub fn value(&self) -> &str { &self.value } /// Returns the tag's quote style. + #[inline] pub fn quote_style(&self) -> QuoteStyle { self.quote_style } } +impl<'a> PartialEq for Tag<'a> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl<'a> TryFrom> for Tag<'a> { + type Error = (); + + fn try_from(value: Cow<'a, str>) -> Result { + Self::new(value, QuoteStyle::Unspecified).ok_or(()) + } +} + +impl<'a> TryFrom<&'a str> for Tag<'a> { + type Error = (); + + fn try_from(value: &'a str) -> Result { + Cow::Borrowed(value).try_into() + } +} + +impl<'a> TryFrom for Tag<'a> { + type Error = (); + + fn try_from(value: String) -> Result { + Cow::<'a, str>::Owned(value).try_into() + } +} + fn requires_quotes(value: &str) -> bool { value.is_empty() || value.contains(|c: char| c == '{' || c == '}' || c == '"' || c.is_whitespace()) diff --git a/src/stream/read.rs b/src/stream/read.rs index 1cde193..4dce1cf 100644 --- a/src/stream/read.rs +++ b/src/stream/read.rs @@ -15,11 +15,15 @@ mod sealed { /// [`serde_json`]: https://docs.rs/serde_json/latest/serde_json/de/trait.Read.html #[allow(private_bounds)] pub trait Read<'a>: sealed::Sealed { + type Inner; + fn peek_char(&mut self, i: usize) -> Result>; fn next_char(&mut self) -> Result>; fn parse_str(&mut self) -> Result>>; + + fn into_inner(self) -> Self::Inner; } #[derive(Debug)] @@ -40,6 +44,8 @@ impl IoReader { impl sealed::Sealed for IoReader {} impl<'a, R: io::Read> Read<'a> for IoReader { + type Inner = io::Bytes; + fn peek_char(&mut self, i: usize) -> Result> { todo!() } @@ -51,6 +57,10 @@ impl<'a, R: io::Read> Read<'a> for IoReader { fn parse_str(&mut self) -> Result>> { todo!() } + + fn into_inner(self) -> Self::Inner { + self.bytes + } } #[derive(Debug, Clone)] @@ -67,6 +77,8 @@ impl<'a> SliceReader<'a> { impl<'a> sealed::Sealed for SliceReader<'a> {} impl<'a> Read<'a> for SliceReader<'a> { + type Inner = &'a [u8]; + fn peek_char(&mut self, i: usize) -> Result> { todo!() } @@ -78,6 +90,10 @@ impl<'a> Read<'a> for SliceReader<'a> { fn parse_str(&mut self) -> Result>> { todo!() } + + fn into_inner(self) -> Self::Inner { + self.slice + } } #[derive(Debug, Clone)] @@ -94,6 +110,8 @@ impl<'a> StrReader<'a> { impl<'a> sealed::Sealed for StrReader<'a> {} impl<'a> Read<'a> for StrReader<'a> { + type Inner = &'a str; + fn peek_char(&mut self, i: usize) -> Result> { todo!() } @@ -105,4 +123,8 @@ impl<'a> Read<'a> for StrReader<'a> { fn parse_str(&mut self) -> Result>> { todo!() } + + fn into_inner(self) -> Self::Inner { + self.str + } } diff --git a/src/stream/reader.rs b/src/stream/reader.rs index 948fac8..8097286 100644 --- a/src/stream/reader.rs +++ b/src/stream/reader.rs @@ -18,7 +18,7 @@ impl Reader { impl Reader> { /// Creates a KeyValues reader from an [`io::Read`]. - /// + /// /// This may be less efficient than creating a `Reader` from a [`&[u8]`][Self::from_slice()] or /// [`&str`][Self::from_str()], as those implementations do not need to buffer input. pub fn from_reader(reader: R) -> Self { @@ -28,7 +28,7 @@ impl Reader> { impl<'a> Reader> { /// Creates a KeyValues reader from a `&[u8]`. - /// + /// /// If the input is known to be UTF-8, use [`Reader::from_str()`] instead to prevent redundant /// UTF-8 validation. pub fn from_slice(slice: &'a [u8]) -> Self { @@ -44,7 +44,8 @@ impl<'a> Reader> { } impl<'a, R: read::Read<'a>> Reader { - /// Reads the next event from the input. + /// Reads the next event from the input. If the end of the file has been reached, each + /// subsequent invocation will return [`Event::EndDocument`]. /// /// # Errors /// @@ -54,4 +55,73 @@ impl<'a, R: read::Read<'a>> Reader { pub fn read(&mut self) -> Result> { todo!() } + + /// Unwraps the inner reader from the `Reader`. + pub fn into_inner(self) -> R::Inner { + self.inner.into_inner() + } +} + +#[cfg(test)] +mod tests { + use crate::stream::{read, Event, Reader}; + use crate::Result; + use indoc::indoc; + + const CHILL_ANIMALS: &str = indoc! {r#" + // The comments here are meant to test comment parsing. + + // I think these animals are nice! + "ChillAnimals" + { + // These guys just seem happy to exist + "Animal" "Quokka" + + "Animal" + // Possums are underrated, by the way + "Possum" + + "Animal" "Capybara" // capybaras can coexist with anything + } + "#}; + + fn simple<'a, R: read::Read<'a>>(reader: &mut Reader) -> Result<()> { + // The whole document is read + assert_eq!(reader.read()?, Event::StartDocument); + assert_eq!(reader.read()?, Event::Scalar("ChillAnimals".into())); + assert_eq!(reader.read()?, Event::StartObject); + assert_eq!(reader.read()?, Event::Scalar("Animal".into())); + assert_eq!(reader.read()?, Event::Scalar("Quokka".into())); + assert_eq!(reader.read()?, Event::Scalar("Animal".into())); + assert_eq!(reader.read()?, Event::Scalar("Possum".into())); + assert_eq!(reader.read()?, Event::Scalar("Animal".into())); + assert_eq!(reader.read()?, Event::Scalar("Capybara".into())); + assert_eq!(reader.read()?, Event::EndObject); + assert_eq!(reader.read()?, Event::EndDocument); + + // Subsequent reads should still produce EndDocument + assert_eq!(reader.read()?, Event::EndDocument); + assert_eq!(reader.read()?, Event::EndDocument); + assert_eq!(reader.read()?, Event::EndDocument); + + Ok(()) + } + + #[test] + pub fn simple_str() -> Result<()> { + let mut reader = Reader::from_str(CHILL_ANIMALS); + simple(&mut reader) + } + + #[test] + pub fn simple_slice() -> Result<()> { + let mut reader = Reader::from_slice(CHILL_ANIMALS.as_bytes()); + simple(&mut reader) + } + + #[test] + pub fn simple_reader() -> Result<()> { + let mut reader = Reader::from_reader(CHILL_ANIMALS.as_bytes()); + simple(&mut reader) + } } diff --git a/src/stream/writer.rs b/src/stream/writer.rs index 4181367..b9ae45f 100644 --- a/src/stream/writer.rs +++ b/src/stream/writer.rs @@ -5,24 +5,75 @@ use std::io; /// A low-level streaming writer that generates text from a sequence of [`Event`]s representing the /// KeyValues format. pub struct Writer { - writer: W, + inner: W, } impl Writer { /// Creates a KeyValues writer from an [`io::Write`]. pub fn new(writer: W) -> Self { - Self { writer } + Self { inner: writer } } } impl Writer { - /// Writes the `event` to the output. + /// Writes the `event` to the output. After writing [`Event::EndDocument`], subsequent events + /// will be ignored. /// /// # Errors /// /// This function returns an error if an `io::Error` occurs. Note that this function does not /// check semantics (is this a valid document?). - pub fn write(event: Event<'_>) -> Result<()> { + pub fn write(&mut self, _event: Event<'_>) -> Result<()> { todo!() } + + /// Unwraps the inner writer from the `Writer`. + pub fn into_inner(self) -> W { + self.inner + } +} + +#[cfg(test)] +mod tests { + use crate::stream::{Event, Writer}; + use crate::Result; + use indoc::indoc; + + const CHILL_ANIMALS: &str = indoc! {r#" + "ChillAnimals" + { + "Animal" "Quokka" + "Animal" "Possum" + "Animal" "Capybara" + } + "#}; + + #[test] + fn simple() -> Result<()> { + let mut writer = Writer::new(Vec::new()); + + writer.write(Event::StartDocument)?; + writer.write(Event::Scalar("ChillAnimals".into()))?; + writer.write(Event::StartObject)?; + writer.write(Event::Scalar("Animal".into()))?; + writer.write(Event::Scalar("Quokka".into()))?; + writer.write(Event::Scalar("Animal".into()))?; + writer.write(Event::Scalar("Possum".into()))?; + writer.write(Event::Scalar("Animal".into()))?; + writer.write(Event::Scalar("Capybara".into()))?; + writer.write(Event::EndObject)?; + writer.write(Event::EndDocument)?; + + // Subsequent writes after EndDocument should be ignored + writer.write(Event::EndDocument)?; + writer.write(Event::EndDocument)?; + writer.write(Event::StartDocument)?; + writer.write(Event::Scalar("yoink".into()))?; + writer.write(Event::EndDocument)?; + + let str = String::from_utf8(writer.into_inner()).unwrap(); + assert_eq!(str, CHILL_ANIMALS); + + Ok(()) + } }