diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a3a3522 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,144 @@ +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<dyn error::Error>> { + 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<Record>) { + println!("placeholder"); +} + +fn print_summary_by_project(records: &Vec<Record>) { + let mut expenses: HashMap<String, HashMap<String, f64>> = 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<String, f64>| { + 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<Vec<Record>, Box<dyn error::Error>> { + let args = env::args().skip(1).collect::<Vec<String>>(); + + 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<csv::Reader<BufReader<File>>, Box<dyn error::Error>> +{ + 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<String>) -> Result<String, Box<dyn error::Error>> { + 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")) + } +} |