From 2211c44ee0d88a0e43f9d6ed32d828825e1aa0d7 Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Tue, 13 Aug 2024 22:46:10 +0200 Subject: [PATCH] Refactored processing --- src/compiler/mod.rs | 1 + src/compiler/postprocess.rs | 2 +- src/compiler/process.rs | 195 ++++++++++++++++++++++++++++++++++++ src/document/document.rs | 2 +- src/document/references.rs | 2 +- src/elements/reference.rs | 77 ++++++++++++-- src/main.rs | 165 ++---------------------------- style.css | 30 ++++++ 8 files changed, 306 insertions(+), 168 deletions(-) create mode 100644 src/compiler/process.rs diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 0ccee49..01f648b 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,3 +1,4 @@ pub mod compiler; pub mod navigation; +pub mod process; pub mod postprocess; diff --git a/src/compiler/postprocess.rs b/src/compiler/postprocess.rs index 85f3263..b8a50be 100644 --- a/src/compiler/postprocess.rs +++ b/src/compiler/postprocess.rs @@ -19,7 +19,7 @@ impl PostProcess { /// Applies postprocessing to a [`CompiledDocument`] pub fn apply( &self, - target: Target, + _target: Target, list: &Vec<(RefCell, Option)>, doc: &RefCell, ) -> Result { diff --git a/src/compiler/process.rs b/src/compiler/process.rs new file mode 100644 index 0000000..5c86708 --- /dev/null +++ b/src/compiler/process.rs @@ -0,0 +1,195 @@ +use std::cell::RefCell; +use std::path::PathBuf; +use std::rc::Rc; +use std::time::UNIX_EPOCH; + +use rusqlite::Connection; + +use crate::document::document::Document; +use crate::parser::langparser::LangParser; +use crate::parser::parser::Parser; +use crate::parser::parser::ParserState; +use crate::parser::source::Source; +use crate::parser::source::SourceFile; + +use super::compiler::CompiledDocument; +use super::compiler::Compiler; +use super::compiler::Target; +use super::postprocess::PostProcess; + +/// Parses a source file into a document +fn parse( + parser: &LangParser, + source: Rc, + debug_opts: &Vec, +) -> Result>, String> { + // Parse + //let source = SourceFile::new(input.to_string(), None).unwrap(); + let (doc, _) = parser.parse(ParserState::new(parser, None), source.clone(), None); + + if debug_opts.contains(&"ast".to_string()) { + println!("-- BEGIN AST DEBUGGING --"); + doc.content() + .borrow() + .iter() + .for_each(|elem| println!("{elem:#?}")); + println!("-- END AST DEBUGGING --"); + } + if debug_opts.contains(&"ref".to_string()) { + println!("-- BEGIN REFERENCES DEBUGGING --"); + let sc = doc.scope().borrow(); + sc.referenceable.iter().for_each(|(name, reference)| { + println!(" - {name}: `{:#?}`", doc.get_from_reference(reference)); + }); + println!("-- END REFERENCES DEBUGGING --"); + } + if debug_opts.contains(&"var".to_string()) { + println!("-- BEGIN VARIABLES DEBUGGING --"); + let sc = doc.scope().borrow(); + sc.variables.iter().for_each(|(_name, var)| { + println!(" - `{:#?}`", var); + }); + println!("-- END VARIABLES DEBUGGING --"); + } + + if parser.has_error() { + return Err("Parsing failed due to errors while parsing".to_string()); + } + + Ok(doc) +} + +/// Takes a list of paths and processes it into a list of compiled documents +pub fn process( + target: Target, + files: Vec, + db_path: &Option, + force_rebuild: bool, + debug_opts: &Vec, +) -> Result, Option)>, String> { + let mut compiled = vec![]; + + let current_dir = std::env::current_dir() + .map_err(|err| format!("Unable to get the current working directory: {err}"))?; + + 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}"))?; + + let parser = LangParser::default(); + 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}"))?; + + // Move to file's directory + let file_parent_path = file + .parent() + .ok_or(format!("Failed to get parent path for `{file:#?}`"))?; + std::env::set_current_dir(file_parent_path) + .map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?; + + let parse_and_compile = || -> Result<(CompiledDocument, Option), String> { + // Parse + let source = SourceFile::new(file.to_str().unwrap().to_string(), None).unwrap(); + println!("Parsing {}...", source.name()); + let doc = parse(&parser, Rc::new(source), debug_opts)?; + + // Compile + let compiler = Compiler::new(target, db_path.clone()); + let (mut compiled, postprocess) = compiler.compile(&*doc); + + compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs(); + + Ok((compiled, Some(postprocess))) + }; + + let (cdoc, post) = if force_rebuild { + parse_and_compile()? + } else { + match CompiledDocument::from_cache(&con, file.to_str().unwrap()) { + Some(compiled) => { + if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() { + parse_and_compile()? + } else { + (compiled, None) + } + } + None => parse_and_compile()?, + } + }; + + compiled.push((RefCell::new(cdoc), post)); + } + + for (doc, postprocess) in &compiled { + if postprocess.is_none() { + continue; + } + + // Post processing + let body = postprocess + .as_ref() + .unwrap() + .apply(target, &compiled, &doc)?; + doc.borrow_mut().body = body; + + // Insert into cache + doc.borrow().insert_cache(&con).map_err(|err| { + format!( + "Failed to insert compiled document from `{}` into cache: {err}", + doc.borrow().input + ) + })?; + } + + std::env::set_current_dir(current_dir) + .map_err(|err| format!("Failed to set current directory: {err}"))?; + + Ok(compiled) +} + +/// Processes sources from in-memory strings +/// This function is indented for testing +fn process_in_memory(target: Target, sources: Vec) -> Result, Option)>, String> { + let mut compiled = vec![]; + + let parser = LangParser::default(); + for (idx, content) in sources.iter().enumerate() { + let parse_and_compile = || -> Result<(CompiledDocument, Option), String> { + // Parse + let source = SourceFile::with_content(format!("{idx}"), content.clone(), None); + let doc = parse(&parser, Rc::new(source), &vec![])?; + + // Compile + let compiler = Compiler::new(target, None); + let (mut compiled, postprocess) = compiler.compile(&*doc); + + Ok((compiled, Some(postprocess))) + }; + + let (cdoc, post) = parse_and_compile()?; + compiled.push((RefCell::new(cdoc), post)); + } + + for (doc, postprocess) in &compiled { + if postprocess.is_none() { + continue; + } + + // Post processing + let body = postprocess + .as_ref() + .unwrap() + .apply(target, &compiled, &doc)?; + doc.borrow_mut().body = body; + } + + Ok(compiled) +} diff --git a/src/document/document.rs b/src/document/document.rs index 4f94268..26c7222 100644 --- a/src/document/document.rs +++ b/src/document/document.rs @@ -22,7 +22,7 @@ pub enum ElemReference { Nested(usize, usize), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum CrossReference { /// When the referenced document is unspecified Unspecific(String), diff --git a/src/document/references.rs b/src/document/references.rs index 0a7e75e..fb379f5 100644 --- a/src/document/references.rs +++ b/src/document/references.rs @@ -45,7 +45,7 @@ pub mod tests { use crate::parser::langparser::LangParser; use crate::parser::parser::Parser; use crate::parser::source::SourceFile; - use crate::ParserState; + use crate::parser::parser::ParserState; #[test] fn validate_refname_tests() { diff --git a/src/elements/reference.rs b/src/elements/reference.rs index f93bcb1..1d03d0e 100644 --- a/src/elements/reference.rs +++ b/src/elements/reference.rs @@ -91,13 +91,15 @@ impl Element for ExternalReference { match compiler.target() { Target::HTML => { let mut result = " &name, - CrossReference::Specific(_, name) => &name, - }); compiler.insert_crossreference(cursor + result.len(), self.reference.clone()); - result += format!("\">{}", Compiler::sanitize(Target::HTML, refname)).as_str(); + + if let Some(caption) = &self.caption { + result += + format!("\">{}", Compiler::sanitize(Target::HTML, caption)).as_str(); + } else { + result += format!("\">{}", self.reference).as_str(); + } Ok(result) } _ => todo!(""), @@ -268,10 +270,6 @@ impl RegexRule for ReferenceRule { .and_then(|(_, s)| Some(s)); if let Some(refdoc) = refdoc { - if caption.is_none() { - return reports; - } - if refdoc.is_empty() { state.push( document, @@ -305,3 +303,64 @@ impl RegexRule for ReferenceRule { reports } } + +#[cfg(test)] +mod tests { + use crate::elements::paragraph::Paragraph; +use crate::elements::section::Section; +use crate::parser::langparser::LangParser; + use crate::parser::parser::Parser; +use crate::parser::source::SourceFile; + use crate::validate_document; + + use super::*; + + #[test] + pub fn parse_internal() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +#{ref} Referenceable section + +§{ref}[caption=Section] +§{ref}[caption=Another] +"# + .to_string(), + None, + )); + let parser = LangParser::default(); + let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None); + + validate_document!(doc.content().borrow(), 0, + Section; + Paragraph { + InternalReference { refname == "ref", caption == Some("Section".to_string()) }; + InternalReference { refname == "ref", caption == Some("Another".to_string()) }; + }; + ); + } + + #[test] + pub fn parse_external() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +§{DocA#ref}[caption=Section] +§{DocB#ref} +§{#ref}[caption='ref' from any document] +"# + .to_string(), + None, + )); + let parser = LangParser::default(); + let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None); + + validate_document!(doc.content().borrow(), 0, + Paragraph { + ExternalReference { reference == CrossReference::Specific("DocA".into(), "ref".into()), caption == Some("Section".to_string()) }; + ExternalReference { reference == CrossReference::Specific("DocB".into(), "ref".into()), caption == None:: }; + ExternalReference { reference == CrossReference::Unspecific("ref".into()), caption == Some("'ref' from any document".to_string()) }; + }; + ); + } +} diff --git a/src/main.rs b/src/main.rs index 798e863..c2d62a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,29 +5,16 @@ mod elements; mod lua; mod parser; -use std::cell::RefCell; use std::env; use std::io::BufWriter; use std::io::Write; -use std::path::PathBuf; 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; -use compiler::postprocess::PostProcess; -use document::document::Document; use getopts::Options; -use parser::langparser::LangParser; -use parser::parser::Parser; -use parser::parser::ParserState; -use rusqlite::Connection; use walkdir::WalkDir; -use crate::parser::source::SourceFile; extern crate getopts; fn print_usage(program: &str, opts: Options) { @@ -49,142 +36,6 @@ NML version: 0.4\n" ); } -fn parse( - parser: &LangParser, - input: &str, - debug_opts: &Vec, -) -> Result>, String> { - println!("Parsing {input}..."); - - // Parse - let source = SourceFile::new(input.to_string(), None).unwrap(); - let (doc, _) = parser.parse(ParserState::new(parser, None), Rc::new(source), None); - - if debug_opts.contains(&"ast".to_string()) { - println!("-- BEGIN AST DEBUGGING --"); - doc.content() - .borrow() - .iter() - .for_each(|elem| println!("{elem:#?}")); - println!("-- END AST DEBUGGING --"); - } - if debug_opts.contains(&"ref".to_string()) { - println!("-- BEGIN REFERENCES DEBUGGING --"); - let sc = doc.scope().borrow(); - sc.referenceable.iter().for_each(|(name, reference)| { - println!(" - {name}: `{:#?}`", doc.get_from_reference(reference)); - }); - println!("-- END REFERENCES DEBUGGING --"); - } - if debug_opts.contains(&"var".to_string()) { - println!("-- BEGIN VARIABLES DEBUGGING --"); - let sc = doc.scope().borrow(); - sc.variables.iter().for_each(|(_name, var)| { - println!(" - `{:#?}`", var); - }); - println!("-- END VARIABLES DEBUGGING --"); - } - - if parser.has_error() { - return Err("Parsing failed due to errors while parsing".to_string()); - } - - Ok(doc) -} - -fn process( - target: Target, - files: Vec, - db_path: &Option, - force_rebuild: bool, - debug_opts: &Vec, -) -> Result, Option)>, String> { - let mut compiled = vec![]; - - let current_dir = std::env::current_dir() - .map_err(|err| format!("Unable to get the current working directory: {err}"))?; - - 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}"))?; - - let parser = LangParser::default(); - 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}"))?; - - // Move to file's directory - let file_parent_path = file - .parent() - .ok_or(format!("Failed to get parent path for `{file:#?}`"))?; - std::env::set_current_dir(file_parent_path) - .map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?; - - let parse_and_compile = || -> Result<(CompiledDocument, Option), String> { - // Parse - let doc = parse(&parser, file.to_str().unwrap(), debug_opts)?; - - // Compile - let compiler = Compiler::new(target, db_path.clone()); - let (mut compiled, postprocess) = compiler.compile(&*doc); - - compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs(); - - Ok((compiled, Some(postprocess))) - }; - - let (cdoc, post) = if force_rebuild { - parse_and_compile()? - } else { - match CompiledDocument::from_cache(&con, file.to_str().unwrap()) { - Some(compiled) => { - if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() { - parse_and_compile()? - } else { - (compiled, None) - } - } - None => parse_and_compile()?, - } - }; - - compiled.push((RefCell::new(cdoc), post)); - } - - for (doc, postprocess) in &compiled { - if postprocess.is_none() { - continue; - } - - // Post processing - let body = postprocess - .as_ref() - .unwrap() - .apply(target, &compiled, &doc)?; - doc.borrow_mut().body = body; - - // Insert into cache - doc.borrow().insert_cache(&con).map_err(|err| { - format!( - "Failed to insert compiled document from `{}` into cache: {err}", - doc.borrow().input - ) - })?; - } - - std::env::set_current_dir(current_dir) - .map_err(|err| format!("Failed to set current directory: {err}"))?; - - Ok(compiled) -} - fn main() -> ExitCode { let args: Vec = env::args().collect(); let program = args[0].clone(); @@ -362,13 +213,15 @@ fn main() -> ExitCode { } // Parse, compile using the cache - let processed = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) { - Ok(processed) => processed, - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; - } - }; + let processed = + match compiler::process::process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) + { + Ok(processed) => processed, + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + } + }; if input_meta.is_dir() // Batch mode diff --git a/style.css b/style.css index a79d686..70e08dc 100644 --- a/style.css +++ b/style.css @@ -137,6 +137,11 @@ a.inline-code { content: "–"; } +/* Sections */ +a.section-link { + text-decoration: none; +} + /* Code blocks */ div.code-block-title { background-color: #20202a; @@ -241,3 +246,28 @@ a:hover.medium-ref img { box-shadow: 0px 0px 6px 2px rgba(0, 0, 0, 0.75); } + +/* Blockquote */ +blockquote { + margin-left: 0.2em; + padding-left: 0.6em; + + border-left: 4px solid #0ff08b; +} + +blockquote p::before { + content: '\201C'; +} + +blockquote p::after { + content: '\201D'; +} + +.blockquote-author:before { + content: '—'; +} + +.blockquote-author { + margin-left: 0.2em; +} +