bean_rs/
loader.rs

1use std::cmp::Ordering;
2
3use log::debug;
4use pest::iterators::Pairs;
5use pest::Parser;
6
7use crate::data::{self, DebugLine};
8use crate::data::{Directive, Options};
9use crate::error::{BeanError, ErrorType};
10use crate::grammar::{BeanParser, Rule};
11use crate::ledger::Ledger;
12use crate::utils;
13
14/// Parse the text using Pest
15pub fn load(data: &str) -> Pairs<'_, Rule> {
16    // The grammer has a badline option which will consume any nonsense
17    // So in theory this shouldn't error!
18    let mut entries = BeanParser::parse(Rule::root, data).unwrap();
19
20    // There will always be at least an `EOI`, so this will also never error
21    let entry = entries.next().unwrap();
22    utils::debug_pair(&entry, 0);
23    entry.into_inner()
24}
25
26/// Convert the AST Pest Pairs into a Vec of Directives
27pub fn consume(entries: Pairs<'_, Rule>) -> Ledger {
28    let mut errs: Vec<BeanError> = Vec::with_capacity(entries.len());
29    let mut dirs: Vec<Directive> = Vec::new();
30    let mut opts = Options::default();
31    for entry in entries {
32        debug!("{:?}\t{:?}", entry.as_rule(), entry.as_span(),);
33        match entry.as_rule() {
34            Rule::option => {
35                opts.update_from_entry(entry);
36            }
37            Rule::custom => {
38                dirs.push(Directive::ConfigCustom(data::ConfigCustom::from_entry(
39                    entry,
40                )));
41            }
42            Rule::query => {
43                dirs.push(Directive::Query(data::Query::from_entry(entry)));
44            }
45            Rule::commodity => {
46                dirs.push(Directive::Commodity(data::Commodity::from_entry(entry)));
47            }
48            Rule::open => {
49                dirs.push(Directive::Open(data::Open::from_entry(entry)));
50            }
51            Rule::close => {
52                dirs.push(Directive::Close(data::Close::from_entry(entry)));
53            }
54            Rule::balance => {
55                dirs.push(Directive::Balance(data::Balance::from_entry(entry)));
56            }
57            Rule::pad => {
58                dirs.push(Directive::Pad(data::Pad::from_entry(entry)));
59            }
60            Rule::price => {
61                dirs.push(Directive::Price(data::Price::from_entry(entry)));
62            }
63            Rule::document => {
64                dirs.push(Directive::Document(data::Document::from_entry(entry)));
65            }
66            Rule::note => {
67                dirs.push(Directive::Note(data::Note::from_entry(entry)));
68            }
69            Rule::transaction => {
70                dirs.push(Directive::Transaction(data::Transaction::from_entry(entry)));
71            }
72            Rule::EOI => {
73                debug!("Hit EOI");
74            }
75            Rule::badline => {
76                let (line, _) = entry.line_col();
77                let debug = DebugLine::new(line);
78                let err =
79                    BeanError::new(ErrorType::Badline, &debug, "Found unparseable line", None);
80                errs.push(err);
81            }
82            _ => {
83                let (l, _) = entry.line_col();
84                let debug = DebugLine::new(l);
85                unreachable!("Found unexpected entry in file, abort.\n{debug}");
86            }
87        };
88    }
89    Ledger { dirs, errs, opts }
90}
91
92/// Sort the Directives by date and `order` inplace
93pub fn sort(dirs: &mut [Directive]) {
94    dirs.sort_by(|a, b| match a.date().cmp(b.date()) {
95        Ordering::Equal => a.order().cmp(&b.order()),
96        other => other,
97    });
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    #[test]
104    fn test_parse() {
105        let text = r#"2023-01-01 open Assets:Bank GBP"#;
106        let entries = load(&text);
107        let Ledger {
108            dirs,
109            errs: _,
110            opts: _,
111        } = consume(entries);
112        let got = &dirs[0];
113        match got {
114            Directive::Open(_) => (),
115            _ => assert!(false, "Found wrong directive type"),
116        }
117    }
118
119    #[test]
120    fn test_bad_consume() {
121        let text = r#"
122            2023-01-01 foo
123        "#;
124        let entries = load(&text);
125        let Ledger {
126            dirs: _,
127            errs,
128            opts: _,
129        } = consume(entries);
130        assert!(errs.len() == 1);
131    }
132
133    #[test]
134    fn test_consume() {
135        let text = r#"
136            option "operating_currency" "GBP"
137        "#;
138        let entries = load(&text);
139        consume(entries);
140    }
141}