1use std::{fs::File, io::Cursor};
34
35use journal::Journal;
36use option::InputOptions;
37
38#[cfg(target_arch = "wasm32")]
39use wasm_bindgen::prelude::*;
40
41pub mod account;
42mod annotate;
43pub mod amount;
44mod balance;
45pub mod commodity;
46mod directives;
47pub mod history;
48mod iterator;
49pub mod journal;
50mod journalreader;
51mod option;
52pub mod parser;
53pub mod pool;
54pub mod post;
55pub mod report;
56pub mod scanner;
57pub mod utilities;
58mod value;
59pub mod xact;
60
61pub fn run(args: Vec<String>) -> Vec<String> {
67 let (commands, options) = option::process_arguments(args);
69
70 execute_command(commands, options)
71}
72
73pub fn run_command(command: &str) -> Vec<String> {
77 let args = shell_words::split(command).unwrap();
78 run(args)
79}
80
81fn execute_command(commands: Vec<String>, input_options: InputOptions) -> Vec<String> {
83 let verb = commands.iter().nth(0).unwrap();
84
85 let journal = session_read_journal_files(&input_options);
91
92 let command_args = &commands[1..];
95
96 match verb.chars().next().unwrap() {
98 'a' => {
99 let mut output = report::report_accounts(&journal);
102 output.sort();
103 output
104 }
105 'b' => {
106 match verb.as_str() {
107 "b" | "bal" | "balance" => {
108 report::balance_report(&journal)
110 }
111 "budget" => {
112 todo!("budget!")
114 }
115 _ => {
116 todo!("?")
117 }
118 }
119 }
120 _ => todo!("handle"),
121 }
122}
123
124fn look_for_precommand(verb: &str) {
125 todo!()
126}
127
128fn session_read_journal_files(options: &InputOptions) -> Journal {
129 let mut journal = Journal::new();
134 for filename in &options.filenames {
135 parse_file(filename, &mut journal);
137 }
138
139 journal
140}
141
142pub fn parse_file(file_path: &str, journal: &mut Journal) {
144 let file = File::open(file_path).expect("file opened");
145 parser::read_into_journal(file, journal);
146}
147
148pub fn parse_text(text: &str, journal: &mut Journal) {
153 let source = Cursor::new(text);
154 parser::read_into_journal(source, journal);
155}
156
157pub fn parser_experiment() {
158 todo!("try the new approach")
167}
168
169#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
170pub fn wasm_test() -> String {
171 "hello from wasm".to_owned()
172}
173
174#[cfg(test)]
175mod lib_tests {
176 use std::assert_eq;
177
178 use crate::{amount::{Amount, Quantity}, option, run};
179
180 #[test]
182 fn test_minimal() {
183 let command = "b -f tests/minimal.ledger";
185 let args = shell_words::split(command).unwrap();
186 let expected = r#"Account has balance 0
187Account Assets has balance -20
188Account Expenses has balance 20"#;
189
190 let actual = run(args).join("\n");
194
195 assert!(!actual.is_empty());
197 assert_eq!(expected, actual);
198 }
199
200 #[test]
201 fn test_multiple_files() {
202 let args =
204 shell_words::split("accounts -f tests/minimal.ledger -f tests/basic.ledger").unwrap();
205 let (_commands, input_options) = option::process_arguments(args);
206 let journal = super::session_read_journal_files(&input_options);
210
211 let xact0 = &journal.xacts[0];
213 let xact1 = &journal.xacts[1];
214
215 assert_eq!(2, journal.xacts.len());
217 assert_eq!("Payee", xact0.payee);
218 assert_eq!("Supermarket", xact1.payee);
219
220 assert_eq!(2, xact0.posts.len());
223 assert_eq!(2, xact1.posts.len());
224 assert_eq!(Some(Amount::new(20.into(), None)), xact0.posts[0].amount);
225 assert_eq!(xact1.posts[0].amount.unwrap().quantity, Quantity::from_str("20").unwrap());
227 assert_eq!(xact1.posts[0].amount.unwrap().get_commodity().unwrap().symbol, "EUR");
228
229 let mut accounts = journal.master.flatten_account_tree();
231 accounts.sort_unstable_by_key(|acc| &acc.name);
233
234 assert_eq!("", accounts[0].name);
235 assert_eq!("Assets", accounts[1].name);
236 assert_eq!("Cash", accounts[2].name);
237 assert_eq!("Expenses", accounts[3].name);
238 assert_eq!("Food", accounts[4].name);
239
240 assert_eq!(1, journal.commodity_pool.commodities.len());
242 assert_eq!(
243 "EUR",
244 journal.commodity_pool.find("EUR").unwrap().symbol
245 );
246 }
247}