1use once_cell::sync::Lazy;
2use regex::Regex;
3
4use crate::message::BodyChain;
5use crate::structs::EmailStore;
6
7#[allow(dead_code)]
8static MSGID_QUOTE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^On\s.*\n?\s*wrote:\s*$").unwrap());
9
10#[allow(dead_code)]
11static EMAIL_QUOTE_RE: Lazy<Regex> =
12 Lazy::new(|| Regex::new(r"<([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>").unwrap());
13
14pub fn link_quotes(store: &mut EmailStore, ignore_types: bool, linkquotes: bool) {
15 if !linkquotes {
16 return;
17 }
18
19 let msgids: Vec<Option<String>> = store.emails.iter().map(|e| e.msgid.clone()).collect();
20
21 for i in 0..store.emails.len() {
22 let body_chain = &store.emails[i].bodylist;
23
24 if !ignore_types {
25 let from_text = collect_body_text(body_chain);
26 for (j, other_msgid) in msgids.iter().enumerate() {
27 if i == j || other_msgid.is_none() {
28 continue;
29 }
30 if let Some(ref mid) = other_msgid {
31 if from_text.contains(mid) {
32 let email_info = &store.emails[i];
33 let from_msgnum = store.emails[j].msgnum;
34 let to_msgnum = email_info.msgnum;
35 if from_msgnum != to_msgnum {
36 store.replylist.push(crate::message::Reply {
37 from_msgnum,
38 msgnum: to_msgnum,
39 data: None,
40 maybe_reply: 1,
41 });
42 }
43 }
44 }
45 }
46 }
47 }
48}
49
50fn collect_body_text(body_chain: &BodyChain) -> String {
51 let mut text = String::new();
52 for body in &body_chain.bodies {
53 if !body.attached && !body.header {
54 text.push_str(&body.line);
55 text.push(' ');
56 }
57 }
58 text
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::message::{Body, BodyChain, EmailInfo};
65
66 #[test]
67 fn test_link_quotes_empty_store() {
68 let mut store = EmailStore::new();
69 link_quotes(&mut store, false, true);
70 assert!(store.replylist.is_empty());
71 }
72
73 #[test]
74 fn test_link_quotes_disabled() {
75 let mut store = EmailStore::new();
76 let e1 = EmailInfo {
77 msgnum: 1,
78 msgid: Some("<a@b>".to_string()),
79 bodylist: BodyChain {
80 bodies: vec![Body {
81 line: "reply to <a@b>".to_string(),
82 html: false,
83 header: false,
84 parsed_header: false,
85 attached: false,
86 demimed: false,
87 msgnum: 0,
88 }],
89 },
90 ..Default::default()
91 };
92 let e2 = EmailInfo {
93 msgnum: 2,
94 msgid: Some("<b@c>".to_string()),
95 bodylist: BodyChain { bodies: Vec::new() },
96 ..Default::default()
97 };
98 store.add_email(e1);
99 store.add_email(e2);
100 link_quotes(&mut store, false, false);
101 assert!(store.replylist.is_empty());
102 }
103
104 #[test]
105 fn test_collect_body_text() {
106 let chain = BodyChain {
107 bodies: vec![
108 Body {
109 line: "hello".to_string(),
110 html: false,
111 header: false,
112 parsed_header: false,
113 attached: false,
114 demimed: false,
115 msgnum: 0,
116 },
117 Body {
118 line: "world".to_string(),
119 html: false,
120 header: false,
121 parsed_header: false,
122 attached: true,
123 demimed: false,
124 msgnum: 0,
125 },
126 ],
127 };
128 let text = collect_body_text(&chain);
129 assert!(!text.contains("world"));
130 assert!(text.contains("hello"));
131 }
132}