show.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. use crate::data::{AccountName, Hoard, Transaction, TransactionRef};
  2. #[derive(Clone, Copy, Default)]
  3. #[allow(unused)]
  4. pub enum Align {
  5. #[default]
  6. Left,
  7. Centre,
  8. Right,
  9. }
  10. #[derive(Clone, Default)]
  11. pub struct Column {
  12. pub align: Align,
  13. pub left_border: bool,
  14. pub right_border: bool,
  15. }
  16. pub enum Row {
  17. Line,
  18. Data(Vec<String>),
  19. }
  20. pub fn show_table(cols: Vec<Column>, rows: impl Iterator<Item = Row>) {
  21. let mut min_widths = vec![0usize; cols.len()];
  22. let table_data = rows.collect::<Vec<_>>();
  23. for row in &table_data {
  24. let Row::Data(row) = &row else { continue };
  25. for (idx, rc) in row.iter().enumerate() {
  26. min_widths[idx] = min_widths[idx].max(console::measure_text_width(rc.as_str()));
  27. }
  28. }
  29. for row in table_data {
  30. match row {
  31. Row::Line => {
  32. for (idx, col) in min_widths.iter().enumerate() {
  33. if cols[idx].left_border {
  34. print!("{}", boxy::Char::cross(boxy::Weight::Normal));
  35. }
  36. for _ in 0..=*col {
  37. print!("{}", boxy::Char::horizontal(boxy::Weight::Normal));
  38. }
  39. if cols[idx].right_border {
  40. print!("{}", boxy::Char::cross(boxy::Weight::Normal));
  41. }
  42. }
  43. }
  44. Row::Data(row) => {
  45. for (idx, col) in row.into_iter().enumerate() {
  46. if cols[idx].left_border {
  47. print!("{}", boxy::Char::vertical(boxy::Weight::Normal));
  48. }
  49. match cols[idx].align {
  50. Align::Left => print!("{col:<width$}", width = min_widths[idx] + 1),
  51. Align::Centre => print!("{col:^width$}", width = min_widths[idx] + 1),
  52. Align::Right => print!("{col:>width$}", width = min_widths[idx] + 1),
  53. }
  54. if cols[idx].right_border {
  55. print!("{}", boxy::Char::vertical(boxy::Weight::Normal));
  56. }
  57. }
  58. }
  59. }
  60. println!();
  61. }
  62. }
  63. pub fn show_transaction(_root: Option<&Hoard>, txn: &Transaction) {
  64. let bluestyle = console::Style::new().blue();
  65. // let greenstyle = console::Style::new().green();
  66. let yellowstyle = console::Style::new().yellow();
  67. let graystyle = console::Style::new().white().dim();
  68. println!(
  69. "{}: {}",
  70. bluestyle.apply_to(txn.datestamp),
  71. txn.title.as_deref().unwrap_or("")
  72. );
  73. for change in &txn.changes {
  74. println!(
  75. " - {}: {} {}",
  76. yellowstyle.apply_to(change.account),
  77. change.amount,
  78. graystyle.apply_to(change.unit)
  79. );
  80. }
  81. }
  82. #[derive(Clone, Copy, Default)]
  83. pub struct TransactionTable {}
  84. impl TransactionTable {
  85. fn make_columns(&self) -> Vec<Column> {
  86. vec![
  87. // datestamp
  88. Column {
  89. align: Align::Left,
  90. right_border: true,
  91. ..Default::default()
  92. },
  93. // Memo
  94. Column {
  95. align: Align::Left,
  96. ..Default::default()
  97. },
  98. // Amount
  99. Column {
  100. align: Align::Right,
  101. ..Default::default()
  102. },
  103. // Balance
  104. Column {
  105. align: Align::Right,
  106. left_border: true,
  107. ..Default::default()
  108. },
  109. ]
  110. }
  111. fn make_header_row(&self) -> Row {
  112. Row::Data(vec![
  113. "Date".into(),
  114. "Memo".into(),
  115. "Amount".into(),
  116. "Balance".into(),
  117. ])
  118. }
  119. fn format_txn(
  120. &self,
  121. root: Option<&Hoard>,
  122. account: AccountName,
  123. txn: &Transaction,
  124. ) -> Option<Row> {
  125. let Some(chg) = txn.change_for(account) else {
  126. return None;
  127. };
  128. let precision = root
  129. .and_then(|r| r.unit_spec(*chg.unit))
  130. .and_then(|v| v.precision)
  131. .unwrap_or(2) as usize;
  132. Some(Row::Data(vec![
  133. txn.datestamp.to_string(),
  134. txn.title.clone().unwrap_or_else(String::new),
  135. format!("{:.precision$}", chg.amount),
  136. chg.balance
  137. .as_deref()
  138. .map(|b| format!("{:.precision$}", b))
  139. .unwrap_or(String::new()),
  140. ]))
  141. }
  142. pub fn show<'d>(
  143. self,
  144. root: Option<&Hoard>,
  145. account: AccountName,
  146. txns: impl Iterator<Item = &'d Transaction>,
  147. ) {
  148. show_table(
  149. self.make_columns(),
  150. vec![self.make_header_row(), Row::Line]
  151. .into_iter()
  152. .chain(txns.filter_map(|txn| self.format_txn(root, account, txn))),
  153. )
  154. }
  155. pub fn show_refs<'d>(
  156. self,
  157. root: Option<&Hoard>,
  158. account: AccountName,
  159. txns: impl Iterator<Item = &'d TransactionRef>,
  160. ) {
  161. show_table(
  162. self.make_columns(),
  163. vec![self.make_header_row(), Row::Line]
  164. .into_iter()
  165. .chain(txns.filter_map(|txn| self.format_txn(root, account, &txn.borrow()))),
  166. )
  167. }
  168. }