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<CompiledDocument>, Option<PostProcess>)>,
doc: &RefCell<CompiledDocument>,
) -> Result<String, String> {
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<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
+fn process_in_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 (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 = "<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,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::<String> };
+ 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<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
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;
+}
+