aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs244
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(),
+ );
+ }
+ }
+}