use super::escape_string;
use crate::{Amount, CostSpec, PriceAnnotation};
pub fn format_amount_with(amount: &Amount, config: &super::FormatConfig) -> String {
format!(
"{} {}",
super::render_number(amount.number, amount.currency.as_str(), config),
amount.currency
)
}
pub fn format_cost_spec(spec: &CostSpec, config: &super::FormatConfig) -> String {
let mut parts = Vec::with_capacity(4);
let uses_double_braces = matches!(spec.number, Some(crate::CostNumber::Total { .. }));
if let Some(curr) = &spec.currency {
let render = |n| super::render_number(n, curr.as_str(), config);
match spec.number {
Some(crate::CostNumber::PerUnit { value: num }) => {
parts.push(format!("{} {curr}", render(num)));
}
Some(crate::CostNumber::PerUnitFromTotal(b)) => {
parts.push(format!("{} {curr}", render(b.per_unit)));
}
Some(crate::CostNumber::Total { value: num }) => {
parts.push(format!("{} {curr}", render(num)));
}
Some(crate::CostNumber::Compound { per_unit, total }) => {
parts.push(format!("{} # {} {curr}", render(per_unit), render(total)));
}
None => {}
}
}
if let Some(date) = spec.date {
parts.push(date.to_string());
}
if let Some(label) = &spec.label {
parts.push(format!("\"{}\"", escape_string(label)));
}
if spec.merge {
parts.push("*".to_string());
}
if uses_double_braces {
format!("{{{{{}}}}}", parts.join(", "))
} else {
format!("{{{}}}", parts.join(", "))
}
}
pub fn format_price_annotation(price: &PriceAnnotation, config: &super::FormatConfig) -> String {
let sigil = price.kind.to_string();
match &price.amount {
Some(crate::IncompleteAmount::Complete(amt)) => {
format!("{sigil} {}", format_amount_with(amt, config))
}
Some(inc) => format!("{sigil} {}", super::format_incomplete_amount(inc, config)),
None => sigil,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BookedCost, CostNumber, CostSpec};
use rust_decimal_macros::dec;
#[test]
fn cost_spec_per_unit_renders_single_braces() {
let spec = CostSpec::empty()
.with_number(crate::CostNumber::PerUnit { value: dec!(150) })
.with_currency("USD");
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{150 USD}"
);
}
#[test]
fn cost_spec_total_renders_double_braces() {
let spec = CostSpec::empty()
.with_number(crate::CostNumber::Total { value: dec!(1500) })
.with_currency("USD");
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{{1500 USD}}"
);
}
#[test]
fn cost_spec_per_unit_from_total_renders_as_per_unit() {
let b = BookedCost::new(dec!(150), dec!(300), dec!(2));
let spec = CostSpec::empty()
.with_number(CostNumber::PerUnitFromTotal(b))
.with_currency("USD");
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{150 USD}"
);
}
#[test]
fn cost_spec_empty_renders_braces() {
let spec = CostSpec::empty();
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{}"
);
}
#[test]
fn cost_spec_currency_only_renders_bare() {
let spec = CostSpec::empty().with_currency("USD");
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{}"
);
}
#[test]
fn cost_spec_total_preserves_date_label_merge() {
let spec = CostSpec::empty()
.with_number(CostNumber::Total { value: dec!(1500) })
.with_currency("USD")
.with_date(crate::naive_date(2024, 1, 15).unwrap())
.with_label("lot1")
.with_merge();
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{{1500 USD, 2024-01-15, \"lot1\", *}}"
);
}
#[test]
fn cost_spec_per_unit_preserves_date_label_merge() {
let spec = CostSpec::empty()
.with_number(CostNumber::PerUnit { value: dec!(150) })
.with_currency("USD")
.with_date(crate::naive_date(2024, 1, 15).unwrap())
.with_label("lot1")
.with_merge();
assert_eq!(
format_cost_spec(&spec, &crate::format::FormatConfig::default()),
"{150 USD, 2024-01-15, \"lot1\", *}"
);
}
}