use std::borrow::Borrow;
use std::{
collections::HashSet,
fmt::{Display, Formatter},
sync::Arc,
};
use nom::{
branch::alt,
bytes::{complete::tag, complete::take_while},
character::complete::{char, satisfy, space0, space1},
combinator::{cut, iterator, opt, recognize},
multi::many1_count,
sequence::{delimited, preceded},
};
use crate::{
amount::{self, Amount, Currency},
Decimal,
};
use super::{IResult, Span};
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Account(Arc<str>);
impl Account {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for Account {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl AsRef<str> for Account {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Borrow<str> for Account {
fn borrow(&self) -> &str {
self.0.borrow()
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Open {
pub account: Account,
pub currencies: HashSet<Currency>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Close {
pub account: Account,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Balance<D> {
pub account: Account,
pub amount: Amount<D>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Pad {
pub account: Account,
pub source_account: Account,
}
pub(super) fn parse(input: Span<'_>) -> IResult<'_, Account> {
let (input, name) = recognize(preceded(
alt((
tag("Expenses"),
tag("Assets"),
tag("Liabilities"),
tag("Income"),
tag("Equity"),
)),
cut(many1_count(preceded(
char(':'),
preceded(
satisfy(|c: char| c.is_uppercase() || c.is_ascii_digit()),
take_while(|c: char| c.is_alphanumeric() || c == '-'),
),
))),
))(input)?;
Ok((input, Account(Arc::from(*name.fragment()))))
}
pub(super) fn open(input: Span<'_>) -> IResult<'_, Open> {
let (input, account) = parse(input)?;
let (input, _) = space0(input)?;
let (input, currencies) = opt(currencies)(input)?;
Ok((
input,
Open {
account,
currencies: currencies.unwrap_or_default(),
},
))
}
fn currencies(input: Span<'_>) -> IResult<'_, HashSet<Currency>> {
let (input, first) = amount::currency(input)?;
let sep = delimited(space0, char(','), space0);
let mut iter = iterator(input, preceded(sep, amount::currency));
let mut currencies = HashSet::new();
currencies.insert(first);
currencies.extend(iter.into_iter());
let (input, _) = iter.finish()?;
Ok((input, currencies))
}
pub(super) fn close(input: Span<'_>) -> IResult<'_, Close> {
let (input, account) = parse(input)?;
Ok((input, Close { account }))
}
pub(super) fn balance<D: Decimal>(input: Span<'_>) -> IResult<'_, Balance<D>> {
let (input, account) = parse(input)?;
let (input, _) = space1(input)?;
let (input, amount) = amount::parse(input)?;
Ok((input, Balance { account, amount }))
}
pub(super) fn pad(input: Span<'_>) -> IResult<'_, Pad> {
let (input, account) = parse(input)?;
let (input, _) = space1(input)?;
let (input, source_account) = parse(input)?;
Ok((
input,
Pad {
account,
source_account,
},
))
}