lcowsay 0.3.3

Cowsay, but combined with lolcat.
Documentation
use hyphenation::{Language, Load, Standard};
use std::fmt::Write;
use unicode_width::UnicodeWidthStr;

pub fn bubble(text: &str, width: usize) -> String {
    let text = text.replace("\t", "    ");
    let hyphenator = Standard::from_embedded(Language::EnglishUS).unwrap();
    let options = textwrap::Options::new(width).word_splitter(hyphenator);
    let text = textwrap::wrap(&text, options);

    let line_count = text.len();
    let line_lengths: Vec<usize> = text.iter().map(|i| i.width()).collect();

    let max_length = line_lengths.iter().max().unwrap();

    let mut out = String::new();
    writeln!(out, " {:_<1$}", "", max_length + 2).unwrap();
    if line_count == 1 {
        writeln!(out, "< {} >", &text[0]).unwrap();
    } else {
        writeln!(out, "/ {:1$} \\", &text[0], max_length).unwrap();
        for (i, line) in text.iter().take(line_count - 1).skip(1).enumerate() {
            let i = i + 1;
            let spaces_count = max_length - line_lengths[i];
            writeln!(out, "| {}{} |", &line, " ".repeat(spaces_count)).unwrap();
        }
        writeln!(out, "\\ {:1$} /", &text[line_count - 1], max_length).unwrap();
    }
    write!(out, " {:-<1$}", "", max_length + 2).unwrap();

    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bubble_sl() {
        let test = "foobar";
        let bubble_t = " ________\n< foobar >\n --------";

        assert_eq!(bubble(test, 20), bubble_t);
    }

    #[test]
    fn test_bubble_break() {
        let test = "foobar";
        let bubble_t = " ______\n/ foo- \\\n\\ bar  /\n ------";

        assert_eq!(bubble(test, 5), bubble_t);
    }

    #[test]
    fn test_bubble_ml() {
        let test = "mul\ntiple\nlines";
        let bubble_t = " _______\n/ mul   \\\n| tiple |\n\\ lines /\n -------";

        assert_eq!(bubble(test, 10), bubble_t);
    }

    #[test]
    fn test_bubble_tab() {
        let test = "\t.";
        let bubble_t = " _______\n<     . >\n -------";

        assert_eq!(bubble(test, 10), bubble_t);
    }
}