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 {
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::rc::Rc<std::cell::RefCell<Vec<Notice>>>);
69
70impl Log {
71 pub fn notices(&self) -> Vec<Notice> {
73 self.0.borrow().clone()
74 }
75
76 pub fn last_text(&self) -> String {
78 self.0.borrow().last().map(Notice::text).unwrap_or_default()
79 }
80
81 pub fn said(&self, needle: &str) -> bool {
83 self.0.borrow().iter().any(|n| n.text().contains(needle))
84 }
85}
86
87impl NoticeSink for Log {
88 fn notice(&mut self, notice: Notice) {
89 self.0.borrow_mut().push(notice);
90 }
91
92 fn latest(&self) -> Option<&Notice> {
93 None
96 }
97
98 fn clear(&mut self) {
99 self.0.borrow_mut().clear();
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn synced_reads_as_prose() {
109 let notice = Notice::Synced {
110 deleted: 2,
111 updated: 3,
112 };
113 assert_eq!(notice.text(), "synced: 2 deleted, 3 updated");
114 assert!(!notice.is_error());
115 }
116
117 #[test]
118 fn new_mail_is_prose_a_bell_can_recognize() {
119 let notice = Notice::NewMail("new mail in inbox (+2)".into());
120 assert_eq!(notice.text(), "new mail in inbox (+2)");
121 assert!(notice.is_new_mail() && !notice.is_error());
122 assert!(!Notice::Info("x".into()).is_new_mail());
123 }
124
125 #[test]
126 fn a_log_keeps_the_order_and_a_handle_reads_it() {
127 let log = Log::default();
128 let mut handle = log.clone();
129 handle.notice(Notice::Info("first".into()));
130 handle.notice(Notice::Error("second".into()));
131 assert_eq!(log.notices().len(), 2);
132 assert_eq!(log.last_text(), "second");
133 assert!(log.said("fir"));
134 handle.clear();
135 assert_eq!(log.notices(), vec![]);
136 }
137}