hledger_parser/directive/transaction/posting/
assertion.rs

1use chumsky::prelude::*;
2
3use crate::component::amount::{amount, Amount};
4use crate::component::price::{amount_price, AmountPrice};
5use crate::component::whitespace::whitespace;
6use crate::state::State;
7
8#[derive(Clone, Debug, PartialEq)]
9pub struct Assertion {
10    pub is_strict: bool,
11    pub is_subaccount_inclusive: bool,
12    pub amount: Amount,
13    pub price: Option<AmountPrice>,
14}
15
16pub fn assertion<'a>() -> impl Parser<'a, &'a str, Assertion, extra::Full<Rich<'a, char>, State, ()>>
17{
18    let price = whitespace().repeated().ignore_then(amount_price());
19    just("=")
20        .repeated()
21        .at_least(1)
22        .at_most(2)
23        .collect::<Vec<_>>()
24        .then(just("*").or_not())
25        .then_ignore(whitespace().repeated())
26        .then(amount())
27        .then(price.or_not())
28        .map(
29            |(((assertion_type, subaccount_inclusive), amount), price)| Assertion {
30                is_strict: assertion_type.len() == 2,
31                is_subaccount_inclusive: subaccount_inclusive.is_some(),
32                amount,
33                price,
34            },
35        )
36}
37
38#[cfg(test)]
39mod tests {
40    use rust_decimal::Decimal;
41
42    use super::*;
43
44    #[test]
45    fn single_with_price() {
46        let result = assertion()
47            .then_ignore(end())
48            .parse("=1$ @@ 5 USD")
49            .into_result();
50        assert_eq!(
51            result,
52            Ok(Assertion {
53                is_strict: false,
54                is_subaccount_inclusive: false,
55                amount: Amount {
56                    commodity: String::from("$"),
57                    quantity: Decimal::new(1, 0),
58                },
59                price: Some(AmountPrice::Total(Amount {
60                    commodity: String::from("USD"),
61                    quantity: Decimal::new(5, 0),
62                })),
63            })
64        );
65    }
66
67    #[test]
68    fn single() {
69        let result = assertion().then_ignore(end()).parse("=1$").into_result();
70        assert_eq!(
71            result,
72            Ok(Assertion {
73                is_strict: false,
74                is_subaccount_inclusive: false,
75                amount: Amount {
76                    commodity: String::from("$"),
77                    quantity: Decimal::new(1, 0),
78                },
79                price: None,
80            })
81        );
82    }
83
84    #[test]
85    fn single_inclusive() {
86        let result = assertion().then_ignore(end()).parse("=*1$").into_result();
87        assert_eq!(
88            result,
89            Ok(Assertion {
90                is_strict: false,
91                is_subaccount_inclusive: true,
92                amount: Amount {
93                    commodity: String::from("$"),
94                    quantity: Decimal::new(1, 0),
95                },
96                price: None,
97            })
98        );
99    }
100
101    #[test]
102    fn strict() {
103        let result = assertion().then_ignore(end()).parse("== 1$").into_result();
104        assert_eq!(
105            result,
106            Ok(Assertion {
107                is_strict: true,
108                is_subaccount_inclusive: false,
109                amount: Amount {
110                    commodity: String::from("$"),
111                    quantity: Decimal::new(1, 0),
112                },
113                price: None,
114            })
115        );
116    }
117
118    #[test]
119    fn strict_inclusive() {
120        let result = assertion().then_ignore(end()).parse("==* 1$").into_result();
121        assert_eq!(
122            result,
123            Ok(Assertion {
124                is_strict: true,
125                is_subaccount_inclusive: true,
126                amount: Amount {
127                    commodity: String::from("$"),
128                    quantity: Decimal::new(1, 0),
129                },
130                price: None,
131            })
132        );
133    }
134}