hledger_parser/directive/
auto_postings.rs1mod 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}