1#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum Notice {
17 Info(String),
18 Error(String),
19 Synced {
22 deleted: usize,
23 updated: usize,
24 },
25 NewMail(String),
29}
30
31impl Notice {
32 pub fn is_error(&self) -> bool {
35 matches!(self, Notice::Error(_))
36 }
37
38 pub fn is_new_mail(&self) -> bool {
40 matches!(self, Notice::NewMail(_))
41 }
42
43 pub fn text(&self) -> String {
45 match self {
46 Notice::Info(msg) | Notice::Error(msg) | Notice::NewMail(msg) => msg.clone(),
47 Notice::Synced { deleted, updated } => {
48 format!("synced: {deleted} deleted, {updated} updated")
49 }
50 }
51 }
52}
53
54pub trait NoticeSink: Send {
57 fn notice(&mut self, notice: Notice);
58 fn latest(&self) -> Option<&Notice>;
60 fn clear(&mut self);
62}
63
64#[derive(Debug, Clone, Default)]
68pub struct Log(std::sync::Arc<std::sync::Mutex<Vec<Notice>>>);
69
70impl Log {
71 pub fn notices(&self) -> Vec<Notice> {
73 self.0.lock().unwrap().clone()
74 }
75
76 pub fn last_text(&self) -> String {
78 self.0
79 .lock()
80 .unwrap()
81 .last()
82 .map(Notice::text)
83 .unwrap_or_default()
84 }
85
86 pub fn said(&self, needle: &str) -> bool {
88 self.0
89 .lock()
90 .unwrap()
91 .iter()
92 .any(|n| n.text().contains(needle))
93 }
94}
95
96impl NoticeSink for Log {
97 fn notice(&mut self, notice: Notice) {
98 self.0.lock().unwrap().push(notice);
99 }
100
101 fn latest(&self) -> Option<&Notice> {
102 None
105 }
106
107 fn clear(&mut self) {
108 self.0.lock().unwrap().clear();
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn synced_reads_as_prose() {
118 let notice = Notice::Synced {
119 deleted: 2,
120 updated: 3,
121 };
122 assert_eq!(notice.text(), "synced: 2 deleted, 3 updated");
123 assert!(!notice.is_error());
124 }
125
126 #[test]
127 fn new_mail_is_prose_a_bell_can_recognize() {
128 let notice = Notice::NewMail("new mail in inbox (+2)".into());
129 assert_eq!(notice.text(), "new mail in inbox (+2)");
130 assert!(notice.is_new_mail() && !notice.is_error());
131 assert!(!Notice::Info("x".into()).is_new_mail());
132 }
133
134 #[test]
135 fn a_log_keeps_the_order_and_a_handle_reads_it() {
136 let log = Log::default();
137 let mut handle = log.clone();
138 handle.notice(Notice::Info("first".into()));
139 handle.notice(Notice::Error("second".into()));
140 assert_eq!(log.notices().len(), 2);
141 assert_eq!(log.last_text(), "second");
142 assert!(log.said("fir"));
143 handle.clear();
144 assert_eq!(log.notices(), vec![]);
145 }
146}