summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam T. Carpenter <atc@53hor.net>2021-03-04 21:44:24 -0500
committerAdam T. Carpenter <atc@53hor.net>2021-03-04 21:44:24 -0500
commit2aaa4a65710e4f5b293d3682e09c02b70cd295ed (patch)
tree4cd7e5cbd2f36608a89e2a187a4b06fb6fdb2941
parentb230c913ede87dd5ea0f445cb35f63d714037436 (diff)
downloadtitler-2aaa4a65710e4f5b293d3682e09c02b70cd295ed.tar.xz
titler-2aaa4a65710e4f5b293d3682e09c02b70cd295ed.zip
faster exception lookups, slightly fuglier function signatures
-rw-r--r--src/main.rs78
1 files changed, 54 insertions, 24 deletions
diff --git a/src/main.rs b/src/main.rs
index b060ed1..c9f03c3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,9 @@
+use std::collections::HashSet;
use std::env::args;
use std::io::{stdin, BufRead, BufReader};
+const HELP_MSG: &str = "Title-Case Tool.\n-i | --ignore [word...]\tIgnores a list of words that should not be altered.";
+
/// Words that will not be capitalized unless they are at the start or end of a title.
const EXCEPTIONS: &[&str] = &[
"vs", "above", "across", "against", "at", "between", "by", "along", "among", "down", "in",
@@ -28,7 +31,7 @@ fn main() {
break;
}
"--help" | "-h" => {
- println!("Title-Case Tool.\n-i | --ignore [word...]\tIgnores a list of words that should not be altered.");
+ println!("{}", HELP_MSG);
}
unrecognized => {
panic!(format!("Unrecognized option: {}", unrecognized));
@@ -36,40 +39,54 @@ fn main() {
}
}
+ // gather exceptions
+ let exceptions: HashSet<&'static str> = EXCEPTIONS.iter().cloned().collect();
+
+ // read input and correct lines
for line in BufReader::new(stdin()).lines() {
let line = line.expect("IO error");
- println!("{}", correct_line(&line, &ignorables));
+ println!("{}", correct_line(&line, &ignorables, &exceptions));
}
}
/// Corrects a single line (title) based on placement and exceptions.
-fn correct_line(line: &str, ignorables: &[String]) -> String {
+fn correct_line(line: &str, ignorables: &[String], exceptions: &HashSet<&'static str>) -> String {
let mut line = line.split_whitespace().peekable();
- let mut result = Vec::new();
+ let mut result = String::new();
if let Some(first) = line.next() {
// always correct the first word
- result.push(correct_word(first, true, ignorables));
+ result.push_str(&correct_word(first, true, ignorables, exceptions));
} else {
- // handle empty lines
- return String::new();
+ // skip empty lines
+ return result;
}
// handle every other word
while let Some(word) = line.next() {
- result.push(correct_word(word, line.peek().is_none(), ignorables));
+ result.push(' ');
+ result.push_str(&correct_word(
+ word,
+ line.peek().is_none(),
+ ignorables,
+ exceptions,
+ ));
}
- // join words into title
- result.join(" ")
+ result
}
/// Corrects a single word based on exceptions and ignorables.
-fn correct_word(word: &str, first_or_last: bool, ignorables: &[String]) -> String {
+fn correct_word(
+ word: &str,
+ first_or_last: bool,
+ ignorables: &[String],
+ exceptions: &HashSet<&'static str>,
+) -> String {
if ignorables.iter().any(|s| s == word) {
// leave ignorables untouched
word.to_owned()
- } else if first_or_last || !EXCEPTIONS.iter().any(|s| **s == word.to_lowercase()) {
+ } else if first_or_last || !exceptions.contains(&word.to_lowercase().as_str()) {
// capitalize words that are first, last, or aren't exceptions
capitalize_word(&word.to_lowercase())
} else {
@@ -78,9 +95,6 @@ fn correct_word(word: &str, first_or_last: bool, ignorables: &[String]) -> Strin
}
}
-// correct with just ignorables
-// correct with ignorables and exceptions
-
/// Capitalizes the first letter of a single word.
fn capitalize_word(word: &str) -> String {
let mut chars = word.chars();
@@ -100,12 +114,24 @@ mod tests {
#[test]
fn test_correct_word() {
- assert_eq!(correct_word("Like", false, &[]), String::from("like"));
- assert_eq!(correct_word("like", false, &[]), String::from("like"));
- assert_eq!(correct_word("like", true, &[]), String::from("Like"));
- assert_eq!(correct_word("liKe", false, &[]), String::from("like"));
assert_eq!(
- correct_word("computers", false, &[]),
+ correct_word("Like", false, &[], &EXCEPTIONS.iter().cloned().collect()),
+ String::from("like")
+ );
+ assert_eq!(
+ correct_word("like", false, &[], &EXCEPTIONS.iter().cloned().collect()),
+ String::from("like")
+ );
+ assert_eq!(
+ correct_word("like", true, &[], &EXCEPTIONS.iter().cloned().collect()),
+ String::from("Like")
+ );
+ assert_eq!(
+ correct_word("liKe", false, &[], &EXCEPTIONS.iter().cloned().collect()),
+ String::from("like")
+ );
+ assert_eq!(
+ correct_word("computers", false, &[], &HashSet::new()),
String::from("Computers")
);
}
@@ -113,11 +139,15 @@ mod tests {
#[test]
fn test_correct_line() {
assert_eq!(
- correct_line("this is a test", &[]),
+ correct_line("this is a test", &[], &EXCEPTIONS.iter().cloned().collect()),
String::from("This Is a Test")
);
assert_eq!(
- correct_line("similEs: lIke Or As", &[]),
+ correct_line(
+ "similEs: lIke Or As",
+ &[],
+ &EXCEPTIONS.iter().cloned().collect()
+ ),
String::from("Similes: like or As")
);
}
@@ -125,13 +155,13 @@ mod tests {
#[test]
fn test_proper_nouns_acronyms() {
assert_eq!(
- correct_line("FreeBSD", &[String::from("FreeBSD")]),
+ correct_line("FreeBSD", &[String::from("FreeBSD")], &HashSet::new()),
String::from("FreeBSD")
);
}
#[test]
fn test_empty_lines() {
- assert_eq!(correct_line("", &[]), String::from(""));
+ assert_eq!(correct_line("", &[], &HashSet::new()), String::from(""),);
}
}