add a simple pretty printer for beancount directives

korrat
Sep 9, 2022, 7:46 PM
QRIJE4AQWN7A7O2CO7FXRV4FXZ5RHONQKRGHYXAD7WSECJYK2MFAC

Dependencies

  • [2] YDK6X6PP add a library of important types for beancount
  • [3] I2P2FTLE add basic parser for german decimals

Change contents

  • file addition: beancount-pretty-printer (d--r------)
    [3.18]
  • file addition: src (d--r------)
    [0.36]
  • file addition: lib.rs (---r------)
    [0.53]
    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(())
    }
    }
  • file addition: Cargo.toml (---r------)
    [0.36]
    [package]
    name = "beancount-pretty-printer"
    version = "0.0.0-dev.0"
    edition = "2021"
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    [dependencies]
    rust_decimal = "1.26.1"
    [dependencies.beancount-types]
    path = "../beancount-types"
  • replacement in Cargo.toml at line 2
    [3.9844][2.26244:26306]()
    members = ["common/beancount-types", "common/german-decimal"]
    [3.9844]
    members = [
    "common/beancount-pretty-printer",
    "common/beancount-types",
    "common/german-decimal",
    ]
  • edit in Cargo.lock at line 25
    [3.10403]
    [3.10403]
    [[package]]
    name = "beancount-pretty-printer"
    version = "0.0.0-dev.0"
    dependencies = [
    "beancount-types",
    "rust_decimal",
    ]