use itertools::Itertools; mod check; mod data; mod show; mod import; #[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, Import { account: String, from: std::path::PathBuf }, } 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()); let tt = show::TransactionTable::default(); if let Some(ld) = data.ledger_data_for(aname) { tt.show(&data, aname, ld.iter().map(data::Spanned::as_ref)); } else { log::error!("account not found!"); } } Self::Reformat => { let data = load_data(&mut fsdata, inv)?; data::ledger::print_ledger(&data, data.all_ledger_data().iter()); } Self::Import { account, from } => { let data = load_data(&mut fsdata, inv)?; let aname = account.into(); let Some(aspec) = data.account_spec(aname) else { todo!() }; let imported = import::import_from(aspec, aname, from.as_path()).unwrap(); let tt = show::TransactionTable::default(); tt.show(&data, aname, imported.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) }