1use crate::card::Card;
10use crate::mailindex::bare_id;
11use std::collections::HashMap;
12
13pub fn ids_of(card: &Card) -> Vec<String> {
15 let mut ids: Vec<String> = card.message_id.iter().map(|id| bare_id(id)).collect();
16 for name in ["In-Reply-To", "References"] {
17 for value in card.headers.all(name) {
18 ids.extend(value.split(|c: char| c.is_whitespace() || c == ',').filter(|t| t.contains('@')).map(bare_id));
19 }
20 }
21 ids.retain(|id| !id.is_empty());
22 ids
23}
24
25pub fn group(cards: &[&Card]) -> Vec<usize> {
27 let mut parent: Vec<usize> = (0..cards.len()).collect();
29 fn root(parent: &mut [usize], mut i: usize) -> usize {
30 while parent[i] != i {
31 parent[i] = parent[parent[i]];
32 i = parent[i];
33 }
34 i
35 }
36 let mut first_with: HashMap<String, usize> = HashMap::new();
37 for (i, card) in cards.iter().enumerate() {
38 for id in ids_of(card) {
39 match first_with.get(&id) {
40 Some(&j) => {
41 let (a, b) = (root(&mut parent, i), root(&mut parent, j));
42 parent[a.max(b)] = a.min(b);
43 }
44 None => {
45 first_with.insert(id, i);
46 }
47 }
48 }
49 }
50 (0..cards.len()).map(|i| root(&mut parent, i)).collect()
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 fn card(raw: &str) -> Card {
58 Card::from_bytes(raw.replace('\n', "\r\n").as_bytes()).unwrap()
59 }
60
61 #[test]
62 fn answers_join_their_conversation() {
63 let first = card("From: jane@example.org\nSubject: Hello\nMessage-ID: <a@example.org>\n\nHi.\n");
64 let answer = card("From: me@example.net\nSubject: Re: Hello\nMessage-ID: <b@example.net>\nIn-Reply-To: <a@example.org>\n\nHi Jane.\n");
65 let third = card("From: jane@example.org\nSubject: Re: Hello\nMessage-ID: <c@example.org>\nReferences: <b@example.net>\n\nThanks.\n");
67 let stranger = card("From: paul@example.com\nSubject: Hello\nMessage-ID: <d@example.com>\n\nHello too.\n");
68 let groups = group(&[&first, &answer, &third, &stranger]);
69 assert_eq!(groups[0], groups[1]);
70 assert_eq!(groups[0], groups[2]);
71 assert_ne!(groups[0], groups[3], "the same subject does not make a conversation");
72 assert_eq!(ids_of(&third), vec!["c@example.org", "b@example.net"]);
73 }
74}