use core::fmt;
use core::fmt::Display;
use core::fmt::Formatter;
use core::hash::Hash;
use beancount_amount::Amount;
use beancount_commodity::Commodity;
use rust_decimal::Decimal;
use time::Date;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CostBasis {
Empty,
PerUnit(Amount),
Total(Amount),
PerUnitAndFixed {
per_unit: Decimal,
total: Decimal,
commodity: Commodity,
},
}
impl CostBasis {
#[must_use]
pub const fn commodity(&self) -> Option<Commodity> {
match self {
Self::Empty => None,
Self::PerUnit(amount) | Self::Total(amount) => Some(amount.commodity),
Self::PerUnitAndFixed { commodity, .. } => Some(*commodity),
}
}
#[must_use]
pub const fn per_unit(&self) -> Option<Amount> {
match self {
Self::Empty | Self::Total(_) => None,
Self::PerUnit(amount) => Some(*amount),
Self::PerUnitAndFixed {
per_unit,
commodity,
..
} => Some(Amount::new(*per_unit, *commodity)),
}
}
#[must_use]
pub const fn total(&self) -> Option<Amount> {
match self {
Self::Empty | Self::PerUnit(_) => None,
Self::Total(amount) => Some(*amount),
Self::PerUnitAndFixed {
total, commodity, ..
} => Some(Amount::new(*total, *commodity)),
}
}
}
impl Display for CostBasis {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "{{}}"),
Self::PerUnit(amount) => write!(f, "{{{amount}}}"),
Self::Total(amount) => write!(f, "{{{{{amount}}}}}"),
Self::PerUnitAndFixed {
per_unit,
total,
commodity,
} => write!(f, "{{{per_unit} # {total} {commodity}}}"),
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CostSpec {
pub basis: CostBasis,
pub date: Option<Date>,
pub label: Option<String>,
}
impl CostSpec {
#[must_use]
pub const fn dated(date: Date) -> Self {
Self {
basis: CostBasis::Empty,
date: Some(date),
label: None,
}
}
#[must_use]
pub const fn empty() -> Self {
Self {
basis: CostBasis::Empty,
date: None,
label: None,
}
}
#[must_use]
pub const fn from_per_unit(per_unit: Decimal, commodity: Commodity) -> Self {
Self {
basis: CostBasis::PerUnit(Amount::new(per_unit, commodity)),
date: None,
label: None,
}
}
#[must_use]
pub const fn from_per_unit_and_fixed(
per_unit: Decimal,
total: Decimal,
commodity: Commodity,
) -> Self {
Self {
basis: CostBasis::PerUnitAndFixed {
per_unit,
total,
commodity,
},
date: None,
label: None,
}
}
#[must_use]
pub const fn from_total(total: Decimal, commodity: Commodity) -> Self {
Self {
basis: CostBasis::Total(Amount::new(total, commodity)),
date: None,
label: None,
}
}
#[must_use]
pub fn labelled(label: impl Into<String>) -> Self {
Self {
basis: CostBasis::Empty,
date: None,
label: Some(label.into()),
}
}
}
impl CostSpec {
pub fn set_label(&mut self, label: impl Into<String>) -> &mut Self {
self.label = Some(label.into());
self
}
}
impl CostSpec {
#![allow(clippy::inline_always)]
delegate::delegate! {
to self.basis {
#[must_use]
pub const fn commodity(&self) -> Option<Commodity>;
#[must_use]
pub const fn per_unit(&self) -> Option<Amount>;
#[must_use]
pub const fn total(&self) -> Option<Amount>;
}
}
}
impl Display for CostSpec {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut had_output = false;
let Self { basis, date, label } = self;
f.write_str("{")?;
match basis {
CostBasis::Empty => {}
CostBasis::PerUnit(amount) => {
write!(f, "{amount}")?;
had_output = true;
}
CostBasis::Total(amount) => {
write!(f, "# {amount}")?;
had_output = true;
}
CostBasis::PerUnitAndFixed {
per_unit,
total,
commodity,
} => {
write!(f, "{per_unit} # {total} {commodity}")?;
had_output = true;
}
}
if let Some(date) = date {
if had_output {
f.write_str(", ")?;
}
write!(f, "{date}")?;
}
if let Some(label) = label {
if had_output {
f.write_str(", ")?;
}
write!(f, "{label:?}")?;
}
f.write_str("}")
}
}
impl From<CostBasis> for CostSpec {
fn from(basis: CostBasis) -> Self {
Self {
basis,
date: None,
label: None,
}
}
}