diff --git a/Cargo.lock b/Cargo.lock index 85a0414..04fa921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -737,6 +737,7 @@ dependencies = [ "tokio", "tower-lsp", "unicode-segmentation", + "walkdir", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3858a37..f6884a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ tokio = { version = "1.38.1", features = ["macros", "rt-multi-thread", "io-std"] tower-lsp = "0.20.0" unicode-segmentation = "1.11.0" +walkdir = "2.5.0" diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 7d8f3f8..a36b89f 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -18,6 +18,7 @@ pub enum Target { pub struct Compiler { target: Target, cache: Option>, + // TODO: reference_count: RefCell>>, } diff --git a/src/main.rs b/src/main.rs index badbc34..d00b9bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,18 +7,20 @@ mod lua; mod parser; use std::env; +use std::process::ExitCode; use std::rc::Rc; use compiler::compiler::Compiler; use getopts::Options; use parser::langparser::LangParser; use parser::parser::Parser; +use walkdir::WalkDir; use crate::parser::source::SourceFile; extern crate getopts; fn print_usage(program: &str, opts: Options) { - let brief = format!("Usage: {} -i FILE [options]", program); + let brief = format!("Usage: {} -i PATH -o PATH [options]", program); print!("{}", opts.usage(&brief)); } @@ -36,42 +38,15 @@ NML version: 0.4\n" ); } -fn main() { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - - let mut opts = Options::new(); - opts.optopt("i", "", "Input file", "FILE"); - opts.optopt("d", "database", "Cache database location", "PATH"); - opts.optmulti("z", "debug", "Debug options", "OPTS"); - opts.optflag("h", "help", "Print this help menu"); - opts.optflag("v", "version", "Print program version and licenses"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => { - panic!("{}", f.to_string()) - } - }; - if matches.opt_present("v") { - print_version(); - return; - } - if matches.opt_present("h") { - print_usage(&program, opts); - return; - } - if !matches.opt_present("i") { - print_usage(&program, opts); - return; - } - - let input = matches.opt_str("i").unwrap(); - let debug_opts = matches.opt_strs("z"); - let db_path = matches.opt_str("d"); - - let parser = LangParser::default(); - +fn process( + parser: &LangParser, + db_path: &Option, + input: &String, + output: &String, + debug_opts: &Vec, + multi_mode: bool, +) -> bool { + println!("Processing {input}..."); // Parse let source = SourceFile::new(input.to_string(), None).unwrap(); let doc = parser.parse(Rc::new(source), None); @@ -104,11 +79,146 @@ fn main() { if parser.has_error() { println!("Compilation aborted due to errors while parsing"); - return; + return false; } - let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path); - let out = compiler.compile(doc.as_ref()); + let compiler = Compiler::new(compiler::compiler::Target::HTML, db_path.clone()); - std::fs::write("a.html", out).unwrap(); + // Get output from file + if multi_mode { + let out_file = match doc.get_variable("compiler.output") { + None => { + eprintln!("Missing required variable `compiler.output` for multifile mode"); + return false; + } + Some(var) => output.clone() + "/" + var.to_string().as_str(), + }; + + let out = compiler.compile(doc.as_ref()); + std::fs::write(out_file, out).is_ok() + } else { + let out = compiler.compile(doc.as_ref()); + std::fs::write(output, out).is_ok() + } +} + +fn main() -> ExitCode { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optopt("i", "input", "Input path", "PATH"); + opts.optopt("o", "output", "Output path", "PATH"); + opts.optopt("d", "database", "Cache database location", "PATH"); + opts.optmulti("z", "debug", "Debug options", "OPTS"); + opts.optflag("h", "help", "Print this help menu"); + opts.optflag("v", "version", "Print program version and licenses"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => { + panic!("{}", f.to_string()) + } + }; + if matches.opt_present("v") { + print_version(); + return ExitCode::SUCCESS; + } + if matches.opt_present("h") { + print_usage(&program, opts); + return ExitCode::SUCCESS; + } + if !matches.opt_present("i") || !matches.opt_present("o") { + print_usage(&program, opts); + return ExitCode::FAILURE; + } + + let input = matches.opt_str("i").unwrap(); + let input_meta = match std::fs::metadata(&input) { + Ok(meta) => meta, + Err(e) => { + eprintln!("Unable to get metadata for input: `{input}`"); + return ExitCode::FAILURE; + } + }; + let output = matches.opt_str("o").unwrap(); + if input_meta.is_dir() { + // Create ouput directories + if !std::fs::exists(&output).unwrap_or(false) { + match std::fs::create_dir_all(&output) { + Ok(()) => {} + Err(err) => { + eprintln!("Unable to create output directory `{output}`: {err}"); + return ExitCode::FAILURE; + } + } + } + match std::fs::metadata(&output) { + Ok(_) => {} + Err(e) => { + eprintln!("Unable to get metadata for output: `{output}`"); + return ExitCode::FAILURE; + } + } + } + + let debug_opts = matches.opt_strs("z"); + let db_path = matches.opt_str("d"); + let parser = LangParser::default(); + + if input_meta.is_dir() { + if db_path.is_none() { + eprintln!("Please specify a database (-d) for directory mode."); + } + + let input_it = match std::fs::read_dir(&input) { + Ok(it) => it, + Err(e) => { + eprintln!("Failed to read input directory `{input}`: {e}"); + return ExitCode::FAILURE; + } + }; + + for entry in WalkDir::new(&input) { + if let Err(err) = entry { + eprintln!("Failed to recursively walk over input directory: {err}"); + return ExitCode::FAILURE; + } + match entry.as_ref().unwrap().metadata() { + Ok(meta) => { + if !meta.is_file() { + continue; + } + } + Err(e) => { + eprintln!("Faield to get metadata for `{entry:#?}`"); + return ExitCode::FAILURE; + } + } + + let path = match entry.as_ref().unwrap().path().to_str() { + Some(path) => path.to_string(), + None => { + eprintln!("Faield to convert input file `{entry:#?}` to UTF-8"); + return ExitCode::FAILURE; + } + }; + if !path.ends_with(".nml") { + println!("Skipping '{path}'"); + continue; + } + + if !process(&parser, &db_path, &path, &output, &debug_opts, true) { + eprintln!("Processing aborted"); + return ExitCode::FAILURE; + } + } + } else { + if !process(&parser, &db_path, &input, &output, &debug_opts, false) { + eprintln!("Processing aborted"); + return ExitCode::FAILURE; + } + } + + return ExitCode::SUCCESS; } diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs index 6d07cdf..0cc569d 100644 --- a/src/parser/langparser.rs +++ b/src/parser/langparser.rs @@ -144,7 +144,7 @@ impl Parser for LangParser { }) .unwrap(); - paragraph.push(elem); + paragraph.push(elem).unwrap(); } else { // Process paragraph events if doc.last_element::().is_some_and(|_| true) {