diff options
author | Yaroslav <contact@yaroslavps.com> | 2020-10-05 21:20:59 +0300 |
---|---|---|
committer | Yaroslav <contact@yaroslavps.com> | 2020-10-05 21:20:59 +0300 |
commit | 408b0ac993496b108ec1e479151d549e9535051a (patch) | |
tree | 6f19aa190a7f5707cc037f3f9590a89187582254 /src | |
download | finbudg-408b0ac993496b108ec1e479151d549e9535051a.tar.gz finbudg-408b0ac993496b108ec1e479151d549e9535051a.zip |
initial commitv0.1.0
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d091965 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,244 @@ +use clap::{ + Arg, + App, + ArgMatches, + crate_version, + crate_authors, + crate_description +}; +use chrono::Duration; +use colored::*; + +use budget::*; + +fn main() { + let matches = get_cli_matches(); + + let no_color = matches.occurrences_of("plain") > 0; + let force_color = matches.occurrences_of("force-color") > 0; + let input = matches.value_of("INPUT").unwrap(); + + let account = match budget::parse_account(input) { + Ok(data) => data, + Err(error) => { + match error { + ParseError::IOError(kind) => { + println!("IO error while parsing: {:?}", kind); + }, + ParseError::DeserializerError(_) => { + println!("Can't parse the file, invalid syntax"); + }, + } + + ::std::process::exit(1); + } + }; + let calculated = budget::calculate(&account); + + if no_color && !force_color { + colored::control::set_override(false); + } else if force_color { + colored::control::set_override(true); + } + + output(account, calculated); +} + +fn get_cli_matches() -> ArgMatches<'static> { + App::new("finbudg") + .version(crate_version!()) + .author(crate_authors!()) + .about(crate_description!()) + .arg(Arg::with_name("plain") + .short("p") + .long("plain") + .help("Don't colorize the output. Can also be set \ + with the NO_COLOR environment variable.") + .takes_value(false)) + .arg(Arg::with_name("force-color") + .long("force-color") + .help("Forces colorized output even when piping. Takes \ + precedence over --plain flag and NO_COLOR environment \ + variable") + .takes_value(false)) + .arg(Arg::with_name("INPUT") + .help("Expenses file in toml format to calculate from.") + .required(true) + .index(1)) + .get_matches() +} + +fn output(account: Account, calculated: Calculated) { + println!( + "{}", + format!( + "Your expenses for the period of {} - {}", + account.start_date.format("%Y-%m-%d"), + account.end_date.format("%Y-%m-%d"), + ).cyan(), + ); + + let last_day = match account.days.last() { + Some(day) => day, + None => { + println!("{}", "Your expenses are empty...".italic()); + + ::std::process::exit(0); + } + }; + + let days_until_end = account.end_date - last_day.date; + + println!( + "{}", + format!( + "Last day on entry: {}", + last_day.date.format("%Y-%m-%d"), + ).cyan(), + ); + + println!( + "{}", + format!( + "Days until period end: {}", + days_until_end.num_days(), + ).cyan(), + ); + + if days_until_end < Duration::zero() { + println!(); + println!( + "{}", + "Your last day on entry is set after the last date of the period!" + .yellow(), + ); + println!(); + } + + println!( + "{}", + format!( + "Budget: {:.2}", + account.budget, + ).cyan(), + ); + + println!(); + + for (category, expenses) in calculated.categories_day_average.iter() { + println!( + "Average per day in {}: {:.2}", + category, + expenses, + ); + } + + println!( + "Average per day in essential expenses: {:.2}", + calculated.essential_day_average, + ); + + println!( + "Average per day: {:.2}", + calculated.all_day_average, + ); + + println!(); + + for (category, expenses) in calculated.categories_subtotal.iter() { + println!( + "Total in {}: {:.2}", + category, + expenses, + ); + } + + println!( + "Total in essential expenses: {:.2}", + calculated.essential_subtotal, + ); + + println!( + "Total: {:.2}", + calculated.total, + ); + + println!(); + + let balance_output = format!("{:.2}", calculated.balance); + let balance_output = if calculated.balance > 0.0 { + if account.budget / calculated.balance < 10.0 { + balance_output.green() + } else { + balance_output.yellow() + } + } else { + balance_output.red() + }; + + println!("Left on balance: {}", balance_output); + + println!(); + + println!("Days until balance runs out:"); + + let days_left_output = format!( + "{:.2}", + calculated.days_left, + ); + let days_left_essential_output = format!( + "{:.2}", + calculated.days_left_essential, + ); + + let mut all_are_healthy = true; + let mut essential_are_healthy = true; + + let days_left_output = + if days_until_end.num_days() as f64 <= calculated.days_left { + days_left_output.green() + } else { + all_are_healthy = false; + + days_left_output.red() + }; + let days_left_essential_output = + if days_until_end.num_days() as f64 <= calculated.days_left_essential { + days_left_essential_output.green() + } else { + essential_are_healthy = false; + + days_left_essential_output.red() + }; + + println!( + "..taking into account all expenses: {}", + days_left_output, + ); + println!( + "..taking into account only essential expenses: {}", + days_left_essential_output, + ); + println!(); + + if all_are_healthy { + println!( + "{}", + "Your expenses are healthy, they should last you from your last \ + day on entry through your last day of the period.".green(), + ); + } else { + println!( + "{}", + "You are spending more than you can afford with your current \ + budget. Try minimizing your expenses".red(), + ); + if essential_are_healthy { + println!( + "{}", + "On the other hand, if you only spend money on essentials, \ + you should be able keep within your budget.".yellow(), + ); + } + } +} |