Cross-references

This commit is contained in:
ef3d0c3e 2024-08-13 19:18:10 +02:00
parent d8fd2feefb
commit d6e6dbd660
23 changed files with 433 additions and 136 deletions

View file

@ -35,9 +35,9 @@ cargo build --release --bin nml
- [x] LaTeX rendering - [x] LaTeX rendering
- [x] Graphviz rendering - [x] Graphviz rendering
- [x] Media - [x] Media
- [ ] References - [x] References
- [ ] Navigation - [x] Navigation
- [ ] Cross-Document references - [x] Cross-Document references
- [ ] Complete Lua api - [ ] Complete Lua api
- [ ] Documentation - [ ] Documentation
- [ ] Table - [ ] Table

View file

@ -6,10 +6,13 @@ use std::rc::Rc;
use rusqlite::Connection; use rusqlite::Connection;
use crate::document::document::CrossReference;
use crate::document::document::Document; 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::postprocess::PostProcess;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Target { pub enum Target {
HTML, HTML,
@ -21,8 +24,9 @@ pub struct Compiler {
target: Target, target: Target,
cache: Option<RefCell<Connection>>, cache: Option<RefCell<Connection>>,
reference_count: RefCell<HashMap<String, HashMap<String, usize>>>, reference_count: RefCell<HashMap<String, HashMap<String, usize>>>,
// TODO: External references, i.e resolved later
sections_counter: RefCell<Vec<usize>>, sections_counter: RefCell<Vec<usize>>,
unresolved_references: RefCell<Vec<(usize, CrossReference)>>,
} }
impl Compiler { impl Compiler {
@ -39,6 +43,7 @@ impl Compiler {
cache: cache.map(|con| RefCell::new(con)), cache: cache.map(|con| RefCell::new(con)),
reference_count: RefCell::new(HashMap::new()), reference_count: RefCell::new(HashMap::new()),
sections_counter: RefCell::new(vec![]), 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 target(&self) -> Target { self.target }
pub fn cache(&self) -> Option<RefMut<'_, Connection>> { pub fn cache(&self) -> Option<RefMut<'_, Connection>> {
@ -179,7 +191,7 @@ impl Compiler {
result result
} }
pub fn compile(&self, document: &dyn Document) -> CompiledDocument { pub fn compile(&self, document: &dyn Document) -> (CompiledDocument, PostProcess) {
let borrow = document.content().borrow(); let borrow = document.content().borrow();
// Header // Header
@ -190,7 +202,7 @@ impl Compiler {
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, body.len()) {
Ok(result) => body.push_str(result.as_str()), Ok(result) => body.push_str(result.as_str()),
Err(err) => println!("Unable to compile element: {err}\n{elem:#?}"), Err(err) => println!("Unable to compile element: {err}\n{elem:#?}"),
} }
@ -209,14 +221,35 @@ impl Compiler {
.map(|(key, var)| (key.clone(), var.to_string())) .map(|(key, var)| (key.clone(), var.to_string()))
.collect::<HashMap<String, String>>(); .collect::<HashMap<String, String>>();
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::<HashMap<String, String>>();
let postprocess = PostProcess {
resolve_references: self.unresolved_references.replace(vec![]),
};
let cdoc = CompiledDocument {
input: document.source().name().clone(), input: document.source().name().clone(),
mtime: 0, mtime: 0,
variables, variables,
references,
header, header,
body, body,
footer, footer,
} };
(cdoc, postprocess)
} }
} }
@ -227,12 +260,14 @@ pub struct CompiledDocument {
/// Modification time (i.e seconds since last epoch) /// Modification time (i.e seconds since last epoch)
pub mtime: u64, pub mtime: u64,
// TODO: Also store exported references /// All the variables defined in the document
// so they can be referenced from elsewhere /// with values mapped by [`Variable::to_string()`]
// 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>, pub variables: HashMap<String, String>,
/// All the referenceable elements in the document
/// with values mapped by [`ReferenceableElement::refid()`]
pub references: HashMap<String, String>,
/// Compiled document's header /// Compiled document's header
pub header: String, pub header: String,
/// Compiled document's body /// Compiled document's body
@ -245,10 +280,11 @@ impl CompiledDocument {
pub fn get_variable(&self, name: &str) -> Option<&String> { self.variables.get(name) } pub fn get_variable(&self, name: &str) -> Option<&String> { self.variables.get(name) }
fn sql_table() -> &'static str { fn sql_table() -> &'static str {
"CREATE TABLE IF NOT EXISTS compiled_documents ( "CREATE TABLE IF NOT EXISTS compiled_documents(
input TEXT PRIMARY KEY, input TEXT PRIMARY KEY,
mtime INTEGER NOT NULL, mtime INTEGER NOT NULL,
variables TEXT NOT NULL, variables TEXT NOT NULL,
internal_references TEXT NOT NULL,
header TEXT NOT NULL, header TEXT NOT NULL,
body TEXT NOT NULL, body TEXT NOT NULL,
footer 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_get_query() -> &'static str { "SELECT * FROM compiled_documents WHERE input = (?1)" }
fn sql_insert_query() -> &'static str { 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<usize, rusqlite::Error> { pub fn init_cache(con: &Connection) -> Result<usize, rusqlite::Error> {
@ -271,9 +307,10 @@ impl CompiledDocument {
input: input.to_string(), input: input.to_string(),
mtime: row.get_unwrap::<_, u64>(1), mtime: row.get_unwrap::<_, u64>(1),
variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(), variables: serde_json::from_str(row.get_unwrap::<_, String>(2).as_str()).unwrap(),
header: row.get_unwrap::<_, String>(3), references: serde_json::from_str(row.get_unwrap::<_, String>(3).as_str()).unwrap(),
body: row.get_unwrap::<_, String>(4), header: row.get_unwrap::<_, String>(4),
footer: row.get_unwrap::<_, String>(5), body: row.get_unwrap::<_, String>(5),
footer: row.get_unwrap::<_, String>(6),
}) })
}) })
.ok() .ok()
@ -287,6 +324,7 @@ impl CompiledDocument {
&self.input, &self.input,
&self.mtime, &self.mtime,
serde_json::to_string(&self.variables).unwrap(), serde_json::to_string(&self.variables).unwrap(),
serde_json::to_string(&self.references).unwrap(),
&self.header, &self.header,
&self.body, &self.body,
&self.footer, &self.footer,

View file

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

View file

@ -1,9 +1,11 @@
use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Compiler;
use super::compiler::CompiledDocument; use super::compiler::CompiledDocument;
use super::compiler::Target; use super::compiler::Target;
use super::postprocess::PostProcess;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct NavEntry { pub struct NavEntry {
@ -13,10 +15,14 @@ pub struct NavEntry {
impl NavEntry { impl NavEntry {
// FIXME: Sanitize // FIXME: Sanitize
pub fn compile(&self, target: Target, doc: &CompiledDocument) -> String { pub fn compile(&self, target: Target, doc: &RefCell<CompiledDocument>) -> String {
let doc_borrow = doc.borrow();
let categories = vec![ let categories = vec![
doc.get_variable("nav.category").map_or("", |s| s.as_str()), doc_borrow
doc.get_variable("nav.subcategory") .get_variable("nav.category")
.map_or("", |s| s.as_str()),
doc_borrow
.get_variable("nav.subcategory")
.map_or("", |s| s.as_str()), .map_or("", |s| s.as_str()),
]; ];
@ -101,7 +107,9 @@ impl NavEntry {
} }
} }
pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, String> { pub fn create_navigation(
docs: &Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>,
) -> Result<NavEntry, String> {
let mut nav = NavEntry { let mut nav = NavEntry {
entries: vec![], entries: vec![],
children: HashMap::new(), children: HashMap::new(),
@ -110,19 +118,20 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, Strin
// All paths (for duplicate checking) // All paths (for duplicate checking)
let mut all_paths = HashMap::new(); let mut all_paths = HashMap::new();
for doc in docs { for (doc, _) in docs {
let cat = doc.get_variable("nav.category"); let doc_borrow = doc.borrow();
let subcat = doc.get_variable("nav.subcategory"); let cat = doc_borrow.get_variable("nav.category");
let title = doc let subcat = doc_borrow.get_variable("nav.subcategory");
let title = doc_borrow
.get_variable("nav.title") .get_variable("nav.title")
.or(doc.get_variable("doc.title")); .or(doc_borrow.get_variable("doc.title"));
let previous = doc.get_variable("nav.previous").map(|s| s.clone()); let previous = doc_borrow.get_variable("nav.previous").map(|s| s.clone());
let path = doc.get_variable("compiler.output"); let path = doc_borrow.get_variable("compiler.output");
let (title, path) = match (title, path) { let (title, path) = match (title, path) {
(Some(title), Some(path)) => (title, path), (Some(title), Some(path)) => (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; continue;
} }
}; };
@ -134,7 +143,7 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, Strin
None => { None => {
eprintln!( eprintln!(
"Skipping `{}`: No `@nav.category`, but `@nav.subcategory` is set", "Skipping `{}`: No `@nav.category`, but `@nav.subcategory` is set",
doc.input doc_borrow.input
); );
continue; continue;
} }

View file

@ -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<CompiledDocument>, Option<PostProcess>)>,
doc: &RefCell<CompiledDocument>,
) -> Result<String, String> {
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<CompiledDocument>)> = 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)
}
}

View file

@ -4,13 +4,17 @@ use std::cell::RefMut;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::rc::Rc; use std::rc::Rc;
use serde::Deserialize;
use serde::Serialize;
use crate::parser::source::Source; use crate::parser::source::Source;
use super::element::Element; use super::element::Element;
use super::element::ReferenceableElement; use super::element::ReferenceableElement;
use super::variable::Variable; use super::variable::Variable;
#[derive(Debug, Clone, Copy)] /// For references inside the current document
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ElemReference { pub enum ElemReference {
Direct(usize), Direct(usize),
@ -18,10 +22,30 @@ pub enum ElemReference {
Nested(usize, usize), 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)] #[derive(Debug)]
pub struct Scope { pub struct Scope {
/// List of all referenceable elements in current 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<String, ElemReference>, pub referenceable: HashMap<String, ElemReference>,
pub variables: HashMap<String, Rc<dyn Variable>>, pub variables: HashMap<String, Rc<dyn Variable>>,
} }

View file

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Compiler;
use crate::elements::reference::Reference; use crate::elements::reference::InternalReference;
use crate::parser::source::Token; use crate::parser::source::Token;
use downcast_rs::impl_downcast; use downcast_rs::impl_downcast;
use downcast_rs::Downcast; use downcast_rs::Downcast;
@ -50,7 +50,7 @@ pub trait Element: Downcast + core::fmt::Debug {
fn as_container(&self) -> Option<&dyn ContainerElement> { None } fn as_container(&self) -> Option<&dyn ContainerElement> { None }
/// Compiles element /// Compiles element
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String>; fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String>;
} }
impl_downcast!(Element); impl_downcast!(Element);
@ -66,9 +66,13 @@ pub trait ReferenceableElement: Element {
&self, &self,
compiler: &Compiler, compiler: &Compiler,
document: &dyn Document, document: &dyn Document,
reference: &Reference, reference: &InternalReference,
refid: usize, refid: usize,
) -> Result<String, String>; ) -> Result<String, String>;
/// 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 { pub trait ContainerElement: Element {
@ -89,7 +93,7 @@ impl Element for DocumentEnd {
fn element_name(&self) -> &'static str { "Document End" } fn element_name(&self) -> &'static str { "Document End" }
fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, _compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
Ok(String::new()) Ok(String::new())
} }
} }

View file

@ -76,7 +76,7 @@ impl Element for Blockquote {
fn element_name(&self) -> &'static str { "Blockquote" } fn element_name(&self) -> &'static str { "Blockquote" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
HTML => { HTML => {
let mut result = r#"<div class="blockquote-content">"#.to_string(); let mut result = r#"<div class="blockquote-content">"#.to_string();
@ -124,7 +124,7 @@ impl Element for Blockquote {
result += "<p>"; result += "<p>";
for elem in &self.content { for elem in &self.content {
result += elem.compile(compiler, document)?.as_str(); result += elem.compile(compiler, document, cursor+result.len())?.as_str();
} }
result += "</p></blockquote>"; result += "</p></blockquote>";
if self.style.author_pos == After { if self.style.author_pos == After {

View file

@ -263,7 +263,7 @@ impl Element for Code {
fn element_name(&self) -> &'static str { "Code Block" } fn element_name(&self) -> &'static str { "Code Block" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
static CACHE_INIT: Once = Once::new(); static CACHE_INIT: Once = Once::new();

View file

@ -25,7 +25,7 @@ impl Element for Comment {
fn location(&self) -> &Token { &self.location } fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Invisible } fn kind(&self) -> ElemKind { ElemKind::Invisible }
fn element_name(&self) -> &'static str { "Comment" } fn element_name(&self) -> &'static str { "Comment" }
fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, _compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
Ok("".to_string()) Ok("".to_string())
} }
} }

View file

@ -111,7 +111,7 @@ impl Element for Graphviz {
fn element_name(&self) -> &'static str { "Graphviz" } fn element_name(&self) -> &'static str { "Graphviz" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
static CACHE_INIT: Once = Once::new(); static CACHE_INIT: Once = Once::new();

View file

@ -232,7 +232,7 @@ impl Element for Layout {
fn location(&self) -> &Token { &self.location } fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Block } fn kind(&self) -> ElemKind { ElemKind::Block }
fn element_name(&self) -> &'static str { "Layout" } fn element_name(&self) -> &'static str { "Layout" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, _cursor: usize) -> Result<String, String> {
self.layout self.layout
.compile(self.token, self.id, &self.properties, compiler, document) .compile(self.token, self.id, &self.properties, compiler, document)
} }

View file

@ -37,7 +37,7 @@ impl Element for Link {
fn location(&self) -> &Token { &self.location } fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Inline } fn kind(&self) -> ElemKind { ElemKind::Inline }
fn element_name(&self) -> &'static str { "Link" } fn element_name(&self) -> &'static str { "Link" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
let mut result = format!( let mut result = format!(
@ -46,7 +46,7 @@ impl Element for Link {
); );
for elem in &self.display { for elem in &self.display {
result += elem.compile(compiler, document)?.as_str(); result += elem.compile(compiler, document, cursor+result.len())?.as_str();
} }
result += "</a>"; result += "</a>";

View file

@ -48,7 +48,7 @@ impl Element for ListMarker {
fn element_name(&self) -> &'static str { "List Marker" } fn element_name(&self) -> &'static str { "List Marker" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => match (self.kind, self.numbered) { Target::HTML => match (self.kind, self.numbered) {
(MarkerKind::Close, true) => Ok("</ol>".to_string()), (MarkerKind::Close, true) => Ok("</ol>".to_string()),
@ -76,12 +76,12 @@ impl Element for ListEntry {
fn element_name(&self) -> &'static str { "List Entry" } fn element_name(&self) -> &'static str { "List Entry" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
let mut result = "<li>".to_string(); let mut result = "<li>".to_string();
for elem in &self.content { for elem in &self.content {
result += elem.compile(compiler, document)?.as_str(); result += elem.compile(compiler, document, cursor+result.len())?.as_str();
} }
result += "</li>"; result += "</li>";
Ok(result) Ok(result)

View file

@ -35,7 +35,7 @@ use crate::parser::util::PropertyMapError;
use crate::parser::util::PropertyParser; use crate::parser::util::PropertyParser;
use super::paragraph::Paragraph; use super::paragraph::Paragraph;
use super::reference::Reference; use super::reference::InternalReference;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum MediaType { pub enum MediaType {
@ -72,17 +72,14 @@ impl Element for Media {
fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) } fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
let mut result = String::new(); let mut result = String::new();
result.push_str("<div class=\"media\">"); result.push_str("<div class=\"media\">");
for medium in &self.media { for medium in &self.media {
match medium.compile(compiler, document) { result += medium.compile(compiler, document, cursor+result.len())?.as_str();
Ok(r) => result.push_str(r.as_str()),
Err(e) => return Err(e),
}
} }
result.push_str("</div>"); result.push_str("</div>");
@ -135,16 +132,20 @@ impl Element for Medium {
fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) } fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
let mut result = String::new(); 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 let width = self
.width .width
.as_ref() .as_ref()
.map_or(String::new(), |w| format!(r#" style="width:{w};""#)); .map_or(String::new(), |w| format!(r#" style="width:{w};""#));
result.push_str(format!(r#"<div class="medium"{width}>"#).as_str()); result.push_str(format!(r#"<div id="{}" class="medium"{width}>"#, self.refid(compiler, refcount)).as_str());
result += match self.media_type { result += match self.media_type {
MediaType::IMAGE => format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri), MediaType::IMAGE => format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri),
MediaType::VIDEO => format!( MediaType::VIDEO => format!(
@ -168,17 +169,11 @@ impl Element for Medium {
}) })
.unwrap_or(String::new()); .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( result.push_str(
format!(r#"<p class="medium-refname">({refcount}){caption}</p>"#).as_str(), format!(r#"<p class="medium-refname">({refcount}){caption}</p>"#).as_str(),
); );
if let Some(paragraph) = self.description.as_ref() { if let Some(paragraph) = self.description.as_ref() {
match paragraph.compile(compiler, document) { result += paragraph.compile(compiler, document, cursor+result.len())?.as_str();
Ok(res) => result.push_str(res.as_str()),
Err(err) => return Err(err),
}
} }
result.push_str("</div>"); result.push_str("</div>");
@ -198,7 +193,7 @@ impl ReferenceableElement for Medium {
&self, &self,
compiler: &Compiler, compiler: &Compiler,
_document: &dyn Document, _document: &dyn Document,
reference: &Reference, reference: &InternalReference,
refid: usize, refid: usize,
) -> Result<String, String> { ) -> Result<String, String> {
match compiler.target() { match compiler.target() {
@ -219,6 +214,10 @@ impl ReferenceableElement for Medium {
_ => todo!(""), _ => todo!(""),
} }
} }
fn refid(&self, _compiler: &Compiler, refid: usize) -> String {
format!("medium-{refid}")
}
} }
#[auto_registry::auto_registry(registry = "rules", path = "crate::elements::media")] #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::media")]

View file

@ -48,7 +48,7 @@ impl Element for Paragraph {
fn element_name(&self) -> &'static str { "Paragraph" } fn element_name(&self) -> &'static str { "Paragraph" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result<String, String> {
if self.content.is_empty() { if self.content.is_empty() {
return Ok(String::new()); return Ok(String::new());
} }
@ -63,7 +63,7 @@ impl Element for Paragraph {
result.push_str("<p>"); result.push_str("<p>");
for elems in &self.content { 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("</p>"); result.push_str("</p>");

View file

@ -39,7 +39,7 @@ impl Element for Raw {
fn element_name(&self) -> &'static str { "Raw" } fn element_name(&self) -> &'static str { "Raw" }
fn compile(&self, _compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, _compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
Ok(self.content.clone()) Ok(self.content.clone())
} }
} }

View file

@ -12,6 +12,7 @@ use regex::Regex;
use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Compiler;
use crate::compiler::compiler::Target; use crate::compiler::compiler::Target;
use crate::document::document::CrossReference;
use crate::document::document::Document; use crate::document::document::Document;
use crate::document::element::ElemKind; use crate::document::element::ElemKind;
use crate::document::element::Element; use crate::document::element::Element;
@ -27,24 +28,29 @@ use crate::parser::util::PropertyMap;
use crate::parser::util::PropertyParser; use crate::parser::util::PropertyParser;
#[derive(Debug)] #[derive(Debug)]
pub struct Reference { pub struct InternalReference {
pub(self) location: Token, pub(self) location: Token,
pub(self) refname: String, pub(self) refname: String,
pub(self) caption: Option<String>, pub(self) caption: Option<String>,
} }
impl Reference { impl InternalReference {
pub fn caption(&self) -> Option<&String> { self.caption.as_ref() } 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 location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Inline } fn kind(&self) -> ElemKind { ElemKind::Inline }
fn element_name(&self) -> &'static str { "Reference" } fn element_name(&self) -> &'static str { "Reference" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(
&self,
compiler: &Compiler,
document: &dyn Document,
_cursor: usize,
) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
let elemref = document.get_reference(self.refname.as_str()).unwrap(); 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<String>,
}
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<String, String> {
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();
Ok(result)
}
_ => todo!(""),
}
}
}
#[auto_registry::auto_registry(registry = "rules", path = "crate::elements::reference")] #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::reference")]
pub struct ReferenceRule { pub struct ReferenceRule {
re: [Regex; 1], re: [Regex; 1],
@ -142,41 +185,71 @@ impl RegexRule for ReferenceRule {
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> { ) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
let mut reports = vec![]; let mut reports = vec![];
let refname = match ( let (refdoc, refname) = if let Some(refname_match) = matches.get(1) {
matches.get(1).unwrap(), if let Some(sep) = refname_match.as_str().find('#')
validate_refname(document, matches.get(1).unwrap().as_str(), false), // External reference
) { {
(m, Ok(refname)) => { let refdoc = refname_match.as_str().split_at(sep).0;
if document.get_reference(refname).is_none() { match validate_refname(document, refname_match.as_str().split_at(sep + 1).1, false)
{
Err(err) => {
reports.push( reports.push(
Report::build(ReportKind::Error, token.source(), m.start()) Report::build(ReportKind::Error, token.source(), refname_match.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;
}
refname.to_string()
}
(m, Err(err)) => {
reports.push(
Report::build(ReportKind::Error, token.source(), m.start())
.with_message("Invalid Reference Refname") .with_message("Invalid Reference Refname")
.with_label( .with_label(
Label::new((token.source().clone(), m.range())).with_message(err), Label::new((token.source().clone(), refname_match.range()))
.with_message(err),
) )
.finish(), .finish(),
); );
return reports; 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())
}
}
}
} else {
panic!("Unknown error")
}; };
// Properties // Properties
let properties = match self.parse_properties(state.parser.colors(), &token, &matches.get(3)) let properties = match self.parse_properties(state.parser.colors(), &token, &matches.get(3))
{ {
@ -194,14 +267,40 @@ impl RegexRule for ReferenceRule {
.ok() .ok()
.and_then(|(_, s)| Some(s)); .and_then(|(_, s)| Some(s));
if let Some(refdoc) = refdoc {
if caption.is_none() {
return reports;
}
if refdoc.is_empty() {
state.push( state.push(
document, document,
Box::new(Reference { 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, location: token,
refname, refname,
caption, caption,
}), }),
); );
}
reports reports
} }

View file

@ -24,6 +24,8 @@ use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use super::reference::InternalReference;
#[derive(Debug)] #[derive(Debug)]
pub struct Section { pub struct Section {
pub(self) location: Token, pub(self) location: Token,
@ -43,7 +45,7 @@ impl Element for Section {
fn location(&self) -> &Token { &self.location } fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Block } fn kind(&self) -> ElemKind { ElemKind::Block }
fn element_name(&self) -> &'static str { "Section" } fn element_name(&self) -> &'static str { "Section" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
// Section numbering // Section numbering
@ -112,7 +114,7 @@ impl ReferenceableElement for Section {
&self, &self,
compiler: &Compiler, compiler: &Compiler,
_document: &dyn Document, _document: &dyn Document,
reference: &super::reference::Reference, reference: &InternalReference,
_refid: usize, _refid: usize,
) -> Result<String, String> { ) -> Result<String, String> {
match compiler.target() { match compiler.target() {
@ -133,6 +135,10 @@ impl ReferenceableElement for Section {
_ => todo!(""), _ => 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")] #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::section")]

View file

@ -46,7 +46,7 @@ impl Element for Style {
fn location(&self) -> &Token { &self.location } fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Inline } fn kind(&self) -> ElemKind { ElemKind::Inline }
fn element_name(&self) -> &'static str { "Style" } fn element_name(&self) -> &'static str { "Style" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
Ok([ Ok([

View file

@ -149,7 +149,7 @@ impl Element for Tex {
fn element_name(&self) -> &'static str { "LaTeX" } fn element_name(&self) -> &'static str { "LaTeX" }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document, _cursor: usize) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
static CACHE_INIT: Once = Once::new(); static CACHE_INIT: Once = Once::new();

View file

@ -37,7 +37,7 @@ impl Element for Text {
fn kind(&self) -> ElemKind { ElemKind::Inline } fn kind(&self) -> ElemKind { ElemKind::Inline }
fn element_name(&self) -> &'static str { "Text" } fn element_name(&self) -> &'static str { "Text" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result<String, String> {
Ok(Compiler::sanitize(compiler.target(), self.content.as_str())) Ok(Compiler::sanitize(compiler.target(), self.content.as_str()))
} }
} }

View file

@ -5,6 +5,7 @@ mod elements;
mod lua; mod lua;
mod parser; mod parser;
use std::cell::RefCell;
use std::env; use std::env;
use std::io::BufWriter; use std::io::BufWriter;
use std::io::Write; use std::io::Write;
@ -17,6 +18,7 @@ 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;
use compiler::postprocess::PostProcess;
use document::document::Document; use document::document::Document;
use getopts::Options; use getopts::Options;
use parser::langparser::LangParser; use parser::langparser::LangParser;
@ -66,7 +68,6 @@ fn parse(
.for_each(|elem| println!("{elem:#?}")); .for_each(|elem| println!("{elem:#?}"));
println!("-- END AST DEBUGGING --"); println!("-- END AST DEBUGGING --");
} }
if debug_opts.contains(&"ref".to_string()) { if debug_opts.contains(&"ref".to_string()) {
println!("-- BEGIN REFERENCES DEBUGGING --"); println!("-- BEGIN REFERENCES DEBUGGING --");
let sc = doc.scope().borrow(); let sc = doc.scope().borrow();
@ -85,7 +86,7 @@ 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 due to errors while parsing".to_string());
} }
Ok(doc) Ok(doc)
@ -97,7 +98,7 @@ fn process(
db_path: &Option<String>, db_path: &Option<String>,
force_rebuild: bool, force_rebuild: bool,
debug_opts: &Vec<String>, debug_opts: &Vec<String>,
) -> Result<Vec<CompiledDocument>, String> { ) -> Result<Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>, String> {
let mut compiled = vec![]; let mut compiled = vec![];
let current_dir = std::env::current_dir() let current_dir = std::env::current_dir()
@ -126,24 +127,20 @@ fn process(
std::env::set_current_dir(file_parent_path) std::env::set_current_dir(file_parent_path)
.map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?; .map_err(|err| format!("Failed to move to path `{file_parent_path:#?}`: {err}"))?;
let parse_and_compile = || -> Result<CompiledDocument, String> { let parse_and_compile = || -> Result<(CompiledDocument, Option<PostProcess>), String> {
// Parse // Parse
let doc = parse(&parser, file.to_str().unwrap(), debug_opts)?; let doc = parse(&parser, file.to_str().unwrap(), debug_opts)?;
// Compile // Compile
let compiler = Compiler::new(target, db_path.clone()); 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.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()? parse_and_compile()?
} else { } else {
match CompiledDocument::from_cache(&con, file.to_str().unwrap()) { 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() { if compiled.mtime < modified.duration_since(UNIX_EPOCH).unwrap().as_secs() {
parse_and_compile()? parse_and_compile()?
} else { } else {
compiled (compiled, None)
} }
} }
None => parse_and_compile()?, 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) std::env::set_current_dir(current_dir)
@ -344,8 +362,8 @@ fn main() -> ExitCode {
} }
// Parse, compile using the cache // Parse, compile using the cache
let compiled = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) { let processed = match process(Target::HTML, files, &db_path, force_rebuild, &debug_opts) {
Ok(compiled) => compiled, Ok(processed) => processed,
Err(e) => { Err(e) => {
eprintln!("{e}"); eprintln!("{e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;
@ -356,7 +374,7 @@ fn main() -> ExitCode {
// Batch mode // Batch mode
{ {
// Build navigation // Build navigation
let navigation = match create_navigation(&compiled) { let navigation = match create_navigation(&processed) {
Ok(nav) => nav, Ok(nav) => nav,
Err(e) => { Err(e) => {
eprintln!("{e}"); eprintln!("{e}");
@ -365,14 +383,15 @@ fn main() -> ExitCode {
}; };
// Output // Output
for doc in compiled { for (doc, _) in &processed {
let out_path = match doc let out_path = match doc
.borrow()
.get_variable("compiler.output") .get_variable("compiler.output")
.or(input_meta.is_file().then_some(&output)) .or(input_meta.is_file().then_some(&output))
{ {
Some(path) => path.clone(), Some(path) => path.clone(),
None => { None => {
eprintln!("Unable to get output file for `{}`", doc.input); eprintln!("Unable to get output file for `{}`", doc.borrow().input);
continue; continue;
} }
}; };
@ -382,18 +401,33 @@ fn main() -> ExitCode {
let mut writer = BufWriter::new(file); 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(); writer.flush().unwrap();
} }
} else } else
// Single file // Single file
{ {
for doc in compiled { for (doc, _) in &processed {
let file = std::fs::File::create(output.clone()).unwrap(); let file = std::fs::File::create(output.clone()).unwrap();
let mut writer = BufWriter::new(file); 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(); writer.flush().unwrap();
} }
} }