beancount_parser_lima/
lib.rs

1use ::beancount_parser_lima as lima;
2use pyo3::prelude::*;
3use pyo3::types::PyList;
4use std::io::{self, prelude::*};
5use std::path::PathBuf;
6use types::Options;
7
8/// Python wrapper for BeancountSources
9#[derive(Debug)]
10#[pyclass(frozen)]
11pub struct BeancountSources(lima::BeancountSources);
12
13#[pymethods]
14impl BeancountSources {
15    #[new]
16    fn new(path: &str) -> PyResult<Self> {
17        let sources = lima::BeancountSources::try_from(PathBuf::from(path))?;
18        Ok(BeancountSources(sources))
19    }
20
21    fn parse(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
22        let mut stderr = &io::stderr();
23
24        let parser = lima::BeancountParser::new(&self.0);
25        // TODO remove
26        writeln!(stderr, "{:?}", &self.0).unwrap();
27
28        parse(py, &parser)
29    }
30
31    // Would be nice to be able to pass in a File object from Python, but this:
32    // https://github.com/PyO3/pyo3/issues/933
33    fn write(&self, py: Python<'_>, errors_and_warnings: Py<PyList>) -> PyResult<()> {
34        let errors_and_warnings = errors_and_warnings.bind(py);
35        let mut errors = Vec::new();
36        let mut warnings = Vec::new();
37
38        for x in errors_and_warnings.iter() {
39            match Error::extract_bound(&x) {
40                Ok(e) => errors.push(e.0),
41                Err(_) => match Warning::extract_bound(&x) {
42                    Ok(w) => warnings.push(w.0),
43                    Err(_) => {
44                        eprintln!("Failed to extract error or warning");
45                    }
46                },
47            }
48        }
49
50        let stderr = &io::stderr();
51        self.0.write(stderr, errors).map_err(PyErr::from)?;
52        self.0.write(stderr, warnings).map_err(PyErr::from)
53    }
54}
55
56/// The Python module in Rust.
57#[pymodule]
58fn beancount_parser_lima(m: &Bound<'_, PyModule>) -> PyResult<()> {
59    m.add_class::<BeancountSources>()?;
60    m.add_class::<ParseSuccess>()?;
61    m.add_class::<ParseError>()?;
62
63    Ok(())
64}
65
66fn parse(py: Python<'_>, parser: &lima::BeancountParser) -> PyResult<Py<PyAny>> {
67    match parser.parse() {
68        Ok(lima::ParseSuccess {
69            directives,
70            options,
71            plugins,
72            warnings,
73        }) => {
74            use lima::DirectiveVariant as V;
75
76            let mut c = Converter::new();
77
78            let directives = directives
79                .into_iter()
80                .map(|d| match d.variant() {
81                    V::Transaction(x) => c.transaction(py, d.date(), d.metadata(), x),
82                    V::Price(x) => c.price(py, d.date(), d.metadata(), x),
83                    V::Balance(x) => c.balance(py, d.date(), d.metadata(), x),
84                    V::Open(x) => c.open(py, d.date(), d.metadata(), x),
85                    V::Close(x) => c.close(py, d.date(), d.metadata(), x),
86                    V::Commodity(x) => c.commodity(py, d.date(), d.metadata(), x),
87                    V::Pad(x) => c.pad(py, d.date(), d.metadata(), x),
88                    V::Document(x) => c.document(py, d.date(), d.metadata(), x),
89                    V::Note(x) => c.note(py, d.date(), d.metadata(), x),
90                    V::Event(x) => c.event(py, d.date(), d.metadata(), x),
91                    V::Query(x) => c.query(py, d.date(), d.metadata(), x),
92                })
93                .collect::<PyResult<Vec<Py<PyAny>>>>()?;
94
95            let options = c.options(py, &options)?;
96            let plugins = plugins
97                .iter()
98                .map(|x| c.plugin(py, x))
99                .collect::<PyResult<Vec<Plugin>>>()?;
100
101            let warnings = warnings.into_iter().map(Warning::new).collect::<Vec<_>>();
102
103            Ok(Py::new(
104                py,
105                (
106                    ParseSuccess {
107                        directives,
108                        options,
109                        plugins,
110                        warnings,
111                    },
112                    ParseResult {},
113                ),
114            )?
115            .into_any())
116        }
117
118        Err(lima::ParseError { errors, warnings }) => {
119            let errors = errors.into_iter().map(Error::new).collect::<Vec<_>>();
120            let warnings = warnings.into_iter().map(Warning::new).collect::<Vec<_>>();
121
122            Ok(Py::new(py, (ParseError { errors, warnings }, ParseResult {}))?.into_any())
123        }
124    }
125}
126
127/// Success or fail base class
128#[derive(Debug)]
129#[pyclass(frozen, subclass)]
130pub struct ParseResult {}
131
132/// Successful parse result
133#[derive(Debug)]
134#[pyclass(frozen, extends=ParseResult)]
135pub struct ParseSuccess {
136    #[pyo3(get)]
137    pub(crate) directives: Vec<Py<PyAny>>,
138    #[pyo3(get)]
139    pub(crate) options: Options,
140    #[pyo3(get)]
141    pub(crate) plugins: Vec<Plugin>,
142    #[pyo3(get)]
143    pub(crate) warnings: Vec<Warning>,
144}
145
146/// Failed parse result
147#[derive(Debug)]
148#[pyclass(frozen, extends=ParseResult)]
149pub struct ParseError {
150    #[pyo3(get)]
151    pub(crate) errors: Vec<Error>,
152    #[pyo3(get)]
153    pub(crate) warnings: Vec<Warning>,
154}
155
156/// Wrapper for lima::Error
157#[derive(Clone, Debug)]
158#[pyclass(frozen)]
159pub struct Error(lima::Error);
160
161impl Error {
162    fn new(e: lima::Error) -> Self {
163        Error(e)
164    }
165}
166
167/// Wrapper for lima::Warning
168#[derive(Clone, Debug)]
169#[pyclass(frozen)]
170pub struct Warning(lima::Warning);
171
172impl Warning {
173    fn new(w: lima::Warning) -> Self {
174        Warning(w)
175    }
176}
177
178mod conversions;
179use conversions::Converter;
180
181use crate::types::Plugin;
182mod types;