1use crate::error::{Result, WritingAnalysisError};
2
3#[derive(Debug, Clone, PartialEq)]
5pub struct ClicheResult {
6 pub instances: Vec<ClicheInstance>,
8 pub count: usize,
10}
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct ClicheInstance {
15 pub phrase: String,
17 pub offset: usize,
19 pub canonical: String,
21}
22
23static CLICHES: &[&str] = &[
24 "a chip on your shoulder",
25 "a dime a dozen",
26 "a picture is worth a thousand words",
27 "absence makes the heart grow fonder",
28 "actions speak louder than words",
29 "add insult to injury",
30 "against all odds",
31 "all in a day's work",
32 "all that glitters is not gold",
33 "at the drop of a hat",
34 "at the end of the day",
35 "back to the drawing board",
36 "barking up the wrong tree",
37 "beat a dead horse",
38 "beat around the bush",
39 "better late than never",
40 "better safe than sorry",
41 "between a rock and a hard place",
42 "bite the bullet",
43 "bite the dust",
44 "blood is thicker than water",
45 "break the ice",
46 "burning the midnight oil",
47 "by the skin of your teeth",
48 "calm before the storm",
49 "cost an arm and a leg",
50 "cut to the chase",
51 "don't cry over spilled milk",
52 "don't put all your eggs in one basket",
53 "drastic times call for drastic measures",
54 "easier said than done",
55 "easy as pie",
56 "every cloud has a silver lining",
57 "fall head over heels",
58 "few and far between",
59 "fit as a fiddle",
60 "go the extra mile",
61 "good things come to those who wait",
62 "hit the nail on the head",
63 "ignorance is bliss",
64 "in the heat of the moment",
65 "it takes two to tango",
66 "keep your chin up",
67 "kill two birds with one stone",
68 "last but not least",
69 "leave no stone unturned",
70 "let the cat out of the bag",
71 "like riding a bicycle",
72 "live and learn",
73 "look before you leap",
74 "more than meets the eye",
75 "needle in a haystack",
76 "no pain no gain",
77 "once in a blue moon",
78 "only time will tell",
79 "out of the frying pan into the fire",
80 "par for the course",
81 "play it by ear",
82 "pulling someone's leg",
83 "read between the lines",
84 "reinvent the wheel",
85 "see eye to eye",
86 "shoot for the moon",
87 "sleep on it",
88 "speak of the devil",
89 "stand the test of time",
90 "take it with a grain of salt",
91 "the apple doesn't fall far from the tree",
92 "the ball is in your court",
93 "the best of both worlds",
94 "the best thing since sliced bread",
95 "the bigger they are the harder they fall",
96 "the early bird catches the worm",
97 "the elephant in the room",
98 "the last straw",
99 "the tip of the iceberg",
100 "the whole nine yards",
101 "think outside the box",
102 "time flies when you're having fun",
103 "time is money",
104 "to each his own",
105 "under the weather",
106 "when it rains it pours",
107 "you can't judge a book by its cover",
108];
109
110pub fn detect_cliches(text: &str) -> Result<ClicheResult> {
112 if text.trim().is_empty() {
113 return Err(WritingAnalysisError::EmptyText);
114 }
115
116 let lower = text.to_lowercase();
117 let mut instances = Vec::new();
118
119 for &cliche in CLICHES {
120 let mut start = 0;
121 while let Some(pos) = lower[start..].find(cliche) {
122 let offset = start + pos;
123 instances.push(ClicheInstance {
124 phrase: text[offset..offset + cliche.len()].to_string(),
125 offset,
126 canonical: cliche.to_string(),
127 });
128 start = offset + cliche.len();
129 }
130 }
131
132 instances.sort_by_key(|i| i.offset);
134
135 let count = instances.len();
136 Ok(ClicheResult { instances, count })
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn detect_single_cliche() {
145 let result = detect_cliches("At the end of the day, we need results.").unwrap();
146 assert_eq!(result.count, 1);
147 assert_eq!(result.instances[0].canonical, "at the end of the day");
148 }
149
150 #[test]
151 fn detect_multiple_cliches() {
152 let text = "It was easier said than done, but better late than never.";
153 let result = detect_cliches(text).unwrap();
154 assert_eq!(result.count, 2);
155 }
156
157 #[test]
158 fn no_cliches() {
159 let result =
160 detect_cliches("The quantum processor achieved remarkable throughput.").unwrap();
161 assert_eq!(result.count, 0);
162 }
163
164 #[test]
165 fn case_insensitive_match() {
166 let result = detect_cliches("Think Outside The Box to solve this.").unwrap();
167 assert_eq!(result.count, 1);
168 }
169
170 #[test]
171 fn empty_text_error() {
172 let result = detect_cliches("");
173 assert!(result.is_err());
174 }
175}