use std::path::Path;
use wasm_bindgen::prelude::*;
use rustledger_core::Directive;
use rustledger_loader::{LoadOptions, Loader, VirtualFileSystem, process};
use rustledger_parser::{ParseResult as ParserResult, parse as parse_beancount};
use crate::types::{Error, LedgerOptions, Severity};
use crate::utils::LineLookup;
pub struct ProcessedLedger {
pub directives: Vec<Directive>,
pub options: LedgerOptions,
pub errors: Vec<Error>,
pub parse_result: ParserResult,
pub lookup: LineLookup,
}
pub fn load_and_book(source: &str) -> ProcessedLedger {
let parse_result = parse_beancount(source);
let lookup = LineLookup::new(source);
if !parse_result.errors.is_empty() {
let errors: Vec<Error> = parse_result
.errors
.iter()
.map(|e| Error::with_line(e.to_string(), lookup.byte_to_line(e.span().0)))
.collect();
let options = extract_options(&parse_result.options);
return ProcessedLedger {
directives: Vec::new(),
options,
errors,
parse_result,
lookup,
};
}
let mut vfs = VirtualFileSystem::new();
vfs.add_file("input.beancount", source);
let mut loader = Loader::new().with_filesystem(Box::new(vfs));
let raw = match loader.load(Path::new("input.beancount")) {
Ok(raw) => raw,
Err(e) => {
let options = extract_options(&parse_result.options);
return ProcessedLedger {
directives: Vec::new(),
options,
errors: vec![Error::new(format!("Load error: {e}"))],
parse_result,
lookup,
};
}
};
let options = extract_loader_options(&raw.options);
let load_options = LoadOptions {
validate: false,
..Default::default()
};
match process(raw, &load_options) {
Ok(ledger) => {
let directives = ledger.directives.into_iter().map(|s| s.value).collect();
let errors: Vec<Error> = ledger.errors.into_iter().map(Error::from).collect();
ProcessedLedger {
directives,
options,
errors,
parse_result,
lookup,
}
}
Err(e) => ProcessedLedger {
directives: Vec::new(),
options,
errors: vec![Error::new(format!("Processing error: {e}"))],
parse_result,
lookup,
},
}
}
pub fn run_validation(load: &ProcessedLedger) -> Vec<Error> {
use rustledger_validate::validate as validate_ledger;
if !load.errors.is_empty() {
return Vec::new();
}
let mut date_to_line: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
for spanned in &load.parse_result.directives {
let line = load.lookup.byte_to_line(spanned.span.start);
let date = spanned.value.date().to_string();
date_to_line.entry(date).or_insert(line);
}
validate_ledger(&load.directives)
.into_iter()
.map(|err| {
let line = date_to_line.get(&err.date.to_string()).copied();
Error {
message: err.message,
line,
column: None,
severity: Severity::Error,
}
})
.collect()
}
pub fn to_js<T: serde::Serialize>(value: &T) -> Result<JsValue, JsError> {
let serializer = serde_wasm_bindgen::Serializer::json_compatible();
value
.serialize(&serializer)
.map_err(|e| JsError::new(&e.to_string()))
}
pub fn extract_options(options: &[(String, String, rustledger_parser::Span)]) -> LedgerOptions {
let mut ledger_options = LedgerOptions::default();
for (key, value, _span) in options {
match key.as_str() {
"title" => ledger_options.title = Some(value.clone()),
"operating_currency" => {
ledger_options.operating_currencies.push(value.clone());
}
_ => {}
}
}
ledger_options
}
fn extract_loader_options(options: &rustledger_loader::Options) -> LedgerOptions {
LedgerOptions {
title: options.title.clone(),
operating_currencies: options.operating_currency.clone(),
}
}