Compare commits

...

2 commits

Author SHA1 Message Date
35408f03b1 Tests 2024-08-13 23:17:08 +02:00
2211c44ee0 Refactored processing 2024-08-13 22:46:10 +02:00
8 changed files with 339 additions and 170 deletions

View file

@ -1,3 +1,4 @@
pub mod compiler;
pub mod navigation;
pub mod process;
pub mod postprocess;

View file

@ -19,7 +19,7 @@ impl PostProcess {
/// Applies postprocessing to a [`CompiledDocument`]
pub fn apply(
&self,
target: Target,
_target: Target,
list: &Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>,
doc: &RefCell<CompiledDocument>,
) -> Result<String, String> {
@ -35,7 +35,7 @@ impl PostProcess {
if let Some(found) = doc.borrow().references.get(name) {
// Check for duplicates
if let Some((_, previous_doc)) = &found_ref {
return Err(format!("Cannot use an unspecific reference for reference named: `{found}`. Found in document `{}` but also in `{}`. Specify the source of the reference to resolve the conflict.", previous_doc.borrow().input, doc.borrow().input));
return Err(format!("Cannot use an unspecific reference for reference named: `{name}`. Found in document `{}` but also in `{}`. Specify the source of the reference to resolve the conflict.", previous_doc.borrow().input, doc.borrow().input));
}
found_ref = Some((found.clone(), &doc));
@ -71,7 +71,7 @@ impl PostProcess {
"Unable to get the output. Aborting postprocessing."
))?;
let insert_content = format!("{found_path}#{found_ref}");
content.insert_str(pos - offset, insert_content.as_str());
content.insert_str(pos + offset, insert_content.as_str());
offset += insert_content.len();
} else {
return Err(format!("Cannot find reference `{cross_ref}` from document `{}`. Aborting postprocessing.", doc.borrow().input));

195
src/compiler/process.rs Normal file
View file

@ -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<dyn Source>,
debug_opts: &Vec<String>,
) -> Result<Box<dyn Document<'static>>, 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<PathBuf>,
db_path: &Option<String>,
force_rebuild: bool,
debug_opts: &Vec<String>,
) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, 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<PostProcess>), 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
pub fn process_from_memory(target: Target, sources: Vec<String>) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, String> {
let mut compiled = vec![];
let parser = LangParser::default();
for (idx, content) in sources.iter().enumerate() {
let parse_and_compile = || -> Result<(CompiledDocument, Option<PostProcess>), 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 (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)
}

View file

@ -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),

View file

@ -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() {

View file

@ -91,13 +91,15 @@ impl Element for ExternalReference {
match compiler.target() {
Target::HTML => {
let mut result = "<a href=\"".to_string();
let refname = self.caption.as_ref().unwrap_or(match &self.reference {
CrossReference::Unspecific(name) => &name,
CrossReference::Specific(_, name) => &name,
});
compiler.insert_crossreference(cursor + result.len(), self.reference.clone());
result += format!("\">{}</a>", Compiler::sanitize(Target::HTML, refname)).as_str();
if let Some(caption) = &self.caption {
result +=
format!("\">{}</a>", Compiler::sanitize(Target::HTML, caption)).as_str();
} else {
result += format!("\">{}</a>", 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,95 @@ impl RegexRule for ReferenceRule {
reports
}
}
#[cfg(test)]
mod tests {
use crate::compiler::process::process_from_memory;
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::<String> };
ExternalReference { reference == CrossReference::Unspecific("ref".into()), caption == Some("'ref' from any document".to_string()) };
};
);
}
#[test]
pub fn test_external()
{
let result = process_from_memory(Target::HTML, vec![
r#"
@html.page_title = 0
@compiler.output = a.html
#{ref} Referenceable section
"#.into(),
r#"
@html.page_title = 1
@compiler.output = b.html
§{#ref}
§{a#ref}
#{ref2} Another Referenceable section
"#.into(),
r#"
@html.page_title = 2
§{#ref}[caption=from 0]
§{#ref2}[caption=from 1]
"#.into(),
]).unwrap();
assert!(result[1].0.borrow().body.starts_with("<div class=\"content\"><p><a href=\"a.html#Referenceable_section\">#ref</a><a href=\"a.html#Referenceable_section\">a#ref</a></p>"));
assert!(result[2].0.borrow().body.starts_with("<div class=\"content\"><p><a href=\"a.html#Referenceable_section\">from 0</a><a href=\"b.html#Another_Referenceable_section\">from 1</a></p>"));
}
}

View file

@ -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<String>,
) -> Result<Box<dyn Document<'static>>, 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<PathBuf>,
db_path: &Option<String>,
force_rebuild: bool,
debug_opts: &Vec<String>,
) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, 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<PostProcess>), 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<String> = 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

View file

@ -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;
}