hledger_parser/directive/transaction/posting/
assertion.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use chumsky::prelude::*;

use crate::component::amount::{amount, Amount};
use crate::component::price::{amount_price, AmountPrice};
use crate::component::whitespace::whitespace;
use crate::state::State;

#[derive(Clone, Debug, PartialEq)]
pub struct Assertion {
    pub is_strict: bool,
    pub is_subaccount_inclusive: bool,
    pub amount: Amount,
    pub price: Option<AmountPrice>,
}

pub fn assertion<'a>() -> impl Parser<'a, &'a str, Assertion, extra::Full<Rich<'a, char>, State, ()>>
{
    let price = whitespace().repeated().ignore_then(amount_price());
    just("=")
        .repeated()
        .at_least(1)
        .at_most(2)
        .collect::<Vec<_>>()
        .then(just("*").or_not())
        .then_ignore(whitespace().repeated())
        .then(amount())
        .then(price.or_not())
        .map(
            |(((assertion_type, subaccount_inclusive), amount), price)| Assertion {
                is_strict: assertion_type.len() == 2,
                is_subaccount_inclusive: subaccount_inclusive.is_some(),
                amount,
                price,
            },
        )
}

#[cfg(test)]
mod tests {
    use rust_decimal::Decimal;

    use super::*;

    #[test]
    fn single_with_price() {
        let result = assertion()
            .then_ignore(end())
            .parse("=1$ @@ 5 USD")
            .into_result();
        assert_eq!(
            result,
            Ok(Assertion {
                is_strict: false,
                is_subaccount_inclusive: false,
                amount: Amount {
                    commodity: String::from("$"),
                    quantity: Decimal::new(1, 0),
                },
                price: Some(AmountPrice::Total(Amount {
                    commodity: String::from("USD"),
                    quantity: Decimal::new(5, 0),
                })),
            })
        );
    }

    #[test]
    fn single() {
        let result = assertion().then_ignore(end()).parse("=1$").into_result();
        assert_eq!(
            result,
            Ok(Assertion {
                is_strict: false,
                is_subaccount_inclusive: false,
                amount: Amount {
                    commodity: String::from("$"),
                    quantity: Decimal::new(1, 0),
                },
                price: None,
            })
        );
    }

    #[test]
    fn single_inclusive() {
        let result = assertion().then_ignore(end()).parse("=*1$").into_result();
        assert_eq!(
            result,
            Ok(Assertion {
                is_strict: false,
                is_subaccount_inclusive: true,
                amount: Amount {
                    commodity: String::from("$"),
                    quantity: Decimal::new(1, 0),
                },
                price: None,
            })
        );
    }

    #[test]
    fn strict() {
        let result = assertion().then_ignore(end()).parse("== 1$").into_result();
        assert_eq!(
            result,
            Ok(Assertion {
                is_strict: true,
                is_subaccount_inclusive: false,
                amount: Amount {
                    commodity: String::from("$"),
                    quantity: Decimal::new(1, 0),
                },
                price: None,
            })
        );
    }

    #[test]
    fn strict_inclusive() {
        let result = assertion().then_ignore(end()).parse("==* 1$").into_result();
        assert_eq!(
            result,
            Ok(Assertion {
                is_strict: true,
                is_subaccount_inclusive: true,
                amount: Amount {
                    commodity: String::from("$"),
                    quantity: Decimal::new(1, 0),
                },
                price: None,
            })
        );
    }
}