+ use beancount_types::Amount;
+ use beancount_types::Balance;
+ use beancount_types::CostBasis;
+ use beancount_types::Directive;
+ use beancount_types::Posting;
+ use beancount_types::Transaction;
+ use rust_decimal::Decimal;
+ use std::io::BufWriter;
+ use std::io::Result;
+ use std::io::Write;
+
+ // TODO consider reimplementation based on pretty.rs
+
+ pub struct PrettyPrinter<
+ W,
+ const FLAG_COLUMN: usize,
+ const ACCOUNT_COLUMN: usize,
+ const AMOUNT_COLUMN: usize,
+ > {
+ inner: W,
+ }
+
+ impl<W, const FLAG_COLUMN: usize, const ACCOUNT_COLUMN: usize, const AMOUNT_COLUMN: usize>
+ PrettyPrinter<W, FLAG_COLUMN, ACCOUNT_COLUMN, AMOUNT_COLUMN>
+ where
+ W: Write,
+ {
+ pub fn unbuffered(inner: W) -> Self {
+ Self { inner }
+ }
+ }
+
+ impl<W, const FLAG_COLUMN: usize, const ACCOUNT_COLUMN: usize, const AMOUNT_COLUMN: usize>
+ PrettyPrinter<BufWriter<W>, FLAG_COLUMN, ACCOUNT_COLUMN, AMOUNT_COLUMN>
+ where
+ W: Write,
+ {
+ pub fn buffered(inner: W) -> Self {
+ let inner = BufWriter::new(inner);
+ Self { inner }
+ }
+ }
+
+ impl<W, const FLAG_COLUMN: usize, const ACCOUNT_COLUMN: usize, const AMOUNT_COLUMN: usize>
+ PrettyPrinter<W, FLAG_COLUMN, ACCOUNT_COLUMN, AMOUNT_COLUMN>
+ where
+ W: Write,
+ {
+ pub fn print_balance(&mut self, balance: &Balance) -> Result<()> {
+ let Balance {
+ date,
+ account,
+ amount,
+ } = balance;
+
+ let account_width = AMOUNT_COLUMN - 19;
+ write!(self.inner, "{date} balance {account:account_width$}")?;
+ self.print_amount(amount)
+ }
+
+ pub fn print_directive(&mut self, directive: &Directive) -> Result<()> {
+ // TODO prevent trailing whitespace
+ match directive {
+ Directive::Transaction(transaction) => self.print_transaction(transaction),
+ Directive::Balance(balance) => self.print_balance(balance),
+ }
+ }
+
+ pub fn print_directives<'a>(
+ &mut self,
+ directives: impl IntoIterator<Item = &'a Directive>,
+ ) -> Result<()> {
+ directives
+ .into_iter()
+ .enumerate()
+ .try_for_each(|(index, directive)| {
+ if 0 < index {
+ // Create empty line between directives
+ self.inner.write_all(b"\n\n")?;
+ }
+
+ self.print_directive(directive)
+ })
+ .and_then(|_| self.inner.flush())
+ }
+
+ pub fn print_transaction(&mut self, transaction: &Transaction) -> Result<()> {
+ let Transaction {
+ date,
+ flag,
+ payee,
+ narration,
+ postings,
+ } = transaction;
+
+ write!(self.inner, "{date} {flag}")?;
+
+ match (payee, narration) {
+ (Some(payee), Some(narration)) => write!(self.inner, r#" "{payee}" "{narration}""#)?,
+ (Some(payee), None) => write!(self.inner, r#" "{payee}" """#)?,
+ (None, Some(narration)) => write!(self.inner, r#" "{narration}""#)?,
+ (None, None) => {}
+ }
+
+ postings
+ .iter()
+ .try_for_each(|posting| self.print_posting(posting))
+ }
+ }
+
+ impl<W, const FLAG_COLUMN: usize, const ACCOUNT_COLUMN: usize, const AMOUNT_COLUMN: usize>
+ PrettyPrinter<W, FLAG_COLUMN, ACCOUNT_COLUMN, AMOUNT_COLUMN>
+ where
+ W: Write,
+ {
+ fn print_amount(&mut self, amount: &Amount) -> Result<()> {
+ let Amount { amount, commodity } = amount;
+
+ self.print_decimal_aligned(amount, 15, 4)?;
+ write!(self.inner, " {commodity:12}")?;
+
+ Ok(())
+ }
+
+ fn print_cost(&mut self, cost: &Option<CostBasis>) -> Result<()> {
+ // TODO align amounts?
+ if let Some(cost) = cost {
+ write!(self.inner, " {cost}")?;
+ }
+
+ Ok(())
+ }
+
+ fn print_decimal_aligned(
+ &mut self,
+ decimal: &Decimal,
+ total_width: usize,
+ decimals: usize,
+ ) -> Result<()> {
+ // TODO handle this with icu or similar (e.g. for digit grouping)?
+ let mut buffer = [b' '; 32];
+
+ let width = total_width - (decimals - (decimal.scale() as usize));
+ write!(&mut buffer[..width], "{decimal:width$}")?;
+
+ self.inner.write_all(&buffer[..total_width])?;
+
+ Ok(())
+ }
+ fn print_posting(&mut self, posting: &Posting) -> Result<()> {
+ let Posting {
+ flag: _,
+ account,
+ amount,
+ cost,
+ price,
+ } = posting;
+ write!(self.inner, "\n ")?;
+
+ let account_width = if amount.is_some() {
+ AMOUNT_COLUMN - ACCOUNT_COLUMN
+ } else {
+ 0
+ };
+ write!(self.inner, "{account:account_width$}")?;
+
+ if let Some(amount) = amount {
+ self.print_amount(amount)?;
+
+ self.print_cost(cost)?;
+
+ if let Some(price) = price {
+ write!(self.inner, " @ {price}")?;
+ }
+ }
+
+ Ok(())
+ }
+ }