1use std::{
2 borrow::Borrow,
3 collections::HashSet,
4 fmt::{Display, Formatter},
5 str::FromStr,
6 sync::Arc,
7};
8
9use nom::{
10 bytes::complete::take_while,
11 character::complete::{char, satisfy, space0, space1},
12 combinator::{all_consuming, cut, iterator, opt, recognize},
13 multi::many1_count,
14 sequence::{delimited, preceded},
15 Finish, Parser,
16};
17
18use crate::{
19 amount::{self, Amount, Currency},
20 Decimal, Span,
21};
22
23use super::IResult;
24
25#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
38pub struct Account(Arc<str>);
39
40impl Account {
41 #[must_use]
43 pub fn as_str(&self) -> &str {
44 &self.0
45 }
46}
47
48impl Display for Account {
49 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50 Display::fmt(&self.0, f)
51 }
52}
53
54impl AsRef<str> for Account {
55 fn as_ref(&self) -> &str {
56 self.0.as_ref()
57 }
58}
59
60impl Borrow<str> for Account {
61 fn borrow(&self) -> &str {
62 self.0.borrow()
63 }
64}
65
66impl FromStr for Account {
67 type Err = crate::Error;
68
69 fn from_str(input: &str) -> Result<Self, Self::Err> {
70 let spanned = Span::new(input);
71 match all_consuming(parse).parse(spanned).finish() {
72 Ok((_, account)) => Ok(account),
73 Err(_) => Err(Self::Err::new(input, spanned)),
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq)]
90#[non_exhaustive]
91pub struct Open {
92 pub account: Account,
94 pub currencies: HashSet<Currency>,
96 pub booking_method: Option<BookingMethod>,
98}
99
100impl Open {
101 #[must_use]
103 pub fn from_account(account: Account) -> Self {
104 Open {
105 account,
106 currencies: HashSet::new(),
107 booking_method: None,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
113pub struct BookingMethod(Arc<str>);
114
115impl AsRef<str> for BookingMethod {
116 fn as_ref(&self) -> &str {
117 &self.0
118 }
119}
120
121impl Borrow<str> for BookingMethod {
122 fn borrow(&self) -> &str {
123 self.0.borrow()
124 }
125}
126
127impl Display for BookingMethod {
128 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129 Display::fmt(&self.0, f)
130 }
131}
132
133impl From<&str> for BookingMethod {
134 fn from(value: &str) -> Self {
135 Self(Arc::from(value))
136 }
137}
138
139#[derive(Debug, Clone, PartialEq)]
150#[non_exhaustive]
151pub struct Close {
152 pub account: Account,
154}
155
156impl Close {
157 #[must_use]
159 pub fn from_account(account: Account) -> Self {
160 Close { account }
161 }
162}
163
164#[derive(Debug, Clone, PartialEq)]
177#[non_exhaustive]
178pub struct Balance<D> {
179 pub account: Account,
181 pub amount: Amount<D>,
183 pub tolerance: Option<D>,
187}
188
189impl<D> Balance<D> {
190 pub fn new(account: Account, amount: Amount<D>) -> Self {
192 Balance {
193 account,
194 amount,
195 tolerance: None,
196 }
197 }
198}
199
200#[derive(Debug, Clone, PartialEq)]
212#[non_exhaustive]
213pub struct Pad {
214 pub account: Account,
216 pub source_account: Account,
218}
219
220impl Pad {
221 #[must_use]
223 pub fn new(account: Account, source_account: Account) -> Self {
224 Pad {
225 account,
226 source_account,
227 }
228 }
229}
230
231pub(super) fn parse(input: Span<'_>) -> IResult<'_, Account> {
232 let (input, name) = recognize(preceded(
233 preceded(
234 satisfy(|c: char| c.is_uppercase() || c.is_ascii_digit()),
235 take_while(|c: char| c.is_alphanumeric() || c == '-'),
236 ),
237 cut(many1_count(preceded(
238 char(':'),
239 preceded(
240 satisfy(|c: char| c.is_uppercase() || c.is_ascii_digit()),
241 take_while(|c: char| c.is_alphanumeric() || c == '-'),
242 ),
243 ))),
244 ))
245 .parse(input)?;
246 Ok((input, Account(Arc::from(*name.fragment()))))
247}
248
249pub(super) fn open(input: Span<'_>) -> IResult<'_, Open> {
250 let (input, account) = parse(input)?;
251 let (input, currencies) = opt(preceded(space1, currencies)).parse(input)?;
252 let (input, booking_method) = opt(preceded(space1, crate::string)).parse(input)?;
253 Ok((
254 input,
255 Open {
256 account,
257 currencies: currencies.unwrap_or_default(),
258 booking_method: booking_method.map(|s| s.as_str().into()),
259 },
260 ))
261}
262
263fn currencies(input: Span<'_>) -> IResult<'_, HashSet<Currency>> {
264 let (input, first) = amount::currency(input)?;
265 let sep = delimited(space0, char(','), space0);
266 let mut iter = iterator(input, preceded(sep, amount::currency));
267 let mut currencies = HashSet::new();
268 currencies.insert(first);
269 currencies.extend(&mut iter);
270 let (input, ()) = iter.finish()?;
271 Ok((input, currencies))
272}
273
274pub(super) fn close(input: Span<'_>) -> IResult<'_, Close> {
275 let (input, account) = parse(input)?;
276 Ok((input, Close { account }))
277}
278
279pub(super) fn balance<D: Decimal>(input: Span<'_>) -> IResult<'_, Balance<D>> {
280 let (input, account) = parse(input)?;
281 let (input, _) = space1(input)?;
282 let (input, value) = amount::expression(input)?;
283 let (input, tolerance) = opt(preceded(space0, tolerance)).parse(input)?;
284 let (input, _) = space1(input)?;
285 let (input, currency) = amount::currency(input)?;
286 Ok((
287 input,
288 Balance {
289 account,
290 amount: Amount { value, currency },
291 tolerance,
292 },
293 ))
294}
295
296fn tolerance<D: Decimal>(input: Span<'_>) -> IResult<'_, D> {
297 let (input, _) = char('~')(input)?;
298 let (input, _) = space0(input)?;
299 let (input, tolerance) = amount::expression(input)?;
300 Ok((input, tolerance))
301}
302
303pub(super) fn pad(input: Span<'_>) -> IResult<'_, Pad> {
304 let (input, account) = parse(input)?;
305 let (input, _) = space1(input)?;
306 let (input, source_account) = parse(input)?;
307 Ok((
308 input,
309 Pad {
310 account,
311 source_account,
312 },
313 ))
314}