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] Graphviz rendering
- [x] Media
- [ ] References
- [ ] Navigation
- [ ] Cross-Document references
- [x] References
- [x] Navigation
- [x] Cross-Document references
- [ ] Complete Lua api
- [ ] Documentation
- [ ] Table

View file

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

View file

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

View file

@ -1,9 +1,11 @@
use std::cell::RefCell;
use std::collections::HashMap;
use crate::compiler::compiler::Compiler;
use super::compiler::CompiledDocument;
use super::compiler::Target;
use super::postprocess::PostProcess;
#[derive(Debug, Default)]
pub struct NavEntry {
@ -13,10 +15,14 @@ pub struct NavEntry {
impl NavEntry {
// FIXME: Sanitize
pub fn compile(&self, target: Target, doc: &CompiledDocument) -> String {
pub fn compile(&self, target: Target, doc: &RefCell<CompiledDocument>) -> String {
let doc_borrow = doc.borrow();
let categories = vec![
doc.get_variable("nav.category").map_or("", |s| s.as_str()),
doc.get_variable("nav.subcategory")
doc_borrow
.get_variable("nav.category")
.map_or("", |s| s.as_str()),
doc_borrow
.get_variable("nav.subcategory")
.map_or("", |s| s.as_str()),
];
@ -101,7 +107,9 @@ impl NavEntry {
}
}
pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, String> {
pub fn create_navigation(
docs: &Vec<(RefCell<CompiledDocument>, Option<PostProcess>)>,
) -> Result<NavEntry, String> {
let mut nav = NavEntry {
entries: vec![],
children: HashMap::new(),
@ -110,19 +118,20 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, Strin
// All paths (for duplicate checking)
let mut all_paths = HashMap::new();
for doc in docs {
let cat = doc.get_variable("nav.category");
let subcat = doc.get_variable("nav.subcategory");
let title = doc
for (doc, _) in docs {
let doc_borrow = doc.borrow();
let cat = doc_borrow.get_variable("nav.category");
let subcat = doc_borrow.get_variable("nav.subcategory");
let title = doc_borrow
.get_variable("nav.title")
.or(doc.get_variable("doc.title"));
let previous = doc.get_variable("nav.previous").map(|s| s.clone());
let path = doc.get_variable("compiler.output");
.or(doc_borrow.get_variable("doc.title"));
let previous = doc_borrow.get_variable("nav.previous").map(|s| s.clone());
let path = doc_borrow.get_variable("compiler.output");
let (title, path) = match (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;
}
};
@ -134,7 +143,7 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, Strin
None => {
eprintln!(
"Skipping `{}`: No `@nav.category`, but `@nav.subcategory` is set",
doc.input
doc_borrow.input
);
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::rc::Rc;
use serde::Deserialize;
use serde::Serialize;
use crate::parser::source::Source;
use super::element::Element;
use super::element::ReferenceableElement;
use super::variable::Variable;
#[derive(Debug, Clone, Copy)]
/// For references inside the current document
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ElemReference {
Direct(usize),
@ -18,10 +22,30 @@ pub enum ElemReference {
Nested(usize, usize),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CrossReference {
/// When the referenced document is unspecified
Unspecific(String),
/// When the referenced document is specified
Specific(String, String),
}
impl core::fmt::Display for CrossReference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self
{
CrossReference::Unspecific(name) => write!(f, "#{name}"),
CrossReference::Specific(doc_name, name) => write!(f, "{doc_name}#{name}"),
}
}
}
#[derive(Debug)]
pub struct Scope {
/// List of all referenceable elements in current scope.
/// All elements in this should return a non empty
/// All elements in this should return a non empty element
/// when [`Element::as_referenceable`] is called
pub referenceable: HashMap<String, ElemReference>,
pub variables: HashMap<String, Rc<dyn Variable>>,
}

View file

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

View file

@ -76,7 +76,7 @@ impl Element for 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() {
HTML => {
let mut result = r#"<div class="blockquote-content">"#.to_string();
@ -124,7 +124,7 @@ impl Element for Blockquote {
result += "<p>";
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>";
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 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() {
Target::HTML => {
static CACHE_INIT: Once = Once::new();

View file

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

View file

@ -111,7 +111,7 @@ impl Element for 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() {
Target::HTML => {
static CACHE_INIT: Once = Once::new();

View file

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

View file

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

View file

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

View file

@ -48,7 +48,7 @@ impl Element for 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() {
return Ok(String::new());
}
@ -63,7 +63,7 @@ impl Element for Paragraph {
result.push_str("<p>");
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>");

View file

@ -39,7 +39,7 @@ impl Element for 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())
}
}

View file

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

View file

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

View file

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

View file

@ -149,7 +149,7 @@ impl Element for Tex {
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() {
Target::HTML => {
static CACHE_INIT: Once = Once::new();

View file

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

View file

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