hledger_parser/directive/
auto_postings.rs

1mod query;
2
3use chumsky::prelude::*;
4
5use crate::component::account_name::account_name;
6use crate::component::amount::{amount, Amount};
7use crate::component::comment::inline;
8use crate::component::whitespace::whitespace;
9use crate::directive::auto_postings::query::query;
10use crate::state::State;
11use crate::utils::end_of_line;
12
13pub use crate::directive::auto_postings::query::{Query, Term};
14
15#[derive(Clone, Debug, PartialEq)]
16pub struct AutosPostingRule {
17    pub query: Query,
18    pub postings: Vec<AutoPosting>,
19}
20
21#[derive(Clone, Debug, PartialEq)]
22pub struct AutoPosting {
23    pub account_name: Vec<String>,
24    pub is_virtual: bool,
25    pub amount: Amount,
26    pub is_mul: bool,
27}
28
29pub fn auto_postings<'a>(
30) -> impl Parser<'a, &'a str, AutosPostingRule, extra::Full<Rich<'a, char>, State, ()>> {
31    let header = just("=")
32        .ignore_then(whitespace().repeated())
33        .ignore_then(query().then_ignore(end_of_line()))
34        .then_ignore(text::newline());
35
36    let account_name = account_name()
37        .delimited_by(just('('), just(')'))
38        .map(|name| (name, true))
39        .or(account_name().map(|name| (name, false)));
40    let posting = whitespace()
41        .repeated()
42        .at_least(1)
43        .ignore_then(account_name)
44        .then_ignore(whitespace().repeated().at_least(2))
45        .then(just("*").or_not())
46        .then(amount())
47        .then_ignore(end_of_line())
48        .map(
49            |(((account_name, is_virtual), is_mul), amount)| AutoPosting {
50                account_name,
51                is_virtual,
52                amount,
53                is_mul: is_mul.is_some(),
54            },
55        );
56
57    header
58        .then_ignore(
59            text::whitespace()
60                .at_least(1)
61                .then(inline())
62                .then_ignore(text::newline())
63                .or_not(),
64        )
65        .then(
66            posting
67                .separated_by(text::newline())
68                .at_least(2)
69                .collect::<Vec<_>>(),
70        )
71        .map(|(query, postings)| AutosPostingRule { query, postings })
72}
73
74#[cfg(test)]
75mod tests {
76    use rust_decimal::Decimal;
77
78    use self::tests::query::Term;
79
80    use super::*;
81
82    #[test]
83    fn full() {
84        let result = auto_postings()
85            .then_ignore(end())
86            .parse(
87                "= expenses:gifts
88    assets:checking:gifts  *-1$
89    (assets:checking)         1",
90            )
91            .into_result();
92        assert_eq!(
93            result,
94            Ok(AutosPostingRule {
95                query: Query {
96                    terms: vec![Term {
97                        r#type: None,
98                        is_not: false,
99                        value: String::from("expenses:gifts"),
100                    }],
101                },
102                postings: vec![
103                    AutoPosting {
104                        account_name: vec![
105                            String::from("assets"),
106                            String::from("checking"),
107                            String::from("gifts")
108                        ],
109                        is_virtual: false,
110                        is_mul: true,
111                        amount: Amount {
112                            quantity: Decimal::new(-1, 0),
113                            commodity: String::from("$"),
114                        },
115                    },
116                    AutoPosting {
117                        account_name: vec![String::from("assets"), String::from("checking"),],
118                        is_virtual: true,
119                        is_mul: false,
120                        amount: Amount {
121                            quantity: Decimal::new(1, 0),
122                            commodity: String::new(),
123                        },
124                    }
125                ],
126            })
127        );
128    }
129}