use std::{
borrow::Borrow,
collections::HashSet,
fmt::{Display, Formatter},
str::FromStr,
sync::Arc,
};
use nom::{
bytes::complete::take_while,
character::complete::{char, satisfy, space0, space1},
combinator::{all_consuming, cut, iterator, opt, recognize},
multi::many1_count,
sequence::{delimited, preceded},
Finish, Parser,
};
use crate::{
amount::{self, Amount, Currency},
Decimal, Span,
};
use super::IResult;
#[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()
}
}
impl FromStr for Account {
type Err = crate::Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let spanned = Span::new(input);
match all_consuming(parse).parse(spanned).finish() {
Ok((_, account)) => Ok(account),
Err(_) => Err(Self::Err::new(input, spanned)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Open {
pub account: Account,
pub currencies: HashSet<Currency>,
pub booking_method: Option<BookingMethod>,
}
impl Open {
#[must_use]
pub fn from_account(account: Account) -> Self {
Open {
account,
currencies: HashSet::new(),
booking_method: None,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct BookingMethod(Arc<str>);
impl AsRef<str> for BookingMethod {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Borrow<str> for BookingMethod {
fn borrow(&self) -> &str {
self.0.borrow()
}
}
impl Display for BookingMethod {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl From<&str> for BookingMethod {
fn from(value: &str) -> Self {
Self(Arc::from(value))
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Close {
pub account: Account,
}
impl Close {
#[must_use]
pub fn from_account(account: Account) -> Self {
Close { account }
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Balance<D> {
pub account: Account,
pub amount: Amount<D>,
pub tolerance: Option<D>,
}
impl<D> Balance<D> {
pub fn new(account: Account, amount: Amount<D>) -> Self {
Balance {
account,
amount,
tolerance: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Pad {
pub account: Account,
pub source_account: Account,
}
impl Pad {
#[must_use]
pub fn new(account: Account, source_account: Account) -> Self {
Pad {
account,
source_account,
}
}
}
pub(super) fn parse(input: Span<'_>) -> IResult<'_, Account> {
let (input, name) = recognize(preceded(
preceded(
satisfy(|c: char| c.is_uppercase() || c.is_ascii_digit()),
take_while(|c: char| c.is_alphanumeric() || c == '-'),
),
cut(many1_count(preceded(
char(':'),
preceded(
satisfy(|c: char| c.is_uppercase() || c.is_ascii_digit()),
take_while(|c: char| c.is_alphanumeric() || c == '-'),
),
))),
))
.parse(input)?;
Ok((input, Account(Arc::from(*name.fragment()))))
}
pub(super) fn open(input: Span<'_>) -> IResult<'_, Open> {
let (input, account) = parse(input)?;
let (input, currencies) = opt(preceded(space1, currencies)).parse(input)?;
let (input, booking_method) = opt(preceded(space1, crate::string)).parse(input)?;
Ok((
input,
Open {
account,
currencies: currencies.unwrap_or_default(),
booking_method: booking_method.map(|s| s.as_str().into()),
},
))
}
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(&mut 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, value) = amount::expression(input)?;
let (input, tolerance) = opt(preceded(space0, tolerance)).parse(input)?;
let (input, _) = space1(input)?;
let (input, currency) = amount::currency(input)?;
Ok((
input,
Balance {
account,
amount: Amount { value, currency },
tolerance,
},
))
}
fn tolerance<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
let (input, _) = char('~')(input)?;
let (input, _) = space0(input)?;
let (input, tolerance) = amount::expression(input)?;
Ok((input, tolerance))
}
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,
},
))
}