gecliht 0.2.0

A disparate collection of text manipulation and formatting algorithms.
Documentation
//! Functions for formatting or rearranging text.

/// Returns a string with the given items separated by 
/// commas and 'and' before the last item.
///
/// # Example
///
/// ```
/// assert_eq!(gecliht::words_with_commas(&["a", "b", "c"]), "a, b and c");
/// ```
///
pub fn words_with_commas (words: &[&str]) -> String {
    add_commas(words, false)
}

/// Returns a string with the given items separated by 
/// commas and 'and' before the last item. An 'Oxford comma' is 
/// inserted before 'and' for three or more items.
///
/// # Example
///
/// ```
/// assert_eq!(gecliht::words_with_oxford_commas(&["a", "b", "c"]), "a, b, and c");
/// ```
///
pub fn words_with_oxford_commas (words: &[&str]) -> String {
    add_commas(words, true)
}

// Returns a string with the given items separated by commas and 'and'
// before the last item. A flag is used to indicate if the Oxford comma
// should be added before 'and' in lists of 3 or more items.
fn add_commas (words: &[&str], do_oxford: bool) -> String {
    let mut result = "".to_string();
    let mut word = 0;

    while word < words.len() {
        result.push_str(words[word]);
        if words.len() - word > 2 {
            result.push_str(", ");
        } else if words.len() - word == 2 {
            if do_oxford && words.len() >= 3 { 
                result.push_str(",");
            }
            result.push_str(" and ");
        }
        word += 1;
    }

    result
}

/// Given a string and a target width, the algorithm returns
/// a vector of strings, each string representing a line 
/// with words wrapped based on the target width.
/// Word wrap uses a greedy algorithm, minimising the number 
/// of returned lines.
///
/// # Example
///
/// ```
/// assert_eq!(gecliht::word_wrap("abc def ghi", 8), vec!["abc def", "ghi"]);
/// assert_eq!(gecliht::word_wrap("abc def ghi", 4), vec!["abc", "def", "ghi"]);
/// ```
///                   
pub fn word_wrap (text: &str, width: usize) -> Vec<String> {
    let mut lines : Vec<String> = vec![];
    let mut words : Vec<&str> = text.split(char::is_whitespace).collect();

    let mut line_length = 0;
    let mut line = "".to_string();

    while !words.is_empty() {
        let word = words[0];

        if word.len() == 0 { // ignore empty words
            words.remove(0);
            continue; 
        } 

        if line_length + word.len() > width {
            if line.len() == 0 { // case where word exceeds line length
                lines.push(word.to_string());
                words.remove(0);
            } else { // word must go to next line, so finish this line
                lines.push(line);
            }
            line_length = 0;
            line = "".to_string ();
        } else { // add word to current line
            line_length += 1 + word.len();
            if line.len() > 0 {
                line.push_str(" ");
            }
            line.push_str(word);
            words.remove(0);
        }
    }
    // add the final line
    if line.len() > 0 {
        lines.push(line);
    }

    lines
}

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

    #[test]
    fn test_words_with_commas () {
        assert_eq!("", words_with_commas(&[]));
        assert_eq!("a", words_with_commas(&["a"]));
        assert_eq!("a and b", words_with_commas(&["a", "b"]));
        assert_eq!("a, b and c", words_with_commas(&["a", "b", "c"]));
    }

    #[test]
    fn test_words_with_oxford_commas () {
        assert_eq!("", words_with_oxford_commas(&[]));
        assert_eq!("a", words_with_oxford_commas(&["a"]));
        assert_eq!("a and b", words_with_oxford_commas(&["a", "b"]));
        assert_eq!("a, b, and c", words_with_oxford_commas(&["a", "b", "c"]));
    }

    #[test]
    fn test_word_wrap () {
        assert_eq!(vec!["abc"], 
                   word_wrap("abc", 30));

        assert_eq!(vec!["abc def ghi"], 
                   word_wrap("abc def ghi", 30));

        assert_eq!(vec!["abc def", "ghi"], 
                   word_wrap("abc def ghi", 8));

        assert_eq!(vec!["abc", "def", "ghi"], 
                   word_wrap("abc def ghi", 4));
    }
}