use itertools::Itertools; mod check; mod data; #[derive(clap::Parser)] struct Invocation { /// path to treasure file #[clap(long, short, env = "TREASURE_FILE")] file: std::path::PathBuf, /// verbosity of output messages #[clap(long, short, action = clap::ArgAction::Count)] verbose: u8, #[clap(subcommand)] cmd: Command, } #[derive(clap::Subcommand)] enum Command { Summarize, Ledger { account: String }, Reformat, } fn load_data( fsdata: &mut data::FilesystemData, invocation: &Invocation, ) -> anyhow::Result { match data::Root::load(fsdata, &invocation.file) { Ok(data) => Ok(data), Err(data::DataError::IOError(ioerror)) => Err(ioerror.into()), Err(data::DataError::Report(report)) => { report.eprint(fsdata)?; Err(anyhow::anyhow!("Error reported")) } Err(data::DataError::Validation(verr)) => Err(anyhow::anyhow!("Validation error: {verr}")), } } impl Command { fn run(&self, inv: &Invocation) -> anyhow::Result<()> { let mut fsdata = data::FilesystemData::default(); match self { Self::Summarize => { let data = load_data(&mut fsdata, inv)?; let positive = console::Style::new().green(); let negative = console::Style::new().red(); let neutral = console::Style::new(); let Some(maxlen) = data.account_names().map(|v| v.len()).max() else { return Ok(()); }; for shortname in data.account_names().sorted_by_key(|an| an.as_str()) { if shortname.as_ref() == "initial" { continue; } if let Some(balances) = data.balance(shortname) { let balances = balances .into_iter() .filter(|(_, b)| !b.is_zero()) .sorted() .map(|(u, b)| { ( u, if b.is_sign_positive() { positive.apply_to(b) } else if b.is_sign_negative() { negative.apply_to(b) } else { neutral.apply_to(b) }, ) }); println!( "{shortname:maxlen$} {}", balances.map(|(u, b)| format!("{b:8} {u:5}")).join(" ") ); } } } Self::Ledger { account } => { let data = load_data(&mut fsdata, inv)?; let aname = data::AccountName::new(account.as_str()); if let Some(ld) = data.ledger_data_for(aname) { for le in ld { let Some((acc_bal, other_bal)) = le.split_balances(aname) else { continue; }; log::info!( "{:?}: {} {:?}", le.datestamp, acc_bal.amount, other_bal.map(|b| b.account).collect::>() ); } } else { log::info!("account not found. data: {data:?}"); } // let data = data::Root::load(&mut fsdata, &inv.file); // data.account(account)?; } Self::Reformat => { let data = load_data(&mut fsdata, inv)?; data::ledger::print_ledger(&data, data.all_ledger_data().iter()); } } Ok(()) } } fn main() -> anyhow::Result<()> { use clap::Parser; let inv = Invocation::parse(); pretty_env_logger::formatted_builder() .filter_level(match inv.verbose { 0 => log::LevelFilter::Info, 1 => log::LevelFilter::Debug, _ => log::LevelFilter::Trace, }) .init(); inv.cmd.run(&inv) }