use std::fmt::{self, Write};
use std::mem;
use super::MAX_LINE_LEN;
pub struct EmailWriter<'a> {
writer: &'a mut dyn Write,
line_len: usize,
spaces: usize,
optional_breakpoint: bool,
can_go_to_new_line_now: bool,
}
impl<'a> EmailWriter<'a> {
pub fn new(
writer: &'a mut dyn Write,
line_len: usize,
spaces: usize,
optional_breakpoint: bool,
can_go_to_new_line_now: bool,
) -> Self {
Self {
writer,
line_len,
spaces,
optional_breakpoint,
can_go_to_new_line_now,
}
}
pub fn new_line(&mut self) -> fmt::Result {
self.writer.write_str("\r\n")?;
self.line_len = 0;
self.optional_breakpoint = false;
self.can_go_to_new_line_now = false;
Ok(())
}
pub(crate) fn new_line_and_space(&mut self) -> fmt::Result {
self.writer.write_str("\r\n ")?;
self.line_len = 1;
self.optional_breakpoint = false;
self.can_go_to_new_line_now = false;
Ok(())
}
#[cfg(not(tarpaulin_include))]
#[doc(hidden)]
#[deprecated(note = "Renamed to `new_line`", since = "0.1.2")]
pub fn new_line_no_initial_space(&mut self) -> fmt::Result {
self.new_line()
}
pub fn space(&mut self) {
self.spaces += 1;
}
pub fn optional_breakpoint(&mut self) {
debug_assert!(!self.optional_breakpoint);
self.optional_breakpoint = true;
}
pub fn line_len(&self) -> usize {
self.line_len
}
pub fn projected_line_len(&self) -> usize {
self.line_len + self.spaces + usize::from(self.optional_breakpoint)
}
pub fn folding<'b>(&'b mut self) -> FoldingEmailWriter<'a, 'b> {
FoldingEmailWriter { writer: self }
}
fn write_spaces(&mut self) -> fmt::Result {
while self.spaces > 0 {
self.writer.write_char(' ')?;
self.line_len += 1;
self.spaces -= 1;
}
Ok(())
}
}
impl<'a> Write for EmailWriter<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.spaces += usize::from(mem::take(&mut self.optional_breakpoint));
self.write_spaces()?;
let s_after = s.trim_end_matches(' ');
self.spaces += s.len() - s_after.len();
if !s_after.is_empty() {
self.writer.write_str(s_after)?;
self.line_len += s_after.len();
self.can_go_to_new_line_now = true;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
if c == ' ' {
self.spaces += 1;
} else {
self.spaces += usize::from(mem::take(&mut self.optional_breakpoint));
self.write_spaces()?;
self.can_go_to_new_line_now = true;
self.writer.write_char(c)?;
self.line_len += c.len_utf8();
}
Ok(())
}
}
impl<'a> Drop for EmailWriter<'a> {
fn drop(&mut self) {
let _ = self.write_spaces();
}
}
pub struct FoldingEmailWriter<'a, 'b> {
writer: &'b mut EmailWriter<'a>,
}
impl<'a, 'b> Write for FoldingEmailWriter<'a, 'b> {
fn write_str(&mut self, mut s: &str) -> fmt::Result {
while !s.is_empty() {
if s.starts_with(' ') {
self.writer.space();
s = &s[1..];
continue;
}
let (start, end) = s.find(' ').map_or((s, ""), |i| s.split_at(i));
match (
self.writer.can_go_to_new_line_now,
self.writer.spaces,
self.writer.optional_breakpoint,
(self.writer.projected_line_len() + start.len()) > MAX_LINE_LEN,
) {
(true, 1.., false, true) => {
self.writer.new_line()?;
}
(true, _, true, true) => {
self.writer.new_line_and_space()?;
}
_ => {}
}
self.writer.write_str(start)?;
s = end;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
if c == ' ' {
self.writer.spaces += 1;
} else {
self.write_str(c.encode_utf8(&mut [0u8; 4]))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn wrap_immediate() {
let mut s =
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 0, false, true);
for _ in 0..16 {
w.folding().write_str("0123456789").unwrap();
}
}
assert_eq!(
s,
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
);
}
#[test]
fn wrap_keeping_final_whitespace() {
let mut s = "Subject: AAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, false, true);
w.folding().write_str("12345 ").unwrap();
w.new_line().unwrap();
w.folding().write_str("12345").unwrap();
}
assert_eq!(s, concat!("Subject: AAAAAAAAAAAAAA 12345\r\n", " 12345"));
}
#[test]
fn catch_space() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, false, true);
w.folding().write_str("BBB ").unwrap();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn catch_spaces() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, false, true);
w.folding().write_str("BBB ").unwrap();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn explicit_space() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, false, true);
w.folding().write_str("BBB").unwrap();
w.space();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn explicit_spaces() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 1, false, true);
w.folding().write_str("BBB").unwrap();
w.space();
w.write_char(' ').unwrap();
w.space();
w.folding().write_str("CCCCCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBB\r\n",
" CCCCCCCCCCCCC"
)
);
}
#[test]
fn optional_breakpoint() {
let mut s = "Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
.to_owned();
let line_len = s.len();
{
let mut w = EmailWriter::new(&mut s, line_len, 0, false, true);
w.optional_breakpoint();
w.folding().write_str("BBBBBBBBBB").unwrap();
w.optional_breakpoint();
w.folding().write_str("CCCCCCCCCC").unwrap();
}
assert_eq!(
s,
concat!(
"Subject: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
" BBBBBBBBBB CCCCCCCCCC",
)
);
}
}