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

Indentation for AsyncWrite trait implementations #606

Merged
merged 3 commits into from Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changelog.md
Expand Up @@ -14,12 +14,14 @@

- [#601]: Add `serde_helper` module to the crate root with some useful utility
functions and document using of enum's unit variants as a text content of element.
- [#606]: Implement indentation for `AsyncWrite` trait implementations.

### Bug Fixes

### Misc Changes

[#601]: https://github.com/tafia/quick-xml/pull/601
[#606]: https://github.com/tafia/quick-xml/pull/606


## 0.28.2 -- 2023-04-12
Expand Down
16 changes: 8 additions & 8 deletions src/writer.rs
Expand Up @@ -71,6 +71,14 @@ impl<W> Writer<W> {
}
}

/// Creates a `Writer` with configured indents from a generic writer.
pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer<W> {
Writer {
writer: inner,
indent: Some(Indentation::new(indent_char, indent_size)),
}
}

/// Consumes this `Writer`, returning the underlying writer.
pub fn into_inner(self) -> W {
self.writer
Expand All @@ -88,14 +96,6 @@ impl<W> Writer<W> {
}

impl<W: Write> Writer<W> {
/// Creates a `Writer` with configured whitespace indents from a generic writer.
pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer<W> {
Writer {
writer: inner,
indent: Some(Indentation::new(indent_char, indent_size)),
}
}

/// Write a [Byte-Order-Mark] character to the document.
///
/// # Example
Expand Down
231 changes: 227 additions & 4 deletions src/writer/async_tokio.rs
Expand Up @@ -7,13 +7,29 @@ use crate::Writer;
impl<W: AsyncWrite + Unpin> Writer<W> {
/// Writes the given event to the underlying writer. Async version of [`Writer::write_event`].
pub async fn write_event_async<'a, E: AsRef<Event<'a>>>(&mut self, event: E) -> Result<()> {
match *event.as_ref() {
Event::Start(ref e) => self.write_wrapped_async(b"<", e, b">").await,
Event::End(ref e) => self.write_wrapped_async(b"</", e, b">").await,
let mut next_should_line_break = true;
let result = match *event.as_ref() {
Event::Start(ref e) => {
let result = self.write_wrapped_async(b"<", e, b">").await;
if let Some(i) = self.indent.as_mut() {
i.grow();
}
result
}
Event::End(ref e) => {
if let Some(i) = self.indent.as_mut() {
i.shrink();
}
self.write_wrapped_async(b"</", e, b">").await
}
Event::Empty(ref e) => self.write_wrapped_async(b"<", e, b"/>").await,
Event::Text(ref e) => self.write_async(e).await,
Event::Text(ref e) => {
next_should_line_break = false;
self.write_async(e).await
}
Event::Comment(ref e) => self.write_wrapped_async(b"<!--", e, b"-->").await,
Event::CData(ref e) => {
next_should_line_break = false;
self.write_async(b"<![CDATA[").await?;
self.write_async(e).await?;
self.write_async(b"]]>").await
Expand All @@ -22,7 +38,23 @@ impl<W: AsyncWrite + Unpin> Writer<W> {
Event::PI(ref e) => self.write_wrapped_async(b"<?", e, b"?>").await,
Event::DocType(ref e) => self.write_wrapped_async(b"<!DOCTYPE ", e, b">").await,
Event::Eof => Ok(()),
};
if let Some(i) = self.indent.as_mut() {
i.should_line_break = next_should_line_break;
}
result
}

/// Manually write a newline and indentation at the proper level. Async version of
/// [`Writer::write_indent`].
///
/// This method will do nothing if `Writer` was not constructed with [`Writer::new_with_indent`].
pub async fn write_indent_async(&mut self) -> Result<()> {
if let Some(ref i) = self.indent {
self.writer.write_all(b"\n").await?;
self.writer.write_all(i.current()).await?;
}
Ok(())
}

#[inline]
Expand All @@ -37,6 +69,12 @@ impl<W: AsyncWrite + Unpin> Writer<W> {
value: &[u8],
after: &[u8],
) -> Result<()> {
if let Some(ref i) = self.indent {
if i.should_line_break {
self.writer.write_all(b"\n").await?;
self.writer.write_all(i.current()).await?;
}
}
self.write_async(before).await?;
self.write_async(value).await?;
self.write_async(after).await?;
Expand Down Expand Up @@ -117,3 +155,188 @@ mod tests {
);
}
}

#[cfg(test)]
mod indentation_async {
use super::*;
use crate::events::*;
use pretty_assertions::assert_eq;

#[tokio::test]
async fn self_closed() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let tag = BytesStart::new("self-closed")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
writer
.write_event_async(Event::Empty(tag))
.await
.expect("write tag failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<self-closed attr1="value1" attr2="value2"/>"#
);
}

#[tokio::test]
async fn empty_paired() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let start = BytesStart::new("paired")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
let end = start.to_end();
writer
.write_event_async(Event::Start(start.clone()))
.await
.expect("write start tag failed");
writer
.write_event_async(Event::End(end))
.await
.expect("write end tag failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<paired attr1="value1" attr2="value2">
</paired>"#
);
}

#[tokio::test]
async fn paired_with_inner() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let start = BytesStart::new("paired")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
let end = start.to_end();
let inner = BytesStart::new("inner");

writer
.write_event_async(Event::Start(start.clone()))
.await
.expect("write start tag failed");
writer
.write_event_async(Event::Empty(inner))
.await
.expect("write inner tag failed");
writer
.write_event_async(Event::End(end))
.await
.expect("write end tag failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<paired attr1="value1" attr2="value2">
<inner/>
</paired>"#
);
}

#[tokio::test]
async fn paired_with_text() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let start = BytesStart::new("paired")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
let end = start.to_end();
let text = BytesText::new("text");

writer
.write_event_async(Event::Start(start.clone()))
.await
.expect("write start tag failed");
writer
.write_event_async(Event::Text(text))
.await
.expect("write text failed");
writer
.write_event_async(Event::End(end))
.await
.expect("write end tag failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<paired attr1="value1" attr2="value2">text</paired>"#
);
}

#[tokio::test]
async fn mixed_content() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let start = BytesStart::new("paired")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
let end = start.to_end();
let text = BytesText::new("text");
let inner = BytesStart::new("inner");

writer
.write_event_async(Event::Start(start.clone()))
.await
.expect("write start tag failed");
writer
.write_event_async(Event::Text(text))
.await
.expect("write text failed");
writer
.write_event_async(Event::Empty(inner))
.await
.expect("write inner tag failed");
writer
.write_event_async(Event::End(end))
.await
.expect("write end tag failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<paired attr1="value1" attr2="value2">text<inner/>
</paired>"#
);
}

#[tokio::test]
async fn nested() {
let mut buffer = Vec::new();
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);

let start = BytesStart::new("paired")
.with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter());
let end = start.to_end();
let inner = BytesStart::new("inner");

writer
.write_event_async(Event::Start(start.clone()))
.await
.expect("write start 1 tag failed");
writer
.write_event_async(Event::Start(start.clone()))
.await
.expect("write start 2 tag failed");
writer
.write_event_async(Event::Empty(inner))
.await
.expect("write inner tag failed");
writer
.write_event_async(Event::End(end.clone()))
.await
.expect("write end tag 2 failed");
writer
.write_event_async(Event::End(end))
.await
.expect("write end tag 1 failed");

assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<paired attr1="value1" attr2="value2">
<paired attr1="value1" attr2="value2">
<inner/>
</paired>
</paired>"#
);
}
}