diff options
| author | garhve <git@garhve.com> | 2024-08-01 02:02:43 +0800 | 
|---|---|---|
| committer | garhve <git@garhve.com> | 2024-08-01 02:02:43 +0800 | 
| commit | c926cd6eceaa2522b557c3dc1f9d7ebe8002702d (patch) | |
| tree | 7f4adab86870dcb2fecf797d0b10bf0aa3a01759 /src/lib.rs | |
add function to print project summary
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")) +    } +} | 
