use std::{ env, error, fs::{File}, io::BufReader, collections::HashMap, }; use itertools::Itertools; mod record; mod drawing; use record::Record; pub fn run() -> Result<(), Box> { let records = load_contents()?; drawing::ui(drawing::Category::Clear); loop { match drawing::ui(drawing::Category::Menu) { 'a' | 'A' => print_summary_by_project(&records), 'b' | 'B' => print_summary_by_time(&records), 'q' | 'Q' => { drawing::ui(drawing::Category::Title("quit program")); break; }, _ => { drawing::ui(drawing::Category::Wrong("Unsupported Option, please try again.\n")); continue; }, } } Ok(()) } fn print_summary_by_time(records: &Vec) { println!("placeholder"); } fn print_summary_by_project(records: &Vec) { let mut expenses: HashMap> = HashMap::new(); let mut sum = HashMap::new(); /* group the project with separate currency */ for r in records.iter() { /* exclude transfer and other type */ match r.record_type() { "Expense" | "Fee" | "Refund" | "Income" => { /* No project name will be ignore */ if let Some(project) = r.project() { if project.is_empty() { continue; } /* group projects with separate currency */ expenses .entry(project) .and_modify(|expense: &mut HashMap| { expense .entry(r.currency()) .and_modify(|v| *v += r.cal()) .or_insert(r.cal()); }) .or_insert(HashMap::from([(r.currency(), r.cal())])); /* sum same project and currency amount */ let element = sum.entry(r.currency()).or_insert(0.0 as f64); *element += r.cal(); } }, _ => continue, } } // print output println!("print summary by project:"); for project in expenses.keys().sorted() { println!("\n\t{project}"); print!("\t\t\t|"); for currency in expenses[project].keys().sorted() { let expense = expenses[project][currency]; print!("\t{currency} {:.2}\t|", expense); } println!(""); } println!("\n\n\tSummary"); print!("\t\t\t|"); for currency in sum.keys().sorted() { print!("\t{currency} {:.2}\t|", sum[currency]); } println!(""); } fn load_contents() -> Result, Box> { let args = env::args().skip(1).collect::>(); let csv_file = check_csv(&args)?; let mut reader = build_reader(csv_file)?; let mut records = Vec::new(); for r in reader.records() { let r = r.unwrap(); records.push(Record::new(r)); } Ok(records) } fn build_reader(file_name: String) -> Result>, Box> { let file = File::open(file_name)?; let buf = BufReader::new(file); let ret = csv::ReaderBuilder::new() .flexible(true) .from_reader(buf); Ok(ret) } fn check_csv(args: &Vec) -> Result> { let n = args.len(); if n != 1 { return Err(Box::from("Only 1 argument required\n\nUsage: moze_analyzer [csv_file]")); } if args[0].contains(".csv") { Ok(args[0].clone()) } else { Err(Box::from("Not csv file")) } }