use crate::data::{AccountName, Hoard, Transaction, TransactionRef}; #[derive(Clone, Copy, Default)] #[allow(unused)] pub enum Align { #[default] Left, Centre, Right, } #[derive(Clone, Default)] pub struct Column { pub align: Align, pub left_border: bool, pub right_border: bool, } pub enum Row { Line, Data(Vec), } pub fn show_table(cols: Vec, rows: impl Iterator) { let mut min_widths = vec![0usize; cols.len()]; let table_data = rows.collect::>(); for row in &table_data { let Row::Data(row) = &row else { continue }; for (idx, rc) in row.iter().enumerate() { min_widths[idx] = min_widths[idx].max(console::measure_text_width(rc.as_str())); } } for row in table_data { match row { Row::Line => { for (idx, col) in min_widths.iter().enumerate() { if cols[idx].left_border { print!("{}", boxy::Char::cross(boxy::Weight::Normal)); } for _ in 0..=*col { print!("{}", boxy::Char::horizontal(boxy::Weight::Normal)); } if cols[idx].right_border { print!("{}", boxy::Char::cross(boxy::Weight::Normal)); } } } Row::Data(row) => { for (idx, col) in row.into_iter().enumerate() { if cols[idx].left_border { print!("{}", boxy::Char::vertical(boxy::Weight::Normal)); } match cols[idx].align { Align::Left => print!("{col: print!("{col:^width$}", width = min_widths[idx] + 1), Align::Right => print!("{col:>width$}", width = min_widths[idx] + 1), } if cols[idx].right_border { print!("{}", boxy::Char::vertical(boxy::Weight::Normal)); } } } } println!(); } } pub fn show_transaction(_root: Option<&Hoard>, txn: &Transaction) { let bluestyle = console::Style::new().blue(); // let greenstyle = console::Style::new().green(); let yellowstyle = console::Style::new().yellow(); let graystyle = console::Style::new().white().dim(); println!( "{}: {}", bluestyle.apply_to(txn.datestamp), txn.title.as_deref().unwrap_or("") ); for change in &txn.changes { println!( " - {}: {} {}", yellowstyle.apply_to(change.account), change.amount, graystyle.apply_to(change.unit) ); } } #[derive(Clone, Copy, Default)] pub struct TransactionTable {} impl TransactionTable { fn make_columns(&self) -> Vec { vec![ // datestamp Column { align: Align::Left, right_border: true, ..Default::default() }, // Memo Column { align: Align::Left, ..Default::default() }, // Amount Column { align: Align::Right, ..Default::default() }, // Balance Column { align: Align::Right, left_border: true, ..Default::default() }, ] } fn make_header_row(&self) -> Row { Row::Data(vec![ "Date".into(), "Memo".into(), "Amount".into(), "Balance".into(), ]) } fn format_txn( &self, root: Option<&Hoard>, account: AccountName, txn: &Transaction, ) -> Option { let Some(chg) = txn.change_for(account) else { return None; }; let precision = root .and_then(|r| r.unit_spec(*chg.unit)) .and_then(|v| v.precision) .unwrap_or(2) as usize; Some(Row::Data(vec![ txn.datestamp.to_string(), txn.title.clone().unwrap_or_else(String::new), format!("{:.precision$}", chg.amount), chg.balance .as_deref() .map(|b| format!("{:.precision$}", b)) .unwrap_or(String::new()), ])) } pub fn show<'d>( self, root: Option<&Hoard>, account: AccountName, txns: impl Iterator, ) { show_table( self.make_columns(), vec![self.make_header_row(), Row::Line] .into_iter() .chain(txns.filter_map(|txn| self.format_txn(root, account, txn))), ) } pub fn show_refs<'d>( self, root: Option<&Hoard>, account: AccountName, txns: impl Iterator, ) { show_table( self.make_columns(), vec![self.make_header_row(), Row::Line] .into_iter() .chain(txns.filter_map(|txn| self.format_txn(root, account, &txn.borrow()))), ) } }