diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 5 | ||||
-rw-r--r-- | Cargo.toml | 9 | ||||
-rw-r--r-- | src/main.rs | 79 |
4 files changed, 94 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..438953f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "titler" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..af5569e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "titler" +version = "0.1.0" +authors = ["Adam T. Carpenter <atc@53hor.net>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bacaef9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,79 @@ +use std::io::stdin; +use std::io::BufRead; +use std::io::BufReader; + +/// Words that will not be capitalized unless they are at the start or end of a title. +static EXCEPTIONS: [&str; 36] = [ + "above", "across", "against", "at", "between", "by", "along", "among", "down", "in", "around", + "of", "off", "on", "to", "with", "before", "behind", "below", "beneath", "down", "from", + "near", "toward", "upon", "within", "a", "an", "the", "and", "but", "for", "or", "nor", "yet", + "so", +]; + +/// Here are the rules: +/// +/// - Every line is its own title. No multi-line titles. +/// - All words are capitalized, except... +/// - Words are not capitalized if they are EXCEPTIONS, unless... +/// - Words are either the first or the last word. +fn main() { + for line in BufReader::new(stdin()).lines() { + println!("{}", capitalize_line(&line.expect("IO error"))); + } +} + +/// Capitalizes a single line (title) based on placement and exceptions. +fn capitalize_line(line: &str) -> String { + let mut line = line.split_whitespace().map(str::to_lowercase).peekable(); + let mut result: Vec<String> = Vec::new(); + + // handle first word + let first = line.next().expect("No words on line"); + result.push(capitalize_word(&first)); + + while let Some(word) = line.next() { + let word = if line.peek().is_none() || !EXCEPTIONS.contains(&word.as_str()) { + // capitalize words that are last or aren't in exceptions + capitalize_word(&word) + } else { + // ignore the rest + word + }; + + result.push(word); + } + + // join words into title + result.join(" ") +} + +/// Capitalizes a single word. +fn capitalize_word(word: &str) -> String { + let mut chars = word.chars(); + let first = chars.next().unwrap(); + format!("{}{}", first.to_uppercase(), chars.collect::<String>()) +} + +#[cfg(test)] +mod tests { + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + + #[test] + fn test_capitalize_word() { + assert_eq!(capitalize_word("word"), String::from("Word")); + assert_eq!(capitalize_word("as"), String::from("As")); + } + + #[test] + fn test_capitalize_line() { + assert_eq!( + capitalize_line("this is a test"), + String::from("This Is a Test") + ); + assert_eq!( + capitalize_line("similEs: lIke Or As"), + String::from("Similes: Like or As") + ); + } +} |