use crate::console::Console;
use std::io;
fn refill(paragraph: &str, width: usize) -> Vec<String> {
if paragraph.is_empty() {
return vec!["".to_owned()];
}
let mut lines = vec![];
let mut line = String::new();
for word in paragraph.split_whitespace() {
if !line.is_empty() {
let spaces = if line.ends_with('.') {
let first = word.chars().next().expect("Words cannot be empty");
if first == first.to_ascii_uppercase() {
2
} else {
1
}
} else {
1
};
if (line.len() + word.len() + spaces) >= width {
lines.push(line);
line = String::new();
} else {
for _ in 0..spaces {
line.push(' ');
}
}
}
line.push_str(word);
}
if !line.is_empty() {
lines.push(line);
}
lines
}
pub fn refill_and_print<S: AsRef<str>, P: IntoIterator<Item = S>>(
console: &mut dyn Console,
paragraphs: P,
indent: &str,
) -> io::Result<()> {
let mut first = true;
for paragraph in paragraphs {
let paragraph = paragraph.as_ref();
if !first {
console.print("")?;
}
first = false;
let mut extra_indent = String::new();
for ch in paragraph.chars() {
if ch.is_whitespace() {
extra_indent.push(' ');
} else {
break;
}
}
let size = console.size_chars()?;
let lines = refill(paragraph, usize::from(size.x) - 4 - indent.len() - extra_indent.len());
for line in lines {
if line.is_empty() {
console.print("")?;
} else {
console.print(&format!("{}{}{}", indent, extra_indent, line))?;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::console::CharsXY;
use crate::testutils::{CapturedOut, MockConsole};
#[test]
fn test_refill_empty() {
assert_eq!(&[""], refill("", 0).as_slice());
assert_eq!(&[""], refill("", 10).as_slice());
}
#[test]
fn test_refill_nothing_fits() {
assert_eq!(&["this", "is", "some", "text"], refill("this is some text", 0).as_slice());
assert_eq!(&["this", "is", "some", "text"], refill("this is some text", 1).as_slice());
}
#[test]
fn test_refill_some_lines() {
assert_eq!(
&["this is a piece", "of text with", "a-fictitious-very-long-word", "within it"],
refill("this is a piece of text with a-fictitious-very-long-word within it", 16)
.as_slice()
);
}
#[test]
fn test_refill_reformats_periods() {
assert_eq!(&["foo. bar. baz."], refill("foo. bar. baz.", 100).as_slice());
assert_eq!(&["foo. Bar. baz."], refill("foo. Bar. baz.", 100).as_slice());
assert_eq!(&["[some .. range]"], refill("[some .. range]", 100).as_slice());
}
#[test]
fn test_refill_and_print_empty() {
let mut console = MockConsole::default();
let paragraphs: &[&str] = &[];
refill_and_print(&mut console, paragraphs, " ").unwrap();
assert!(console.captured_out().is_empty());
}
#[test]
fn test_refill_and_print_one() {
let mut console = MockConsole::default();
let paragraphs = &["First paragraph"];
refill_and_print(&mut console, paragraphs, " ").unwrap();
assert_eq!(&[CapturedOut::Print(" First paragraph".to_owned())], console.captured_out());
}
#[test]
fn test_refill_and_print_multiple() {
let mut console = MockConsole::default();
let paragraphs = &["First paragraph", "Second", "Third. The. end."];
refill_and_print(&mut console, paragraphs, " ").unwrap();
assert_eq!(
&[
CapturedOut::Print(" First paragraph".to_owned()),
CapturedOut::Print("".to_owned()),
CapturedOut::Print(" Second".to_owned()),
CapturedOut::Print("".to_owned()),
CapturedOut::Print(" Third. The. end.".to_owned()),
],
console.captured_out()
);
}
#[test]
fn test_refill_and_print_multiple_with_extra_indent() {
let mut console = MockConsole::default();
console.set_size_chars(CharsXY { x: 30, y: 30 });
let paragraphs = &[
"First paragraph that is somewhat long",
" The second paragraph contains an extra indent",
"Third. The. end.",
];
refill_and_print(&mut console, paragraphs, " ").unwrap();
assert_eq!(
&[
CapturedOut::Print(" First paragraph that".to_owned()),
CapturedOut::Print(" is somewhat long".to_owned()),
CapturedOut::Print("".to_owned()),
CapturedOut::Print(" The second".to_owned()),
CapturedOut::Print(" paragraph contains".to_owned()),
CapturedOut::Print(" an extra indent".to_owned()),
CapturedOut::Print("".to_owned()),
CapturedOut::Print(" Third. The. end.".to_owned()),
],
console.captured_out()
);
}
}