use super::directives::format_metadata;
use super::{FormatConfig, format_cost_spec, format_price_annotation};
use crate::{CostSpec, IncompleteAmount, Posting, PriceAnnotation, Transaction};
use std::fmt::Write;
pub fn format_transaction(txn: &Transaction, config: &FormatConfig) -> String {
let mut out = String::with_capacity(400);
write!(out, "{} {}", txn.date, txn.flag).unwrap();
if let Some(payee) = &txn.payee {
write!(out, " \"{}\"", super::escape_string(payee)).unwrap();
}
write!(out, " \"{}\"", super::escape_string(&txn.narration)).unwrap();
for tag in &txn.tags {
write!(out, " #{tag}").unwrap();
}
for link in &txn.links {
write!(out, " ^{link}").unwrap();
}
out.push('\n');
format_metadata(&txn.meta, &config.indent, &mut out);
let meta_indent = format!("{}{}", &config.indent, &config.indent);
for posting in &txn.postings {
for comment in &posting.comments {
writeln!(out, "{}{}", &config.indent, comment).unwrap();
}
let posting_line = format_posting(posting, config);
if let Some(trailing) = posting.trailing_comments.first() {
writeln!(out, "{posting_line} {trailing}").unwrap();
} else {
out.push_str(&posting_line);
out.push('\n');
}
for trailing in posting.trailing_comments.iter().skip(1) {
writeln!(out, "{}{}", &config.indent, trailing).unwrap();
}
if !posting.meta.is_empty() {
format_metadata(&posting.meta, &meta_indent, &mut out);
}
}
for comment in &txn.trailing_comments {
writeln!(out, "{}{}", &config.indent, comment).unwrap();
}
out
}
pub fn format_posting(posting: &Posting, config: &FormatConfig) -> String {
let mut line = String::new();
line.push_str(&config.indent);
if let Some(flag) = posting.flag {
write!(line, "{flag} ").unwrap();
}
line.push_str(&posting.account);
if let Some(incomplete_amount) = &posting.units {
let current_len = line.len();
let amount_str = format_incomplete_amount(incomplete_amount);
let amount_with_extras =
format_posting_incomplete_amount(incomplete_amount, &posting.cost, &posting.price);
let target_col = config.amount_column.saturating_sub(amount_str.len());
if current_len < target_col {
let padding = target_col - current_len;
for _ in 0..padding {
line.push(' ');
}
} else {
line.push_str(" "); }
line.push_str(&amount_with_extras);
}
line
}
pub fn format_incomplete_amount(amount: &IncompleteAmount) -> String {
match amount {
IncompleteAmount::Complete(a) => format!("{} {}", a.number, a.currency),
IncompleteAmount::NumberOnly(n) => n.to_string(),
IncompleteAmount::CurrencyOnly(c) => c.to_string(),
}
}
pub fn format_posting_incomplete_amount(
units: &IncompleteAmount,
cost: &Option<CostSpec>,
price: &Option<PriceAnnotation>,
) -> String {
let mut out = format_incomplete_amount(units);
if let Some(cost_spec) = cost {
out.push(' ');
out.push_str(&format_cost_spec(cost_spec));
}
if let Some(price_ann) = price {
out.push(' ');
out.push_str(&format_price_annotation(price_ann));
}
out
}