From c8d35a7dc334d624f5c43fe6ba8cd0cfad82d25b Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Mon, 29 Jul 2024 13:32:05 +0200 Subject: [PATCH] Cached building --- src/compiler/compiler.rs | 151 ++++++++++++++++++++++------------ src/compiler/navigation.rs | 71 +++++++++++----- src/main.rs | 164 +++++++++++++++++++++++-------------- 3 files changed, 250 insertions(+), 136 deletions(-) diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index d12bf43..3d30b39 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -9,9 +9,6 @@ use crate::document::document::Document; use crate::document::document::ElemReference; use crate::document::variable::Variable; -use super::navigation::NavEntry; -use super::navigation::Navigation; - #[derive(Clone, Copy)] pub enum Target { HTML, @@ -133,80 +130,128 @@ impl Compiler { result } - pub fn navigation(&self, navigation: &Navigation, document: &dyn Document) -> String - { - let mut result = String::new(); - match self.target() - { - Target::HTML => { - result += r#""#; - }, - _ => todo!("") - } - result - } - pub fn footer(&self, _document: &dyn Document) -> String { let mut result = String::new(); match self.target() { Target::HTML => { result += ""; } - Target::LATEX => todo!("") + Target::LATEX => todo!(""), } result } - pub fn compile(&self, navigation: &Navigation, document: &dyn Document) -> String { - let mut out = String::new(); + pub fn compile(&self, document: &dyn Document) -> CompiledDocument { let borrow = document.content().borrow(); // Header - out += self.header(document).as_str(); - - // Navigation - out += self.navigation(navigation, document).as_str(); + let header = self.header(document); // Body + let mut body = String::new(); for i in 0..borrow.len() { let elem = &borrow[i]; match elem.compile(self, document) { - Ok(result) => { - out.push_str(result.as_str()) - } + Ok(result) => body.push_str(result.as_str()), Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()), } } // Footer - out += self.footer(document).as_str(); + let footer = self.footer(document); - out + // Variables + let variables = document + .scope() + .borrow_mut() + .variables + .iter() + .map(|(key, var)| (key.clone(), var.to_string())) + .collect::>(); + + CompiledDocument { + input: document.source().name().clone(), + mtime: 0, + variables, + header, + body, + footer, + } + } +} + +#[derive(Debug)] +pub struct CompiledDocument { + /// Input path relative to the input directory + pub input: String, + /// Modification time (i.e seconds since last epoch) + pub mtime: u64, + + // TODO: Also store exported references + // so they can be referenced from elsewhere + // This will also require rebuilding in case some exported references have changed... + /// Variables exported to string, so they can be querried later + pub variables: HashMap, + + /// Compiled document's header + pub header: String, + /// Compiled document's body + pub body: String, + /// Compiled document's footer + pub footer: String, +} + +impl CompiledDocument { + pub fn get_variable(&self, name: &str) -> Option<&String> { self.variables.get(name) } + + fn sql_table() -> &'static str { + "CREATE TABLE IF NOT EXISTS compiled_documents ( + input TEXT PRIMARY KEY, + mtime INTEGER NOT NULL, + variables TEXT NOT NULL, + header TEXT NOT NULL, + body TEXT NOT NULL, + footer TEXT NOT NULL + );" + } + + fn sql_get_query() -> &'static str { "SELECT * FROM compiled_documents WHERE input = (?1)" } + + fn sql_insert_query() -> &'static str { + "INSERT OR REPLACE INTO compiled_documents (input, mtime, variables, header, body, footer) VALUES (?1, ?2, ?3, ?4, ?5, ?6)" + } + + pub fn init_cache(con: &Connection) -> Result { + con.execute(Self::sql_table(), []) + } + + pub fn from_cache(con: &Connection, input: &String) -> Option { + con.query_row(Self::sql_get_query(), [input], |row| { + Ok(CompiledDocument { + input: input.clone(), + mtime: row.get_unwrap::<_, u64>(1), + variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(), + header: row.get_unwrap::<_, String>(3), + body: row.get_unwrap::<_, String>(4), + footer: row.get_unwrap::<_, String>(5), + }) + }) + .ok() + } + + /// Inserts [`CompiledDocument`] into cache + pub fn insert_cache(&self, con: &Connection) -> Result { + con.execute( + Self::sql_insert_query(), + ( + &self.input, + &self.mtime, + serde_json::to_string(&self.variables).unwrap(), + &self.header, + &self.body, + &self.footer, + ), + ) } } diff --git a/src/compiler/navigation.rs b/src/compiler/navigation.rs index 722d563..43dd8a8 100644 --- a/src/compiler/navigation.rs +++ b/src/compiler/navigation.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use crate::document::document::Document; +use super::compiler::CompiledDocument; +use super::compiler::Target; #[derive(Debug)] pub struct NavEntry { @@ -14,7 +15,43 @@ pub struct Navigation { pub(crate) entries: HashMap, } -pub fn create_navigation(docs: &Vec>) -> Result { +impl Navigation { + pub fn compile(&self, target: Target) -> String { + let mut result = String::new(); + match target { + Target::HTML => { + result += r#""#; + } + _ => todo!(""), + } + result + } +} + +pub fn create_navigation(docs: &Vec) -> Result { let mut nav = Navigation { entries: HashMap::new(), }; @@ -30,63 +67,57 @@ pub fn create_navigation(docs: &Vec>) -> Result (cat, title, path), _ => { - println!( - "Skipping navigation generation for `{}`", - doc.source().name() - ); + println!("Skipping navigation generation for `{}`", doc.input); continue; } }; if let Some(subcat) = subcat { // Get parent entry - let cat_name = cat.to_string(); - let mut pent = match nav.entries.get_mut(cat_name.as_str()) { + let mut pent = match nav.entries.get_mut(cat.as_str()) { Some(pent) => pent, None => { // Create parent entry nav.entries.insert( - cat_name.clone(), + cat.clone(), NavEntry { - name: cat_name.clone(), + name: cat.clone(), path: None, children: Some(HashMap::new()), }, ); - nav.entries.get_mut(cat_name.as_str()).unwrap() + nav.entries.get_mut(cat.as_str()).unwrap() } }; // Insert into parent - let subcat_name = subcat.to_string(); if let Some(previous) = pent.children.as_mut().unwrap().insert( - subcat_name.clone(), + subcat.clone(), NavEntry { - name: subcat_name.clone(), + name: subcat.clone(), path: Some(path.to_string()), children: None, }, ) { return Err(format!( - "Duplicate subcategory:\n{subcat:#?}\nclashes with:\n{previous:#?}" + "Duplicate subcategory:\n{subcat}\nclashes with:\n{previous:#?}" )); } } else { // Get entry - let cat_name = cat.to_string(); - let mut ent = match nav.entries.get_mut(cat_name.as_str()) { + let mut ent = match nav.entries.get_mut(cat.as_str()) { Some(ent) => ent, None => { // Create parent entry nav.entries.insert( - cat_name.clone(), + cat.clone(), NavEntry { - name: cat_name.clone(), + name: cat.clone(), path: None, children: Some(HashMap::new()), }, ); - nav.entries.get_mut(cat_name.as_str()).unwrap() + nav.entries.get_mut(cat.as_str()).unwrap() } }; diff --git a/src/main.rs b/src/main.rs index a19a37d..02be5b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,14 @@ mod lua; mod parser; use std::env; +use std::fs::OpenOptions; +use std::io::BufWriter; +use std::io::Write; use std::process::ExitCode; use std::rc::Rc; +use std::time::UNIX_EPOCH; +use compiler::compiler::CompiledDocument; use compiler::compiler::Compiler; use compiler::compiler::Target; use compiler::navigation::create_navigation; @@ -18,6 +23,7 @@ use document::document::Document; use getopts::Options; use parser::langparser::LangParser; use parser::parser::Parser; +use rusqlite::Connection; use walkdir::WalkDir; use crate::parser::source::SourceFile; @@ -42,38 +48,7 @@ NML version: 0.4\n" ); } -fn compile( - target: Target, - doc: &Box, - output: &String, - db_path: &Option, - navigation: &Navigation, - multi_mode: bool, -) -> bool { - let compiler = Compiler::new(target, db_path.clone()); - - // 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(navigation, doc.as_ref()); - std::fs::write(out_file, out).is_ok() - } else { - let out = compiler.compile(navigation, doc.as_ref()); - std::fs::write(output, out).is_ok() - } -} - -fn parse( - input: &String, - debug_opts: &Vec, -) -> Result>, String> { +fn parse(input: &String, debug_opts: &Vec) -> Result>, String> { println!("Parsing {input}..."); let parser = LangParser::default(); @@ -108,12 +83,69 @@ fn parse( } if parser.has_error() { - return Err("Parsing failed aborted due to errors while parsing".to_string()) + return Err("Parsing failed aborted due to errors while parsing".to_string()); } Ok(doc) } +fn process( + target: Target, + files: Vec, + db_path: &Option, + debug_opts: &Vec, +) -> Result, String> { + let mut compiled = vec![]; + + let con = db_path + .as_ref() + .map_or(Connection::open_in_memory(), |path| Connection::open(path)) + .map_err(|err| format!("Unable to open connection to the database: {err}"))?; + CompiledDocument::init_cache(&con) + .map_err(|err| format!("Failed to initialize cached document table: {err}"))?; + + for file in files { + let meta = std::fs::metadata(&file) + .map_err(|err| format!("Failed to get metadata for `{file}`: {err}"))?; + + let modified = meta + .modified() + .map_err(|err| format!("Unable to query modification time for `{file}`: {err}"))?; + + let parse_and_compile = || -> Result { + // Parse + let doc = parse(&file, debug_opts)?; + + // Compile + let compiler = Compiler::new(target, db_path.clone()); + let mut compiled = compiler.compile(&*doc); + + // Insert into cache + compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs(); + compiled.insert_cache(&con).map_err(|err| { + format!("Failed to insert compiled document from `{file}` into cache: {err}") + })?; + + Ok(compiled) + }; + + let cdoc = match CompiledDocument::from_cache(&con, &file) { + Some(compiled) => { + if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() { + parse_and_compile()? + } else { + compiled + } + } + None => parse_and_compile()?, + }; + + compiled.push(cdoc); + } + + Ok(compiled) +} + fn main() -> ExitCode { let args: Vec = env::args().collect(); let program = args[0].clone(); @@ -177,8 +209,7 @@ fn main() -> ExitCode { let debug_opts = matches.opt_strs("z"); let db_path = matches.opt_str("d"); - let mut docs = vec![]; - + let mut files = vec![]; if input_meta.is_dir() { if db_path.is_none() { eprintln!("Please specify a database (-d) for directory mode."); @@ -221,46 +252,53 @@ fn main() -> ExitCode { continue; } - match parse(&path, &debug_opts) - { - Ok(doc) => docs.push(doc), - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; - } - } + files.push(path); } } else { - match parse(&input, &debug_opts) - { - Ok(doc) => docs.push(doc), - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; - } - } + files.push(input); } - // Build navigation - let navigation = match create_navigation(&docs) - { - Ok(nav) => nav, + // Parse, compile using the cache + let compiled = match process(Target::HTML, files, &db_path, &debug_opts) { + Ok(compiled) => compiled, Err(e) => { eprintln!("{e}"); return ExitCode::FAILURE; } }; - println!("{navigation:#?}"); - - let multi_mode = input_meta.is_dir(); - for doc in docs - { - if !compile(Target::HTML, &doc, &output, &db_path, &navigation, multi_mode) - { - eprintln!("Compilation failed, processing aborted"); + // Build navigation + let navigation = match create_navigation(&compiled) { + Ok(nav) => nav, + Err(e) => { + eprintln!("{e}"); return ExitCode::FAILURE; } + }; + let compiled_navigation = navigation.compile(Target::HTML); + + // Output + for doc in compiled { + let out_path = match doc + .get_variable("compiler.output") + .or(input_meta.is_file().then_some(&output)) + { + Some(path) => path.clone(), + None => { + eprintln!("Unable to get output file for `{}`", doc.input); + return ExitCode::FAILURE; + } + }; + + let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap(); + let mut writer = BufWriter::new(file); + + write!( + writer, + "{}{}{}{}", + doc.header, compiled_navigation, doc.body, doc.footer + ); + writer.flush(); } return ExitCode::SUCCESS;