From d6e6dbd660d42c805490d26c4d8f7dd709fb5b8f Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Tue, 13 Aug 2024 19:18:10 +0200 Subject: [PATCH] Cross-references --- README.md | 6 +- src/compiler/compiler.rs | 66 ++++++++++--- src/compiler/mod.rs | 1 + src/compiler/navigation.rs | 35 ++++--- src/compiler/postprocess.rs | 83 ++++++++++++++++ src/document/document.rs | 28 +++++- src/document/element.rs | 12 ++- src/elements/blockquote.rs | 4 +- src/elements/code.rs | 2 +- src/elements/comment.rs | 2 +- src/elements/graphviz.rs | 2 +- src/elements/layout.rs | 2 +- src/elements/link.rs | 4 +- src/elements/list.rs | 6 +- src/elements/media.rs | 31 +++--- src/elements/paragraph.rs | 4 +- src/elements/raw.rs | 2 +- src/elements/reference.rs | 187 +++++++++++++++++++++++++++--------- src/elements/section.rs | 10 +- src/elements/style.rs | 2 +- src/elements/tex.rs | 2 +- src/elements/text.rs | 2 +- src/main.rs | 76 +++++++++++---- 23 files changed, 433 insertions(+), 136 deletions(-) create mode 100644 src/compiler/postprocess.rs diff --git a/README.md b/README.md index 4683722..4ffc9a3 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ cargo build --release --bin nml - [x] LaTeX rendering - [x] Graphviz rendering - [x] Media - - [ ] References - - [ ] Navigation - - [ ] Cross-Document references + - [x] References + - [x] Navigation + - [x] Cross-Document references - [ ] Complete Lua api - [ ] Documentation - [ ] Table diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 24c37a5..fec022e 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -6,10 +6,13 @@ use std::rc::Rc; use rusqlite::Connection; +use crate::document::document::CrossReference; use crate::document::document::Document; use crate::document::document::ElemReference; use crate::document::variable::Variable; +use super::postprocess::PostProcess; + #[derive(Clone, Copy)] pub enum Target { HTML, @@ -21,8 +24,9 @@ pub struct Compiler { target: Target, cache: Option>, reference_count: RefCell>>, - // TODO: External references, i.e resolved later sections_counter: RefCell>, + + unresolved_references: RefCell>, } impl Compiler { @@ -39,6 +43,7 @@ impl Compiler { cache: cache.map(|con| RefCell::new(con)), reference_count: RefCell::new(HashMap::new()), sections_counter: RefCell::new(vec![]), + unresolved_references: RefCell::new(vec![]), } } @@ -115,6 +120,13 @@ impl Compiler { } } + /// Inserts a new crossreference + pub fn insert_crossreference(&self, pos: usize, reference: CrossReference) { + self.unresolved_references + .borrow_mut() + .push((pos, reference)); + } + pub fn target(&self) -> Target { self.target } pub fn cache(&self) -> Option> { @@ -179,7 +191,7 @@ impl Compiler { result } - pub fn compile(&self, document: &dyn Document) -> CompiledDocument { + pub fn compile(&self, document: &dyn Document) -> (CompiledDocument, PostProcess) { let borrow = document.content().borrow(); // Header @@ -190,7 +202,7 @@ impl Compiler { for i in 0..borrow.len() { let elem = &borrow[i]; - match elem.compile(self, document) { + match elem.compile(self, document, body.len()) { Ok(result) => body.push_str(result.as_str()), Err(err) => println!("Unable to compile element: {err}\n{elem:#?}"), } @@ -209,14 +221,35 @@ impl Compiler { .map(|(key, var)| (key.clone(), var.to_string())) .collect::>(); - CompiledDocument { + // References + let references = document + .scope() + .borrow_mut() + .referenceable + .iter() + .map(|(key, reference)| { + let elem = document.get_from_reference(reference).unwrap(); + let refid = self.reference_id(document, *reference); + + (key.clone(), elem.refid(self, refid)) + }) + .collect::>(); + + let postprocess = PostProcess { + resolve_references: self.unresolved_references.replace(vec![]), + }; + + let cdoc = CompiledDocument { input: document.source().name().clone(), mtime: 0, variables, + references, header, body, footer, - } + }; + + (cdoc, postprocess) } } @@ -227,12 +260,14 @@ pub struct CompiledDocument { /// 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 + /// All the variables defined in the document + /// with values mapped by [`Variable::to_string()`] pub variables: HashMap, + /// All the referenceable elements in the document + /// with values mapped by [`ReferenceableElement::refid()`] + pub references: HashMap, + /// Compiled document's header pub header: String, /// Compiled document's body @@ -245,10 +280,11 @@ 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 ( + "CREATE TABLE IF NOT EXISTS compiled_documents( input TEXT PRIMARY KEY, mtime INTEGER NOT NULL, variables TEXT NOT NULL, + internal_references TEXT NOT NULL, header TEXT NOT NULL, body TEXT NOT NULL, footer TEXT NOT NULL @@ -258,7 +294,7 @@ impl CompiledDocument { 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)" + "INSERT OR REPLACE INTO compiled_documents (input, mtime, variables, internal_references, header, body, footer) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)" } pub fn init_cache(con: &Connection) -> Result { @@ -271,9 +307,10 @@ impl CompiledDocument { input: input.to_string(), 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), + references: serde_json::from_str(row.get_unwrap::<_, String>(3).as_str()).unwrap(), + header: row.get_unwrap::<_, String>(4), + body: row.get_unwrap::<_, String>(5), + footer: row.get_unwrap::<_, String>(6), }) }) .ok() @@ -287,6 +324,7 @@ impl CompiledDocument { &self.input, &self.mtime, serde_json::to_string(&self.variables).unwrap(), + serde_json::to_string(&self.references).unwrap(), &self.header, &self.body, &self.footer, diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index ecf558f..0ccee49 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -1,2 +1,3 @@ pub mod compiler; pub mod navigation; +pub mod postprocess; diff --git a/src/compiler/navigation.rs b/src/compiler/navigation.rs index 2ce01c5..99ac351 100644 --- a/src/compiler/navigation.rs +++ b/src/compiler/navigation.rs @@ -1,9 +1,11 @@ +use std::cell::RefCell; use std::collections::HashMap; use crate::compiler::compiler::Compiler; use super::compiler::CompiledDocument; use super::compiler::Target; +use super::postprocess::PostProcess; #[derive(Debug, Default)] pub struct NavEntry { @@ -13,10 +15,14 @@ pub struct NavEntry { impl NavEntry { // FIXME: Sanitize - pub fn compile(&self, target: Target, doc: &CompiledDocument) -> String { + pub fn compile(&self, target: Target, doc: &RefCell) -> String { + let doc_borrow = doc.borrow(); let categories = vec![ - doc.get_variable("nav.category").map_or("", |s| s.as_str()), - doc.get_variable("nav.subcategory") + doc_borrow + .get_variable("nav.category") + .map_or("", |s| s.as_str()), + doc_borrow + .get_variable("nav.subcategory") .map_or("", |s| s.as_str()), ]; @@ -101,7 +107,9 @@ impl NavEntry { } } -pub fn create_navigation(docs: &Vec) -> Result { +pub fn create_navigation( + docs: &Vec<(RefCell, Option)>, +) -> Result { let mut nav = NavEntry { entries: vec![], children: HashMap::new(), @@ -110,19 +118,20 @@ pub fn create_navigation(docs: &Vec) -> Result (title, path), _ => { - eprintln!("Skipping navigation generation for `{}`, must have a defined `@nav.title` and `@compiler.output`", doc.input); + eprintln!("Skipping navigation generation for `{}`, must have a defined `@nav.title` and `@compiler.output`", doc_borrow.input); continue; } }; @@ -134,7 +143,7 @@ pub fn create_navigation(docs: &Vec) -> Result { eprintln!( "Skipping `{}`: No `@nav.category`, but `@nav.subcategory` is set", - doc.input + doc_borrow.input ); continue; } diff --git a/src/compiler/postprocess.rs b/src/compiler/postprocess.rs new file mode 100644 index 0000000..85f3263 --- /dev/null +++ b/src/compiler/postprocess.rs @@ -0,0 +1,83 @@ +use std::cell::RefCell; + +use crate::document::document::CrossReference; + +use super::compiler::CompiledDocument; +use super::compiler::Target; + +/// Represents the list of tasks that have to be ran after the document has been compiled and the +/// compiled document list has been built. Every task is stored with a raw byte position in the +/// compiled document's body. The position represents the original position and thus should be +/// offset accordingly to other postprocessing tasks. +pub struct PostProcess { + /// List of references to resolve i.e insert the resolved refname at a certain byte position + /// in the document's body + pub resolve_references: Vec<(usize, CrossReference)>, +} + +impl PostProcess { + /// Applies postprocessing to a [`CompiledDocument`] + pub fn apply( + &self, + target: Target, + list: &Vec<(RefCell, Option)>, + doc: &RefCell, + ) -> Result { + let mut content = doc.borrow().body.clone(); + + let mut offset = 0; + for (pos, cross_ref) in &self.resolve_references { + // Cross-references + let mut found_ref: Option<(String, &RefCell)> = None; + match cross_ref { + CrossReference::Unspecific(name) => { + for (doc, _) in list { + 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)); + } + + found_ref = Some((found.clone(), &doc)); + } + } + } + CrossReference::Specific(doc_name, name) => { + let ref_doc = list.iter().find(|(doc, _)| { + let doc_borrow = doc.borrow(); + if let Some(outname) = doc_borrow.variables.get("compiler.output") { + // Strip extension + let split_at = outname.rfind('.').unwrap_or(outname.len()); + return doc_name == outname.split_at(split_at).0; + } + + false + }); + if ref_doc.is_none() { + return Err(format!( + "Cannot find document `{doc_name}` for reference `{name}` in `{}`", + doc.borrow().input + )); + } + + if let Some(found) = ref_doc.unwrap().0.borrow().references.get(name) { + found_ref = Some((found.clone(), &ref_doc.unwrap().0)); + } + } + } + if let Some((found_ref, found_doc)) = &found_ref { + let found_borrow = found_doc.borrow(); + let found_path = found_borrow.get_variable("compiler.output").ok_or(format!( + "Unable to get the output. Aborting postprocessing." + ))?; + let insert_content = format!("{found_path}#{found_ref}"); + 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)); + } + } + + Ok(content) + } +} diff --git a/src/document/document.rs b/src/document/document.rs index 2e92c6a..4f94268 100644 --- a/src/document/document.rs +++ b/src/document/document.rs @@ -4,13 +4,17 @@ use std::cell::RefMut; use std::collections::hash_map::HashMap; use std::rc::Rc; +use serde::Deserialize; +use serde::Serialize; + use crate::parser::source::Source; use super::element::Element; use super::element::ReferenceableElement; use super::variable::Variable; -#[derive(Debug, Clone, Copy)] +/// For references inside the current document +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum ElemReference { Direct(usize), @@ -18,10 +22,30 @@ pub enum ElemReference { Nested(usize, usize), } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CrossReference { + /// When the referenced document is unspecified + Unspecific(String), + + /// When the referenced document is specified + Specific(String, String), +} + +impl core::fmt::Display for CrossReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self + { + CrossReference::Unspecific(name) => write!(f, "#{name}"), + CrossReference::Specific(doc_name, name) => write!(f, "{doc_name}#{name}"), + } + } +} + #[derive(Debug)] pub struct Scope { /// List of all referenceable elements in current scope. - /// All elements in this should return a non empty + /// All elements in this should return a non empty element + /// when [`Element::as_referenceable`] is called pub referenceable: HashMap, pub variables: HashMap>, } diff --git a/src/document/element.rs b/src/document/element.rs index 049519a..2200540 100644 --- a/src/document/element.rs +++ b/src/document/element.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use crate::compiler::compiler::Compiler; -use crate::elements::reference::Reference; +use crate::elements::reference::InternalReference; use crate::parser::source::Token; use downcast_rs::impl_downcast; use downcast_rs::Downcast; @@ -50,7 +50,7 @@ pub trait Element: Downcast + core::fmt::Debug { fn as_container(&self) -> Option<&dyn ContainerElement> { None } /// Compiles element - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result; + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result; } impl_downcast!(Element); @@ -66,9 +66,13 @@ pub trait ReferenceableElement: Element { &self, compiler: &Compiler, document: &dyn Document, - reference: &Reference, + reference: &InternalReference, refid: usize, ) -> Result; + + /// Gets the refid for a compiler. The refid is some key that can be used from an external + /// document to reference this element. + fn refid(&self, compiler: &Compiler, refid: usize) -> String; } pub trait ContainerElement: Element { @@ -89,7 +93,7 @@ impl Element for DocumentEnd { fn element_name(&self) -> &'static str { "Document End" } - fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, _compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { Ok(String::new()) } } diff --git a/src/elements/blockquote.rs b/src/elements/blockquote.rs index 0c159bf..3be3d2b 100644 --- a/src/elements/blockquote.rs +++ b/src/elements/blockquote.rs @@ -76,7 +76,7 @@ impl Element for Blockquote { fn element_name(&self) -> &'static str { "Blockquote" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { match compiler.target() { HTML => { let mut result = r#"
"#.to_string(); @@ -124,7 +124,7 @@ impl Element for Blockquote { result += "

