1use chrono::{Local, Timelike};
2use rand::seq::SliceRandom;
3
4const ANYTIME: &[&str] = &[
5 "Hey, it's {name}",
6 "Hi there, {name} here",
7 "Hello, this is {name}",
8 "{name} here, what's up?",
9];
10
11const MORNING: &[&str] = &["Good morning, {name} here", "Morning! It's {name}"];
12
13const AFTERNOON: &[&str] = &[
14 "Good afternoon, it's {name}",
15 "Hey, good afternoon, {name} here",
16];
17
18const EVENING: &[&str] = &["Good evening, this is {name}", "Evening! {name} here"];
19
20const NIGHT: &[&str] = &[
21 "Hey, it's late, but {name}'s here",
22 "{name} here, burning the midnight oil?",
23];
24
25fn time_pool(hour: u32) -> &'static [&'static str] {
26 match hour {
27 5..=11 => MORNING,
28 12..=16 => AFTERNOON,
29 17..=20 => EVENING,
30 _ => NIGHT,
31 }
32}
33
34pub fn select_greeting(name: &str) -> String {
39 let hour = Local::now().hour();
40 select_greeting_for_hour(name, hour)
41}
42
43fn select_greeting_for_hour(name: &str, hour: u32) -> String {
44 let time_specific = time_pool(hour);
45 let mut pool: Vec<&str> = Vec::with_capacity(ANYTIME.len() + time_specific.len());
46 pool.extend_from_slice(ANYTIME);
47 pool.extend_from_slice(time_specific);
48
49 let mut rng = rand::thread_rng();
50 let template = pool.choose(&mut rng).unwrap_or(&ANYTIME[0]);
51 template.replace("{name}", name)
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn greeting_contains_name() {
60 let greeting = select_greeting_for_hour("TestBot", 10);
61 assert!(
62 greeting.contains("TestBot"),
63 "greeting should contain entity name: {greeting}"
64 );
65 }
66
67 #[test]
68 fn greeting_no_placeholder_leftover() {
69 for hour in 0..24 {
70 let greeting = select_greeting_for_hour("Echo", hour);
71 assert!(
72 !greeting.contains("{name}"),
73 "placeholder not replaced at hour {hour}: {greeting}"
74 );
75 }
76 }
77
78 #[test]
79 fn greeting_never_empty() {
80 for hour in 0..24 {
81 let greeting = select_greeting_for_hour("X", hour);
82 assert!(!greeting.is_empty(), "empty greeting at hour {hour}");
83 }
84 }
85
86 #[test]
87 fn time_pool_morning() {
88 let pool = time_pool(8);
89 assert!(pool
90 .iter()
91 .any(|g| g.contains("morning") || g.contains("Morning")));
92 }
93
94 #[test]
95 fn time_pool_afternoon() {
96 let pool = time_pool(14);
97 assert!(pool.iter().any(|g| g.contains("afternoon")));
98 }
99
100 #[test]
101 fn time_pool_evening() {
102 let pool = time_pool(19);
103 assert!(pool
104 .iter()
105 .any(|g| g.contains("evening") || g.contains("Evening")));
106 }
107
108 #[test]
109 fn time_pool_night() {
110 let pool = time_pool(23);
111 assert!(pool
112 .iter()
113 .any(|g| g.contains("late") || g.contains("midnight")));
114 }
115
116 #[test]
117 fn time_pool_boundaries() {
118 assert_eq!(time_pool(4), NIGHT);
120 assert_eq!(time_pool(5), MORNING);
121 assert_eq!(time_pool(11), MORNING);
122 assert_eq!(time_pool(12), AFTERNOON);
123 assert_eq!(time_pool(16), AFTERNOON);
124 assert_eq!(time_pool(17), EVENING);
125 assert_eq!(time_pool(20), EVENING);
126 assert_eq!(time_pool(21), NIGHT);
127 }
128}