summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs79
1 files changed, 79 insertions, 0 deletions
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")
+ );
+ }
+}