mod helper_functions;
use helper_functions::{get_index_spread, replace_matches};
use std::fmt;
enum AlignmentType {
Center,
Left,
Right,
}
pub trait TextAlign {
fn center_align(&self, width: usize) -> String;
fn left_align(&self, width: usize) -> String;
fn right_align(&self, width: usize) -> String;
fn justify(&self, width: usize) -> String;
fn dejustify(&self, spaces_after_punctuation: usize) -> String;
}
impl<T: AsRef<str> + fmt::Display> TextAlign for T {
fn center_align(&self, width: usize) -> String {
align(self, width, AlignmentType::Center)
}
fn left_align(&self, width: usize) -> String {
align(self, width, AlignmentType::Left)
}
fn right_align(&self, width: usize) -> String {
align(self, width, AlignmentType::Right)
}
fn justify(&self, width: usize) -> String {
let mut str_ref = self.as_ref();
let has_newline = str_ref.ends_with('\n');
if has_newline {
str_ref = str_ref.trim_end();
}
if width <= str_ref.chars().count() {
return self.to_string();
}
let words: Vec<&str> = str_ref.split_ascii_whitespace().collect();
let length_of_words: usize = words.iter().map(|word| word.chars().count()).sum();
let spaces_required = width - length_of_words;
let space_blocks_required = words.len() - 1;
let spaces_per_block = spaces_required / space_blocks_required;
let remaining_spaces = spaces_required % space_blocks_required;
let mut space_counts = vec![spaces_per_block; space_blocks_required];
if let Some(indices) = get_index_spread(remaining_spaces, space_counts.len()) {
for index in indices {
space_counts[index] += 1
}
}
space_counts.push(0);
assert_eq!(words.len(), space_counts.len());
let last_character = if has_newline { "\n" } else { "" };
let text: String = words
.iter()
.zip(space_counts.iter())
.map(|(word, space_count)| format!("{}{}", word, " ".repeat(*space_count)))
.collect::<Vec<String>>()
.join("");
format!("{}{}", text, last_character)
}
fn dejustify(&self, spaces_after_punctuation: usize) -> String {
let mut text = replace_matches(&self, "[' ']{2,}", " ");
if spaces_after_punctuation > 1 {
let padding_string = " ".repeat(spaces_after_punctuation);
let regular_expressions_and_replacements = [
(r"\.[' ']", format!(".{}", padding_string)),
(r"\?[' ']", format!("?{}", padding_string)),
(r"![' ']", format!("!{}", padding_string)),
];
for (regular_expression, replacement) in regular_expressions_and_replacements {
text = replace_matches(&text, regular_expression, &replacement);
}
}
text
}
}
fn align<T: AsRef<str> + fmt::Display>(
text: T,
mut width: usize,
alignment_type: AlignmentType,
) -> String {
let mut str_ref = text.as_ref().trim_start();
let has_newline = str_ref.ends_with('\n');
let text_length = str_ref.chars().count();
str_ref = str_ref.trim_end();
if has_newline {
width += 1;
}
if width <= text_length {
return text.to_string();
}
let spaces = width - text_length;
let last_character = if has_newline { "\n" } else { "" };
match alignment_type {
AlignmentType::Center => {
let left_padding_length = spaces / 2;
let right_padding_length = spaces - left_padding_length;
format!(
"{}{}{}{}",
" ".repeat(left_padding_length),
str_ref,
" ".repeat(right_padding_length),
last_character
)
}
AlignmentType::Left => {
format!("{}{}{}", str_ref, " ".repeat(spaces), last_character)
}
AlignmentType::Right => {
format!("{}{}{}", " ".repeat(spaces), str_ref, last_character)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_center_align_even_length_text() {
assert_eq!("hi".center_align(3), "hi ");
assert_eq!("hi".center_align(5), " hi ");
assert_eq!("hi".center_align(8), " hi ");
assert_eq!("hi\n".center_align(3), "hi \n");
assert_eq!("hi\n".center_align(5), " hi \n");
assert_eq!("hi\n".center_align(8), " hi \n");
}
#[test]
fn test_center_align_odd_length_text() {
assert_eq!("doggy".center_align(3), "doggy");
assert_eq!("doggy".center_align(8), " doggy ");
assert_eq!("doggy".center_align(9), " doggy ");
assert_eq!("doggy\n".center_align(3), "doggy\n");
assert_eq!("doggy\n".center_align(8), " doggy \n");
assert_eq!("doggy\n".center_align(9), " doggy \n");
}
#[test]
fn test_right_align() {
assert_eq!("hi".right_align(1), "hi");
assert_eq!("hi".right_align(3), " hi");
assert_eq!("hi".right_align(5), " hi");
assert_eq!("hi\n".right_align(1), "hi\n");
assert_eq!("hi\n".right_align(3), " hi\n");
assert_eq!("hi\n".right_align(5), " hi\n");
}
#[test]
fn test_left_align() {
assert_eq!("hi".left_align(1), "hi");
assert_eq!("hi".left_align(3), "hi ");
assert_eq!(" hi".left_align(5), "hi ");
assert_eq!("hi\n".left_align(1), "hi\n");
assert_eq!(" hi\n".left_align(3), "hi \n");
assert_eq!(" hi\n".left_align(5), "hi \n");
}
#[test]
fn test_justify_sentence() {
assert_eq!("Good dog".justify(1), "Good dog");
assert_eq!("Good dog".justify(8), "Good dog");
assert_eq!("Good dog".justify(9), "Good dog");
assert_eq!("Good dog".justify(10), "Good dog");
assert_eq!("Really good dog".justify(16), "Really good dog");
assert_eq!("Really good dog".justify(17), "Really good dog");
assert_eq!("Really good dog".justify(18), "Really good dog");
assert_eq!("Good dog\n".justify(1), "Good dog\n");
assert_eq!("Good dog\n".justify(8), "Good dog\n");
assert_eq!("Good dog\n".justify(9), "Good dog\n");
assert_eq!("Good dog\n".justify(10), "Good dog\n");
assert_eq!("Really good dog\n".justify(16), "Really good dog\n");
assert_eq!("Really good dog\n".justify(17), "Really good dog\n");
assert_eq!("Really good dog\n".justify(18), "Really good dog\n");
}
#[test]
fn test_dejustify() {
assert_eq!(
"Hi bud. How are you?".dejustify(1),
"Hi bud. How are you?"
);
assert_eq!(
"Hi bud. How are you?".dejustify(2),
"Hi bud. How are you?"
);
assert_eq!(
"Hi! Hey? Hello. Bud.".dejustify(2),
"Hi! Hey? Hello. Bud."
);
assert_eq!(
"Hi! Hey?\nHello. Bud.".dejustify(1),
"Hi! Hey?\nHello. Bud."
);
assert_eq!(
"Hi bud. How are you?\n".dejustify(1),
"Hi bud. How are you?\n"
);
assert_eq!(
"Hi bud. How are you?\n".dejustify(2),
"Hi bud. How are you?\n"
);
assert_eq!(
"Hi! Hey? Hello. Bud.\n".dejustify(2),
"Hi! Hey? Hello. Bud.\n"
);
assert_eq!(
"Hi! Hey?\nHello. Bud.\n".dejustify(1),
"Hi! Hey?\nHello. Bud.\n"
);
}
}