summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam T. Carpenter <atc@53hor.net>2021-03-02 17:04:18 -0500
committerAdam T. Carpenter <atc@53hor.net>2021-03-02 17:04:18 -0500
commit41439f0ec165cf24989f5b1f2bd30a6669ee4d0f (patch)
tree383d6cad0534a1e9d52caa554bc0069c6172ffdd
downloadtitler-41439f0ec165cf24989f5b1f2bd30a6669ee4d0f.tar.xz
titler-41439f0ec165cf24989f5b1f2bd30a6669ee4d0f.zip
basic functionality done, tests passing
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock5
-rw-r--r--Cargo.toml9
-rw-r--r--src/main.rs79
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")
+ );
+ }
+}