use super::{escape_string, format_incomplete_amount};
use crate::{Amount, CostSpec, PriceAnnotation};
pub fn format_amount(amount: &Amount) -> String {
format!("{} {}", amount.number, amount.currency)
}
pub fn format_cost_spec(spec: &CostSpec) -> 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 {
match spec.number {
Some(crate::CostNumber::PerUnit { value: num }) => {
parts.push(format!("{num} {curr}"));
}
Some(crate::CostNumber::PerUnitFromTotal(b)) => {
parts.push(format!("{} {curr}", b.per_unit));
}
Some(crate::CostNumber::Total { value: num }) => {
parts.push(format!("{num} {curr}"));
}
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) -> String {
let sigil = price.kind.to_string();
match &price.amount {
Some(crate::IncompleteAmount::Complete(amt)) => {
format!("{sigil} {}", format_amount(amt))
}
Some(inc) => format!("{sigil} {}", format_incomplete_amount(inc)),
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), "{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), "{{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), "{150 USD}");
}
#[test]
fn cost_spec_empty_renders_braces() {
let spec = CostSpec::empty();
assert_eq!(format_cost_spec(&spec), "{}");
}
#[test]
fn cost_spec_currency_only_renders_bare() {
let spec = CostSpec::empty().with_currency("USD");
assert_eq!(format_cost_spec(&spec), "{}");
}
#[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),
"{{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),
"{150 USD, 2024-01-15, \"lot1\", *}"
);
}
}