use console::Style; use crate::data::{ AccountName, Decimal, Root, ledger::{Change, Transaction}, spec::AccountSpec, }; #[derive(Clone, Copy, Default)] enum Align { #[default] Left, Centre, Right, } #[derive(Default)] struct Column { pub align: Align, pub left_border: bool, pub right_border: bool, // pub contents: Box>, } enum Row { Line, Data(Vec), } 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!(); } } #[derive(Clone, Copy, Default)] pub struct TransactionTable {} impl TransactionTable { pub fn show<'d>( self, root: Option<&Root>, account: AccountName, txns: impl Iterator, ) { show_table( 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() }, ], vec![ Row::Data(vec![ "Date".into(), "Memo".into(), "Amount".into(), "Balance".into(), ]), Row::Line, ] .into_iter() .chain( txns.filter_map(|txn| txn.change_for(account).map(|chg| (txn, chg))) .map(|(txn, chg)| { let precision = root.and_then(|r| r.unit_spec(*chg.unit)).and_then(|v| v.precision).unwrap_or(2) as usize; 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()), ]) }), ), ) } }