use self::beancount::{
data::{
meta::KV, meta_value, Amount, Balance, Close, Commodity, Directive, Document, Error, Event,
MetaValue, Note, Open, Pad, Posting, Price, Query, Transaction,
},
date::Date,
inter::{CostSpec, PriceSpec},
ledger::Ledger,
number::Number,
options::{options::ProcessingMode, processing_info::Plugin, AccountTypes, Booking, Options},
};
use ::beancount_parser_lima as lima;
use derive_more::Display;
use lima::{BeancountParser, BeancountSources, OptionalItem, ParseError, ParseSuccess};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::{
collections::{HashMap, HashSet},
env,
fmt::Display,
fs::read_to_string,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
};
fn check(sources: &BeancountSources, parser: &BeancountParser, expected_ledger: Ledger) {
let stderr = &std::io::stderr();
let expected_directives = expected_ledger.directives;
let expected_options = expected_ledger.options.as_ref();
let expected_plugins = expected_ledger.info.as_ref().map(|info| &info.plugin);
let expected_errors = expected_ledger.errors;
match parser.parse() {
Ok(ParseSuccess {
directives,
options,
plugins,
..
}) => {
for (i, (actual, expected)) in directives
.iter()
.zip(expected_directives.iter())
.enumerate()
{
actual.expect_eq(expected, context(format!("directives[{}]", i)));
}
assert_eq!(
directives.len(),
expected_directives.len(),
"directives.len()"
);
let default_options = Options::default();
options.expect_eq(
expected_options.unwrap_or(&default_options),
context("options"),
);
let default_plugins = Vec::default();
plugins.expect_eq(
expected_plugins.unwrap_or(&default_plugins),
context("options"),
);
assert_eq!(
0,
expected_errors.len(),
"expected {} errors, found none",
expected_errors.len()
);
}
Err(ParseError { errors, .. }) => {
let n_errors = errors.len();
if n_errors == expected_errors.len() {
for (i, (actual, expected)) in errors
.into_iter()
.zip(expected_errors.into_iter())
.enumerate()
{
if Some(actual.message()) != expected.message.as_deref() {
let actual_message = actual.message().to_string();
sources.write(stderr, vec![actual]).unwrap();
panic!(
"expected '{}' found '{}' at errors[{}]",
expected.message.as_deref().unwrap_or(""),
actual_message,
i,
);
}
}
} else {
sources.write(stderr, errors).unwrap();
panic!(
"parse failed with {} errors, expected {}",
n_errors,
expected_errors.len()
);
}
}
}
}
fn create_sources_and_check
(input_path: P, expected_ledger: Ledger)
where
P: AsRef,
{
let sources = BeancountSources::try_from(input_path.as_ref()).unwrap();
let parser = BeancountParser::new(&sources);
check(&sources, &parser, expected_ledger);
}
pub fn check_parse(test_name: S)
where
S: AsRef,
{
let cargo_manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into();
// unwrap here is safe because we know the repo structure, so there definitely is a parent
let testcase_dir = cargo_manifest_dir.parent().unwrap().join("test-cases");
let input_file: PathBuf = Into::::into(format!("{}.beancount", test_name.as_ref()));
let input_path = testcase_dir.join(input_file);
let expected_output_file: PathBuf =
Into::::into(format!("{}.txtpb", test_name.as_ref()));
let expected_output_path = testcase_dir.join(expected_output_file);
let expected_output = read_to_string(&expected_output_path).unwrap_or_else(|_| {
panic!(
"failed to read expected output from {:?}",
&expected_output_path
)
});
let expected_output_ledger: Ledger = protobuf::text_format::parse_from_str(&expected_output)
.unwrap_or_else(|e| {
panic!(
"failed to parse Protobuf Text Format in {:?}: {}",
&expected_output_path, e
)
});
create_sources_and_check(input_path, expected_output_ledger);
}
#[derive(Clone, Debug)]
struct ContextChain {
label: String,
parent: Option,
}
#[derive(Clone, Debug)]
struct Context(Rc);
fn context(label: S) -> Context
where
S: AsRef,
{
Context(Rc::new(ContextChain {
label: label.as_ref().to_owned(),
parent: None,
}))
}
impl Context {
fn with(&self, label: S) -> Context
where
S: AsRef,
{
Context(Rc::new(ContextChain {
label: label.as_ref().to_owned(),
parent: Some(Context(self.0.clone())),
}))
}
}
impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(parent) = &self.0.parent {
write!(f, "{}.{}", parent, self.0.label)
} else {
write!(f, "{}", self.0.label)
}
}
}
trait ExpectEq
where
Rhs: ?Sized,
{
fn expect_eq(&self, expected: &Rhs, ctx: Context);
}
impl<'a, Rhs, T> ExpectEq for &'a T
where
T: ExpectEq,
{
fn expect_eq(&self, expected: &Rhs, ctx: Context) {
(*self).expect_eq(expected, ctx);
}
}
impl<'r, 't, Rhs, T> ExpectEq