Cached building

This commit is contained in:
ef3d0c3e 2024-07-29 13:32:05 +02:00
parent ac0c4050eb
commit c8d35a7dc3
3 changed files with 250 additions and 136 deletions

View file

@ -9,9 +9,6 @@ use crate::document::document::Document;
use crate::document::document::ElemReference; use crate::document::document::ElemReference;
use crate::document::variable::Variable; use crate::document::variable::Variable;
use super::navigation::NavEntry;
use super::navigation::Navigation;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Target { pub enum Target {
HTML, HTML,
@ -133,80 +130,128 @@ impl Compiler {
result result
} }
pub fn navigation(&self, navigation: &Navigation, document: &dyn Document) -> String
{
let mut result = String::new();
match self.target()
{
Target::HTML => {
result += r#"<ul id="navbar">"#;
fn process(result: &mut String, name: &String, ent: &NavEntry, depth: usize)
{
let ent_path = ent.path.as_ref()
.map_or("#".to_string(),|path| path.clone());
result.push_str(format!(r#"<li><a href="{ent_path}">{name}</a></li>"#).as_str());
if let Some(children) = ent.children.as_ref()
{
result.push_str("<ul>");
for (name, ent) in children
{
process(result, name, ent, depth+1);
}
result.push_str("</ul>");
}
}
for (name, ent) in &navigation.entries
{
process(&mut result, name, ent, 0);
}
result += r#"</ul>"#;
},
_ => todo!("")
}
result
}
pub fn footer(&self, _document: &dyn Document) -> String { pub fn footer(&self, _document: &dyn Document) -> String {
let mut result = String::new(); let mut result = String::new();
match self.target() { match self.target() {
Target::HTML => { Target::HTML => {
result += "</body></html>"; result += "</body></html>";
} }
Target::LATEX => todo!("") Target::LATEX => todo!(""),
} }
result result
} }
pub fn compile(&self, navigation: &Navigation, document: &dyn Document) -> String { pub fn compile(&self, document: &dyn Document) -> CompiledDocument {
let mut out = String::new();
let borrow = document.content().borrow(); let borrow = document.content().borrow();
// Header // Header
out += self.header(document).as_str(); let header = self.header(document);
// Navigation
out += self.navigation(navigation, document).as_str();
// Body // Body
let mut body = String::new();
for i in 0..borrow.len() { for i in 0..borrow.len() {
let elem = &borrow[i]; let elem = &borrow[i];
match elem.compile(self, document) { match elem.compile(self, document) {
Ok(result) => { Ok(result) => body.push_str(result.as_str()),
out.push_str(result.as_str())
}
Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()), Err(err) => println!("Unable to compile element: {err}\n{}", elem.to_string()),
} }
} }
// Footer // 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::<HashMap<String, String>>();
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<String, String>,
/// 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<usize, rusqlite::Error> {
con.execute(Self::sql_table(), [])
}
pub fn from_cache(con: &Connection, input: &String) -> Option<Self> {
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<usize, rusqlite::Error> {
con.execute(
Self::sql_insert_query(),
(
&self.input,
&self.mtime,
serde_json::to_string(&self.variables).unwrap(),
&self.header,
&self.body,
&self.footer,
),
)
} }
} }

View file

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::document::document::Document; use super::compiler::CompiledDocument;
use super::compiler::Target;
#[derive(Debug)] #[derive(Debug)]
pub struct NavEntry { pub struct NavEntry {
@ -14,7 +15,43 @@ pub struct Navigation {
pub(crate) entries: HashMap<String, NavEntry>, pub(crate) entries: HashMap<String, NavEntry>,
} }
pub fn create_navigation(docs: &Vec<Box<dyn Document>>) -> Result<Navigation, String> { impl Navigation {
pub fn compile(&self, target: Target) -> String {
let mut result = String::new();
match target {
Target::HTML => {
result += r#"<ul id="navbar">"#;
fn process(result: &mut String, name: &String, ent: &NavEntry, depth: usize) {
let ent_path = ent
.path
.as_ref()
.map_or("#".to_string(), |path| path.clone());
result
.push_str(format!(r#"<li><a href="{ent_path}">{name}</a></li>"#).as_str());
if let Some(children) = ent.children.as_ref() {
result.push_str("<ul>");
for (name, ent) in children {
process(result, name, ent, depth + 1);
}
result.push_str("</ul>");
}
}
for (name, ent) in &self.entries {
process(&mut result, name, ent, 0);
}
result += r#"</ul>"#;
}
_ => todo!(""),
}
result
}
}
pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<Navigation, String> {
let mut nav = Navigation { let mut nav = Navigation {
entries: HashMap::new(), entries: HashMap::new(),
}; };
@ -30,63 +67,57 @@ pub fn create_navigation(docs: &Vec<Box<dyn Document>>) -> Result<Navigation, St
let (cat, title, path) = match (cat, title, path) { let (cat, title, path) = match (cat, title, path) {
(Some(cat), Some(title), Some(path)) => (cat, title, path), (Some(cat), Some(title), Some(path)) => (cat, title, path),
_ => { _ => {
println!( println!("Skipping navigation generation for `{}`", doc.input);
"Skipping navigation generation for `{}`",
doc.source().name()
);
continue; continue;
} }
}; };
if let Some(subcat) = subcat { if let Some(subcat) = subcat {
// Get parent entry // Get parent entry
let cat_name = cat.to_string(); let mut pent = match nav.entries.get_mut(cat.as_str()) {
let mut pent = match nav.entries.get_mut(cat_name.as_str()) {
Some(pent) => pent, Some(pent) => pent,
None => { None => {
// Create parent entry // Create parent entry
nav.entries.insert( nav.entries.insert(
cat_name.clone(), cat.clone(),
NavEntry { NavEntry {
name: cat_name.clone(), name: cat.clone(),
path: None, path: None,
children: Some(HashMap::new()), children: Some(HashMap::new()),
}, },
); );
nav.entries.get_mut(cat_name.as_str()).unwrap() nav.entries.get_mut(cat.as_str()).unwrap()
} }
}; };
// Insert into parent // Insert into parent
let subcat_name = subcat.to_string();
if let Some(previous) = pent.children.as_mut().unwrap().insert( if let Some(previous) = pent.children.as_mut().unwrap().insert(
subcat_name.clone(), subcat.clone(),
NavEntry { NavEntry {
name: subcat_name.clone(), name: subcat.clone(),
path: Some(path.to_string()), path: Some(path.to_string()),
children: None, children: None,
}, },
) { ) {
return Err(format!( return Err(format!(
"Duplicate subcategory:\n{subcat:#?}\nclashes with:\n{previous:#?}" "Duplicate subcategory:\n{subcat}\nclashes with:\n{previous:#?}"
)); ));
} }
} else { } else {
// Get entry // Get entry
let cat_name = cat.to_string(); let mut ent = match nav.entries.get_mut(cat.as_str()) {
let mut ent = match nav.entries.get_mut(cat_name.as_str()) {
Some(ent) => ent, Some(ent) => ent,
None => { None => {
// Create parent entry // Create parent entry
nav.entries.insert( nav.entries.insert(
cat_name.clone(), cat.clone(),
NavEntry { NavEntry {
name: cat_name.clone(), name: cat.clone(),
path: None, path: None,
children: Some(HashMap::new()), children: Some(HashMap::new()),
}, },
); );
nav.entries.get_mut(cat_name.as_str()).unwrap() nav.entries.get_mut(cat.as_str()).unwrap()
} }
}; };

View file

@ -7,9 +7,14 @@ mod lua;
mod parser; mod parser;
use std::env; use std::env;
use std::fs::OpenOptions;
use std::io::BufWriter;
use std::io::Write;
use std::process::ExitCode; use std::process::ExitCode;
use std::rc::Rc; use std::rc::Rc;
use std::time::UNIX_EPOCH;
use compiler::compiler::CompiledDocument;
use compiler::compiler::Compiler; use compiler::compiler::Compiler;
use compiler::compiler::Target; use compiler::compiler::Target;
use compiler::navigation::create_navigation; use compiler::navigation::create_navigation;
@ -18,6 +23,7 @@ use document::document::Document;
use getopts::Options; use getopts::Options;
use parser::langparser::LangParser; use parser::langparser::LangParser;
use parser::parser::Parser; use parser::parser::Parser;
use rusqlite::Connection;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::parser::source::SourceFile; use crate::parser::source::SourceFile;
@ -42,38 +48,7 @@ NML version: 0.4\n"
); );
} }
fn compile( fn parse(input: &String, debug_opts: &Vec<String>) -> Result<Box<dyn Document<'static>>, String> {
target: Target,
doc: &Box<dyn Document>,
output: &String,
db_path: &Option<String>,
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<String>,
) -> Result<Box<dyn Document<'static>>, String> {
println!("Parsing {input}..."); println!("Parsing {input}...");
let parser = LangParser::default(); let parser = LangParser::default();
@ -108,12 +83,69 @@ fn parse(
} }
if parser.has_error() { 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) Ok(doc)
} }
fn process(
target: Target,
files: Vec<String>,
db_path: &Option<String>,
debug_opts: &Vec<String>,
) -> Result<Vec<CompiledDocument>, 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<CompiledDocument, String> {
// 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 { fn main() -> ExitCode {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let program = args[0].clone(); let program = args[0].clone();
@ -177,8 +209,7 @@ fn main() -> ExitCode {
let debug_opts = matches.opt_strs("z"); let debug_opts = matches.opt_strs("z");
let db_path = matches.opt_str("d"); let db_path = matches.opt_str("d");
let mut docs = vec![]; let mut files = vec![];
if input_meta.is_dir() { if input_meta.is_dir() {
if db_path.is_none() { if db_path.is_none() {
eprintln!("Please specify a database (-d) for directory mode."); eprintln!("Please specify a database (-d) for directory mode.");
@ -221,46 +252,53 @@ fn main() -> ExitCode {
continue; continue;
} }
match parse(&path, &debug_opts) files.push(path);
{
Ok(doc) => docs.push(doc),
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
}
}
} }
} else { } else {
match parse(&input, &debug_opts) files.push(input);
{
Ok(doc) => docs.push(doc),
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
}
}
} }
// Build navigation // Parse, compile using the cache
let navigation = match create_navigation(&docs) let compiled = match process(Target::HTML, files, &db_path, &debug_opts) {
{ Ok(compiled) => compiled,
Ok(nav) => nav,
Err(e) => { Err(e) => {
eprintln!("{e}"); eprintln!("{e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };
println!("{navigation:#?}"); // Build navigation
let navigation = match create_navigation(&compiled) {
let multi_mode = input_meta.is_dir(); Ok(nav) => nav,
for doc in docs Err(e) => {
{ eprintln!("{e}");
if !compile(Target::HTML, &doc, &output, &db_path, &navigation, multi_mode)
{
eprintln!("Compilation failed, processing aborted");
return ExitCode::FAILURE; 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; return ExitCode::SUCCESS;