use std::io::BufRead; pub const DEFAULT_OUTPUT_FILE: &str = "numbers.json"; const LETTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; #[derive(Clone, Debug, Default)] pub struct NumberParser { pub filepath: String, } impl NumberParser { pub async fn file_dump( &self, ) -> Result, std::io::Error> { println!("Dumping files"); let mut objs: Vec = Vec::new(); match self.read_lines(&self.filepath).await { Ok(lines) => { for line in lines { let contains_letters = line.chars().any(|c| LETTERS.contains(c)); if contains_letters { continue; } else { let my_obj = schedtxt_models::contact::Contact { phone_number: line.clone(), ..Default::default() }; if !my_obj.phone_number.is_empty() { objs.push(my_obj); } } } let updated_contacts = self.update_contacts(&objs).await; let removed_duplicates = self.remove_dups(&updated_contacts).await; Ok(removed_duplicates) } Err(err) => Err(err), } } pub async fn save_file(&self, numbers: Vec, filename: &str) { match serde_json::to_string(&numbers) { Ok(json_string) => match std::fs::write(filename, json_string) { Ok(_) => { println!("Saved to: {filename:?}"); } Err(err) => { eprintln!("Error: {err:?}"); } }, Err(err) => { eprintln!("Error: {err:?}"); } }; } pub fn print_phone_numbers(&self, contacts: &Vec) { println!("Printing phone numbers"); for contact in contacts { println!("Phone Number: {:?}", contact.phone_number); } println!("Total phone numbers: {:?}", contacts.len()); } async fn read_lines(&self, filename: &String) -> Result, std::io::Error> { match std::fs::File::open(filename) { Ok(file) => { let reader = std::io::BufReader::new(file); reader.lines().collect() } Err(err) => Err(err), } } async fn remove_dups( &self, contacts: &Vec, ) -> Vec { let mut unique_contacts: Vec = Vec::new(); let mut unique_items = std::collections::HashSet::new(); for val in contacts { unique_items.insert(val.phone_number.clone()); } for unique_item in unique_items { let contact = schedtxt_models::contact::Contact { phone_number: unique_item, ..Default::default() }; unique_contacts.push(contact); } unique_contacts } async fn update_contacts( &self, contacts: &Vec, ) -> Vec { let mut updated: Vec = Vec::new(); for ct in contacts { let phone_number = ct.phone_number.clone(); if phone_number.len() < 2 { println!("Invalid number: {phone_number:?}"); continue; } let pars = self.remove_some_data(&phone_number).await; match self.add_prefix(&pars) { Ok(updated_parsed) => { let contact = schedtxt_models::contact::Contact { phone_number: updated_parsed, ..Default::default() }; updated.push(contact); } Err(err) => { eprintln!("Error: {err:?}"); std::process::exit(-2); } }; } updated } fn add_prefix(&self, raw: &String) -> Result { if raw.is_empty() { Err(std::io::Error::other("Empty")) } else { let mut parsed: String = String::new(); let first_char = raw.chars().nth(0).unwrap(); if first_char == '1' { parsed = format!("+{raw}"); } else if first_char != '+' { parsed = format!("+1{raw}"); } Ok(parsed) } } async fn remove_some_data(&self, unparsed: &str) -> String { let mut parsed: String = String::new(); for ch in unparsed.chars() { if ch == ' ' { continue; } else { if ch.is_numeric() { parsed.push(ch); } } } parsed } } #[cfg(test)] mod tests { use schedtxt_models::contact::Contact; const TEST_NUMBERS_0: &str = "tests/numbers-0.txt"; #[test] fn test_file_dump() { let file_exists = { let path = std::path::Path::new(TEST_NUMBERS_0); path.exists() }; assert_eq!(true, file_exists, "File does not exists {TEST_NUMBERS_0:?}"); let filename = String::from(TEST_NUMBERS_0); let prsr = super::NumberParser { filepath: filename }; let original: Vec = vec![ Contact { phone_number: String::from("+13055550112"), ..Default::default() }, Contact { phone_number: String::from("+13175550123"), ..Default::default() }, Contact { phone_number: String::from("+14155550135"), ..Default::default() }, Contact { phone_number: String::from("+16085550190"), ..Default::default() }, Contact { phone_number: String::from("+17135550167"), ..Default::default() }, Contact { phone_number: String::from("+16025550177"), ..Default::default() }, Contact { phone_number: String::from("+13605550158"), ..Default::default() }, Contact { phone_number: String::from("+12175550194"), ..Default::default() }, ]; match async_std::task::block_on(prsr.file_dump()) { Ok(contacts) => { assert!((contacts.len() > 0), "No contacts has been parsed"); let phone_numbers_1: std::collections::HashSet<_> = contacts.iter().map(|p| p.phone_number.clone()).collect(); let phone_numbers_2: std::collections::HashSet<_> = original.iter().map(|p| p.phone_number.clone()).collect(); assert_eq!(phone_numbers_1, phone_numbers_2, "No match"); } Err(err) => { assert!(false, "Error: {err:?}"); } }; } #[test] fn test_save_file() { let prsr = super::NumberParser { filepath: String::from(TEST_NUMBERS_0), }; let results = async_std::task::block_on(prsr.file_dump()); assert!(results.is_ok(), "File dump not successful"); let test_output_file: &str = "test-numbers.json"; let contacts = results.unwrap(); async_std::task::block_on(prsr.save_file(contacts, test_output_file)); let file_exists = { let path = std::path::Path::new(test_output_file); path.exists() }; assert!(file_exists, "Contacts were not saved"); match std::fs::remove_file(test_output_file) { Ok(_) => { println!("File removed"); } Err(err) => { assert!(false, "Error: {err:?}"); } } } }