diff --git a/.gitignore b/.gitignore index 2f7896d..a0e161f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +numbers.json diff --git a/Cargo.lock b/Cargo.lock index a349e45..27fd152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,9 +76,11 @@ dependencies = [ [[package]] name = "clean_file" -version = "0.8.2" +version = "0.8.3" dependencies = [ "futures", + "serde", + "serde_json", "tempfile", "textsender_models", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 8cc8efb..6443b21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clean_file" -version = "0.8.2" +version = "0.8.3" edition = "2024" rust-version = "1.95" @@ -8,6 +8,8 @@ rust-version = "1.95" [dependencies] tokio = { version = "1.52.2", features = ["full"] } futures = { version = "0.3.32" } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = { version = "1.0.149" } textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.2.5-v0.3.0-migrate-38dbcb09f7-111" } [dev-dependencies] diff --git a/src/main.rs b/src/main.rs index 0cf9f92..0503ddf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,32 @@ -fn main() { - println!("clean_file"); +pub mod parser; +pub mod version; + +#[tokio::main] +async fn main() { + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + println!("Invalid arguments provided"); + std::process::exit(-1); + } + + if version::contains_version(&args) { + version::print_version(); + std::process::exit(-1); + } + + let filepath = &args[1]; + println!("Filepath: {filepath:?}"); + let prsr = parser::NumberParser { + filepath: filepath.clone(), + }; + + match prsr.file_dump().await { + Ok(contacts) => { + prsr.print_phone_numbers(&contacts); + prsr.save_file(contacts).await; + } + Err(err) => { + eprintln!("Error: {err:?}"); + } + }; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..9239aa8 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,168 @@ +use std::io::BufRead; + +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 = textsender_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) { + let filename = String::from("numbers.json"); + + 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 = textsender_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 = textsender_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 + } +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..22ea1eb --- /dev/null +++ b/src/version.rs @@ -0,0 +1,20 @@ +pub fn print_version() { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + + println!("{name:?} {version:?}"); +} + +pub fn contains_version(args: &Vec) -> bool { + let valid_flags = vec!["-v", "--version"]; + + for arg in args { + for valid_flag in &valid_flags { + if valid_flag == arg { + return true; + } + } + } + + false +}