"; for elem in &self.content { - result += elem.compile(compiler, document)?.as_str(); + result += elem.compile(compiler, document, cursor+result.len())?.as_str(); } result += "

"; if self.style.author_pos == After { diff --git a/src/elements/code.rs b/src/elements/code.rs index 0904731..d659e9b 100644 --- a/src/elements/code.rs +++ b/src/elements/code.rs @@ -263,7 +263,7 @@ impl Element for Code { fn element_name(&self) -> &'static str { "Code Block" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { match compiler.target() { Target::HTML => { static CACHE_INIT: Once = Once::new(); diff --git a/src/elements/comment.rs b/src/elements/comment.rs index a10dbb5..7390e75 100644 --- a/src/elements/comment.rs +++ b/src/elements/comment.rs @@ -25,7 +25,7 @@ impl Element for Comment { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Invisible } fn element_name(&self) -> &'static str { "Comment" } - fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, _compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { Ok("".to_string()) } } diff --git a/src/elements/graphviz.rs b/src/elements/graphviz.rs index 9b3ecab..4bddaa0 100644 --- a/src/elements/graphviz.rs +++ b/src/elements/graphviz.rs @@ -111,7 +111,7 @@ impl Element for Graphviz { fn element_name(&self) -> &'static str { "Graphviz" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { match compiler.target() { Target::HTML => { static CACHE_INIT: Once = Once::new(); diff --git a/src/elements/layout.rs b/src/elements/layout.rs index 9e2a48a..374dc9b 100644 --- a/src/elements/layout.rs +++ b/src/elements/layout.rs @@ -232,7 +232,7 @@ impl Element for Layout { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Block } fn element_name(&self) -> &'static str { "Layout" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, _cursor: usize) -> Result { self.layout .compile(self.token, self.id, &self.properties, compiler, document) } diff --git a/src/elements/link.rs b/src/elements/link.rs index f191e36..b93f26f 100644 --- a/src/elements/link.rs +++ b/src/elements/link.rs @@ -37,7 +37,7 @@ impl Element for Link { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Inline } fn element_name(&self) -> &'static str { "Link" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { match compiler.target() { Target::HTML => { let mut result = format!( @@ -46,7 +46,7 @@ impl Element for Link { ); for elem in &self.display { - result += elem.compile(compiler, document)?.as_str(); + result += elem.compile(compiler, document, cursor+result.len())?.as_str(); } result += ""; diff --git a/src/elements/list.rs b/src/elements/list.rs index e5fdc93..8bc84ef 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -48,7 +48,7 @@ impl Element for ListMarker { fn element_name(&self) -> &'static str { "List Marker" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { match compiler.target() { Target::HTML => match (self.kind, self.numbered) { (MarkerKind::Close, true) => Ok("".to_string()), @@ -76,12 +76,12 @@ impl Element for ListEntry { fn element_name(&self) -> &'static str { "List Entry" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { match compiler.target() { Target::HTML => { let mut result = "
  • ".to_string(); for elem in &self.content { - result += elem.compile(compiler, document)?.as_str(); + result += elem.compile(compiler, document, cursor+result.len())?.as_str(); } result += "
  • "; Ok(result) diff --git a/src/elements/media.rs b/src/elements/media.rs index 26a277a..f8acbc4 100644 --- a/src/elements/media.rs +++ b/src/elements/media.rs @@ -35,7 +35,7 @@ use crate::parser::util::PropertyMapError; use crate::parser::util::PropertyParser; use super::paragraph::Paragraph; -use super::reference::Reference; +use super::reference::InternalReference; #[derive(Debug, PartialEq, Eq)] pub enum MediaType { @@ -72,17 +72,14 @@ impl Element for Media { fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { match compiler.target() { Target::HTML => { let mut result = String::new(); result.push_str("
    "); for medium in &self.media { - match medium.compile(compiler, document) { - Ok(r) => result.push_str(r.as_str()), - Err(e) => return Err(e), - } + result += medium.compile(compiler, document, cursor+result.len())?.as_str(); } result.push_str("
    "); @@ -135,16 +132,20 @@ impl Element for Medium { fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { match compiler.target() { Target::HTML => { let mut result = String::new(); + // Reference + let elemref = document.get_reference(self.reference.as_str()).unwrap(); + let refcount = compiler.reference_id(document, elemref); + let width = self .width .as_ref() .map_or(String::new(), |w| format!(r#" style="width:{w};""#)); - result.push_str(format!(r#"
    "#).as_str()); + result.push_str(format!(r#"
    "#, self.refid(compiler, refcount)).as_str()); result += match self.media_type { MediaType::IMAGE => format!(r#""#, self.uri), MediaType::VIDEO => format!( @@ -168,17 +169,11 @@ impl Element for Medium { }) .unwrap_or(String::new()); - // Reference - let elemref = document.get_reference(self.reference.as_str()).unwrap(); - let refcount = compiler.reference_id(document, elemref); result.push_str( format!(r#"

    ({refcount}){caption}

    "#).as_str(), ); if let Some(paragraph) = self.description.as_ref() { - match paragraph.compile(compiler, document) { - Ok(res) => result.push_str(res.as_str()), - Err(err) => return Err(err), - } + result += paragraph.compile(compiler, document, cursor+result.len())?.as_str(); } result.push_str("
    "); @@ -198,7 +193,7 @@ impl ReferenceableElement for Medium { &self, compiler: &Compiler, _document: &dyn Document, - reference: &Reference, + reference: &InternalReference, refid: usize, ) -> Result { match compiler.target() { @@ -219,6 +214,10 @@ impl ReferenceableElement for Medium { _ => todo!(""), } } + + fn refid(&self, _compiler: &Compiler, refid: usize) -> String { + format!("medium-{refid}") + } } #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::media")] diff --git a/src/elements/paragraph.rs b/src/elements/paragraph.rs index 3c962b0..ef10282 100644 --- a/src/elements/paragraph.rs +++ b/src/elements/paragraph.rs @@ -48,7 +48,7 @@ impl Element for Paragraph { fn element_name(&self) -> &'static str { "Paragraph" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { if self.content.is_empty() { return Ok(String::new()); } @@ -63,7 +63,7 @@ impl Element for Paragraph { result.push_str("

    "); for elems in &self.content { - result += elems.compile(compiler, document)?.as_str(); + result += elems.compile(compiler, document, cursor+result.len())?.as_str(); } result.push_str("

    "); diff --git a/src/elements/raw.rs b/src/elements/raw.rs index edb0b98..53b8fd7 100644 --- a/src/elements/raw.rs +++ b/src/elements/raw.rs @@ -39,7 +39,7 @@ impl Element for Raw { fn element_name(&self) -> &'static str { "Raw" } - fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, _compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { Ok(self.content.clone()) } } diff --git a/src/elements/reference.rs b/src/elements/reference.rs index 5647091..f93bcb1 100644 --- a/src/elements/reference.rs +++ b/src/elements/reference.rs @@ -12,6 +12,7 @@ use regex::Regex; use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Target; +use crate::document::document::CrossReference; use crate::document::document::Document; use crate::document::element::ElemKind; use crate::document::element::Element; @@ -27,24 +28,29 @@ use crate::parser::util::PropertyMap; use crate::parser::util::PropertyParser; #[derive(Debug)] -pub struct Reference { +pub struct InternalReference { pub(self) location: Token, pub(self) refname: String, pub(self) caption: Option, } -impl Reference { +impl InternalReference { pub fn caption(&self) -> Option<&String> { self.caption.as_ref() } } -impl Element for Reference { +impl Element for InternalReference { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Inline } fn element_name(&self) -> &'static str { "Reference" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile( + &self, + compiler: &Compiler, + document: &dyn Document, + _cursor: usize, + ) -> Result { match compiler.target() { Target::HTML => { let elemref = document.get_reference(self.refname.as_str()).unwrap(); @@ -62,6 +68,43 @@ impl Element for Reference { } } +#[derive(Debug)] +pub struct ExternalReference { + pub(self) location: Token, + pub(self) reference: CrossReference, + pub(self) caption: Option, +} + +impl Element for ExternalReference { + fn location(&self) -> &Token { &self.location } + + fn kind(&self) -> ElemKind { ElemKind::Inline } + + fn element_name(&self) -> &'static str { "Reference" } + + fn compile( + &self, + compiler: &Compiler, + _document: &dyn Document, + cursor: usize, + ) -> Result { + 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(); + Ok(result) + } + _ => todo!(""), + } + } +} + #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::reference")] pub struct ReferenceRule { re: [Regex; 1], @@ -142,41 +185,71 @@ impl RegexRule for ReferenceRule { ) -> Vec, Range)>> { let mut reports = vec![]; - let refname = match ( - matches.get(1).unwrap(), - validate_refname(document, matches.get(1).unwrap().as_str(), false), - ) { - (m, Ok(refname)) => { - if document.get_reference(refname).is_none() { - reports.push( - Report::build(ReportKind::Error, token.source(), m.start()) - .with_message("Uknown Reference Refname") - .with_label( - Label::new((token.source().clone(), m.range())).with_message( - format!( - "Could not find element with reference: `{}`", - refname.fg(state.parser.colors().info) - ), - ), - ) - .finish(), - ); - return reports; + let (refdoc, refname) = if let Some(refname_match) = matches.get(1) { + if let Some(sep) = refname_match.as_str().find('#') + // External reference + { + let refdoc = refname_match.as_str().split_at(sep).0; + match validate_refname(document, refname_match.as_str().split_at(sep + 1).1, false) + { + Err(err) => { + reports.push( + Report::build(ReportKind::Error, token.source(), refname_match.start()) + .with_message("Invalid Reference Refname") + .with_label( + Label::new((token.source().clone(), refname_match.range())) + .with_message(err), + ) + .finish(), + ); + return reports; + } + Ok(refname) => (Some(refdoc.to_string()), refname.to_string()), + } + } else + // Internal reference + { + match validate_refname(document, refname_match.as_str(), false) { + Err(err) => { + reports.push( + Report::build(ReportKind::Error, token.source(), refname_match.start()) + .with_message("Invalid Reference Refname") + .with_label( + Label::new((token.source().clone(), refname_match.range())) + .with_message(err), + ) + .finish(), + ); + return reports; + } + Ok(refname) => { + if document.get_reference(refname).is_none() { + reports.push( + Report::build( + ReportKind::Error, + token.source(), + refname_match.start(), + ) + .with_message("Uknown Reference Refname") + .with_label( + Label::new((token.source().clone(), refname_match.range())) + .with_message(format!( + "Could not find element with reference: `{}`", + refname.fg(state.parser.colors().info) + )), + ) + .finish(), + ); + return reports; + } + (None, refname.to_string()) + } } - refname.to_string() - } - (m, Err(err)) => { - reports.push( - Report::build(ReportKind::Error, token.source(), m.start()) - .with_message("Invalid Reference Refname") - .with_label( - Label::new((token.source().clone(), m.range())).with_message(err), - ) - .finish(), - ); - return reports; } + } else { + panic!("Unknown error") }; + // Properties let properties = match self.parse_properties(state.parser.colors(), &token, &matches.get(3)) { @@ -194,14 +267,40 @@ impl RegexRule for ReferenceRule { .ok() .and_then(|(_, s)| Some(s)); - state.push( - document, - Box::new(Reference { - location: token, - refname, - caption, - }), - ); + if let Some(refdoc) = refdoc { + if caption.is_none() { + return reports; + } + + if refdoc.is_empty() { + state.push( + document, + Box::new(ExternalReference { + location: token, + reference: CrossReference::Unspecific(refname), + caption, + }), + ); + } else { + state.push( + document, + Box::new(ExternalReference { + location: token, + reference: CrossReference::Specific(refdoc, refname), + caption, + }), + ); + } + } else { + state.push( + document, + Box::new(InternalReference { + location: token, + refname, + caption, + }), + ); + } reports } diff --git a/src/elements/section.rs b/src/elements/section.rs index 6c257c6..a213da1 100644 --- a/src/elements/section.rs +++ b/src/elements/section.rs @@ -24,6 +24,8 @@ use std::ops::Range; use std::rc::Rc; use std::sync::Arc; +use super::reference::InternalReference; + #[derive(Debug)] pub struct Section { pub(self) location: Token, @@ -43,7 +45,7 @@ impl Element for Section { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Block } fn element_name(&self) -> &'static str { "Section" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { match compiler.target() { Target::HTML => { // Section numbering @@ -112,7 +114,7 @@ impl ReferenceableElement for Section { &self, compiler: &Compiler, _document: &dyn Document, - reference: &super::reference::Reference, + reference: &InternalReference, _refid: usize, ) -> Result { match compiler.target() { @@ -133,6 +135,10 @@ impl ReferenceableElement for Section { _ => todo!(""), } } + + fn refid(&self, compiler: &Compiler, _refid: usize) -> String { + Compiler::refname(compiler.target(), self.title.as_str()) + } } #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::section")] diff --git a/src/elements/style.rs b/src/elements/style.rs index f9bd1eb..c53cdb6 100644 --- a/src/elements/style.rs +++ b/src/elements/style.rs @@ -46,7 +46,7 @@ impl Element for Style { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Inline } fn element_name(&self) -> &'static str { "Style" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { match compiler.target() { Target::HTML => { Ok([ diff --git a/src/elements/tex.rs b/src/elements/tex.rs index da7c091..f51b716 100644 --- a/src/elements/tex.rs +++ b/src/elements/tex.rs @@ -149,7 +149,7 @@ impl Element for Tex { fn element_name(&self) -> &'static str { "LaTeX" } - fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, document: &dyn Document, _cursor: usize) -> Result { match compiler.target() { Target::HTML => { static CACHE_INIT: Once = Once::new(); diff --git a/src/elements/text.rs b/src/elements/text.rs index 16130a0..08d917e 100644 --- a/src/elements/text.rs +++ b/src/elements/text.rs @@ -37,7 +37,7 @@ impl Element for Text { fn kind(&self) -> ElemKind { ElemKind::Inline } fn element_name(&self) -> &'static str { "Text" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { + fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { Ok(Compiler::sanitize(compiler.target(), self.content.as_str())) } } diff --git a/src/main.rs b/src/main.rs index 57099d4..798e863 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod elements; mod lua; mod parser; +use std::cell::RefCell; use std::env; use std::io::BufWriter; use std::io::Write; @@ -17,6 +18,7 @@ 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; @@ -66,7 +68,6 @@ fn parse( .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(); @@ -85,7 +86,7 @@ fn parse( } if parser.has_error() { - return Err("Parsing failed aborted due to errors while parsing".to_string()); + return Err("Parsing failed due to errors while parsing".to_string()); } Ok(doc) @@ -97,7 +98,7 @@ fn process( db_path: &Option, force_rebuild: bool, debug_opts: &Vec, -) -> Result, String> { +) -> Result, Option)>, String> { let mut compiled = vec![]; let current_dir = std::env::current_dir() @@ -126,24 +127,20 @@ fn process( 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 { + 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 = compiler.compile(&*doc); + let (mut compiled, postprocess) = 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) + Ok((compiled, Some(postprocess))) }; - let cdoc = if force_rebuild { + let (cdoc, post) = if force_rebuild { parse_and_compile()? } else { match CompiledDocument::from_cache(&con, file.to_str().unwrap()) { @@ -151,14 +148,35 @@ fn process( if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() { parse_and_compile()? } else { - compiled + (compiled, None) } } None => parse_and_compile()?, } }; - compiled.push(cdoc); + 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) @@ -344,8 +362,8 @@ fn main() -> ExitCode { } // Parse, compile using the cache - let compiled = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) { - Ok(compiled) => compiled, + let processed = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) { + Ok(processed) => processed, Err(e) => { eprintln!("{e}"); return ExitCode::FAILURE; @@ -356,7 +374,7 @@ fn main() -> ExitCode { // Batch mode { // Build navigation - let navigation = match create_navigation(&compiled) { + let navigation = match create_navigation(&processed) { Ok(nav) => nav, Err(e) => { eprintln!("{e}"); @@ -365,14 +383,15 @@ fn main() -> ExitCode { }; // Output - for doc in compiled { + for (doc, _) in &processed { let out_path = match doc + .borrow() .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); + eprintln!("Unable to get output file for `{}`", doc.borrow().input); continue; } }; @@ -382,18 +401,33 @@ fn main() -> ExitCode { let mut writer = BufWriter::new(file); - write!(writer, "{}{}{}{}", doc.header, nav, doc.body, doc.footer).unwrap(); + write!( + writer, + "{}{}{}{}", + doc.borrow().header, + nav, + doc.borrow().body, + doc.borrow().footer + ) + .unwrap(); writer.flush().unwrap(); } } else // Single file { - for doc in compiled { + for (doc, _) in &processed { let file = std::fs::File::create(output.clone()).unwrap(); let mut writer = BufWriter::new(file); - write!(writer, "{}{}{}", doc.header, doc.body, doc.footer).unwrap(); + write!( + writer, + "{}{}{}", + doc.borrow().header, + doc.borrow().body, + doc.borrow().footer + ) + .unwrap(); writer.flush().unwrap(); } }