Skip to main content

git_su/
user_list.rs

1// UserList: find users by initials, name segment, etc.
2
3use crate::user::User;
4use crate::user_file::UserFile;
5
6pub struct UserList {
7    user_file: UserFile,
8}
9
10impl UserList {
11    pub fn new(user_file: UserFile) -> Self {
12        UserList { user_file }
13    }
14
15    pub fn add(&self, user: &User) {
16        let _ = self.user_file.write(user);
17    }
18
19    pub fn list(&self) -> Vec<User> {
20        self.user_file.read()
21    }
22
23    /// Find a unique combination of users matching the given search terms.
24    pub fn find(&self, search_terms: &[String]) -> Result<Vec<User>, String> {
25        let all = self.list();
26        if search_terms.is_empty() {
27            return Ok(vec![]);
28        }
29        let mut matches_per_term: Vec<Vec<User>> = Vec::new();
30        for term in search_terms {
31            let m = Self::matching_users(&all, term);
32            if m.is_empty() {
33                return Err(format!("No user found matching '{}'", term));
34            }
35            matches_per_term.push(m);
36        }
37        Self::unique_combination(&matches_per_term)
38            .ok_or_else(|| {
39                format!(
40                    "Couldn't find a combination of users matching {}",
41                    search_terms
42                        .iter()
43                        .map(|s| format!("'{}'", s))
44                        .collect::<Vec<_>>()
45                        .join(", ")
46                )
47            })
48    }
49
50    fn matching_users(all: &[User], search_term: &str) -> Vec<User> {
51        let term_lower = search_term.to_lowercase();
52        let mut result: Vec<User> = all
53            .iter()
54            .filter(|u| {
55                // Whole word of name
56                u.name()
57                    .split_whitespace()
58                    .any(|w| w.to_lowercase() == term_lower)
59                    || u.name().to_lowercase().split_whitespace().any(|w| {
60                        w.starts_with(&term_lower) || term_lower.starts_with(&w.to_lowercase())
61                    })
62                    || u.initials().contains(&term_lower)
63                    || u.name().to_lowercase().contains(&term_lower)
64                    || u.email().to_lowercase().contains(&term_lower)
65            })
66            .cloned()
67            .collect();
68        result.dedup();
69        result
70    }
71
72    fn unique_combination(term_matches: &[Vec<User>]) -> Option<Vec<User>> {
73        let mut combinations: Vec<Vec<User>> = vec![vec![]];
74        for matches in term_matches {
75            let mut new_combos = Vec::new();
76            for combo in &combinations {
77                for u in matches {
78                    if !combo.iter().any(|c| c == u) {
79                        let mut extended = combo.clone();
80                        extended.push(u.clone());
81                        new_combos.push(extended);
82                    }
83                }
84            }
85            combinations = new_combos;
86        }
87        combinations.into_iter().find(|c| c.len() == term_matches.len())
88    }
89}