Cross-references
This commit is contained in:
parent
d8fd2feefb
commit
d6e6dbd660
23 changed files with 433 additions and 136 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
@ -249,6 +284,7 @@ impl CompiledDocument {
|
||||||
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,
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
|
pub mod postprocess;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
83
src/compiler/postprocess.rs
Normal file
83
src/compiler/postprocess.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>";
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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>");
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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([
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
76
src/main.rs
76
src/main.rs
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue