hledger_parser/directive/transaction/posting/
assertion.rs1use 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}