From b3b99c406998543e18b2c48e51e720322442d794 Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Wed, 14 Aug 2024 11:09:42 +0200 Subject: [PATCH] Added bindings & tests --- src/cache/cache.rs | 4 +- src/compiler/compiler.rs | 25 +++----- src/compiler/navigation.rs | 54 +++++++++++++++- src/compiler/process.rs | 2 +- src/elements/code.rs | 8 +-- src/elements/graphviz.rs | 120 ++++++++++++++++++++++++++++++++++-- src/elements/media.rs | 2 +- src/elements/reference.rs | 34 ++++++----- src/elements/tex.rs | 122 +++++++++++++++++++++++++++++++++++-- src/parser/rule.rs | 2 +- 10 files changed, 321 insertions(+), 52 deletions(-) diff --git a/src/cache/cache.rs b/src/cache/cache.rs index bbe90ed..bc2f418 100644 --- a/src/cache/cache.rs +++ b/src/cache/cache.rs @@ -23,7 +23,7 @@ pub trait Cached { fn key(&self) -> ::Key; - fn init(con: &mut Connection) -> Result<(), rusqlite::Error> { + fn init(con: &Connection) -> Result<(), rusqlite::Error> { con.execute(::sql_table(), ()).map(|_| ()) } @@ -38,7 +38,7 @@ pub trait Cached { /// Note that on error, [`f`] may still have been called fn cached( &self, - con: &mut Connection, + con: &Connection, f: F, ) -> Result<::Value, CachedError> where diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index fec022e..660d69f 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -1,6 +1,5 @@ use std::cell::Ref; use std::cell::RefCell; -use std::cell::RefMut; use std::collections::HashMap; use std::rc::Rc; @@ -20,27 +19,20 @@ pub enum Target { LATEX, } -pub struct Compiler { +pub struct Compiler<'a> { target: Target, - cache: Option>, + cache: Option<&'a Connection>, reference_count: RefCell>>, sections_counter: RefCell>, unresolved_references: RefCell>, } -impl Compiler { - pub fn new(target: Target, db_path: Option) -> Self { - let cache = match db_path { - None => None, - Some(path) => match Connection::open(path) { - Err(e) => panic!("Cannot connect to database: {e}"), - Ok(con) => Some(con), - }, - }; +impl<'a> Compiler<'a> { + pub fn new(target: Target, con: Option<&'a Connection>) -> Self { Self { target, - cache: cache.map(|con| RefCell::new(con)), + cache: con, reference_count: RefCell::new(HashMap::new()), sections_counter: RefCell::new(vec![]), unresolved_references: RefCell::new(vec![]), @@ -94,7 +86,7 @@ impl Compiler { /// /// # Parameters /// - [`reference`] The reference to get or insert - pub fn reference_id<'a>(&self, document: &'a dyn Document, reference: ElemReference) -> usize { + pub fn reference_id<'b>(&self, document: &'b dyn Document, reference: ElemReference) -> usize { let mut borrow = self.reference_count.borrow_mut(); let reference = document.get_from_reference(&reference).unwrap(); let refkey = reference.refcount_key(); @@ -129,8 +121,9 @@ impl Compiler { pub fn target(&self) -> Target { self.target } - pub fn cache(&self) -> Option> { - self.cache.as_ref().map(RefCell::borrow_mut) + pub fn cache(&self) -> Option<&'a Connection> { + self.cache + //self.cache.as_ref().map(RefCell::borrow_mut) } pub fn header(&self, document: &dyn Document) -> String { diff --git a/src/compiler/navigation.rs b/src/compiler/navigation.rs index 99ac351..cc7d5ad 100644 --- a/src/compiler/navigation.rs +++ b/src/compiler/navigation.rs @@ -221,7 +221,9 @@ mod tests { use rand::rngs::OsRng; use rand::RngCore; - use super::*; + use crate::compiler::process::process_from_memory; + +use super::*; #[test] fn sort() { @@ -244,4 +246,54 @@ mod tests { assert_eq!(shuffled, entries); } } + + #[test] + pub fn batch() { + let result = process_from_memory( + Target::HTML, + vec![ + r#" +@html.page_title = 0 +@compiler.output = 0.html +@nav.title = C +@nav.category = First +"# + .into(), + r#" +@html.page_title = 1 +@compiler.output = 1.html +@nav.title = A +@nav.category = First +"# + .into(), + r#" +@html.page_title = 2 +@compiler.output = 2.html +@nav.title = B +@nav.category = First +"# + .into(), + ], + ) + .unwrap(); + + let nav = create_navigation(&result).unwrap(); + assert_eq!(nav.children.get("First").unwrap().entries, vec![ + ( + "A".to_string(), + "1.html".to_string(), + None, + ), + ( + "B".to_string(), + "2.html".to_string(), + None, + ), + ( + "C".to_string(), + "0.html".to_string(), + None, + ), + ]); + } } diff --git a/src/compiler/process.rs b/src/compiler/process.rs index a8980aa..6c9fa6e 100644 --- a/src/compiler/process.rs +++ b/src/compiler/process.rs @@ -102,7 +102,7 @@ pub fn process( let doc = parse(&parser, Rc::new(source), debug_opts)?; // Compile - let compiler = Compiler::new(target, db_path.clone()); + let compiler = Compiler::new(target, Some(&con)); let (mut compiled, postprocess) = compiler.compile(&*doc); compiled.mtime = modified.duration_since(UNIX_EPOCH).unwrap().as_secs(); diff --git a/src/elements/code.rs b/src/elements/code.rs index d659e9b..5c4499e 100644 --- a/src/elements/code.rs +++ b/src/elements/code.rs @@ -268,15 +268,15 @@ impl Element for Code { Target::HTML => { static CACHE_INIT: Once = Once::new(); CACHE_INIT.call_once(|| { - if let Some(mut con) = compiler.cache() { - if let Err(e) = Code::init(&mut con) { + if let Some(con) = compiler.cache() { + if let Err(e) = Code::init(con) { eprintln!("Unable to create cache table: {e}"); } } }); - if let Some(mut con) = compiler.cache() { - match self.cached(&mut con, |s| s.highlight_html(compiler)) { + if let Some(con) = compiler.cache() { + match self.cached(con, |s| s.highlight_html(compiler)) { Ok(s) => Ok(s), Err(e) => match e { CachedError::SqlErr(e) => { diff --git a/src/elements/graphviz.rs b/src/elements/graphviz.rs index 4bddaa0..89cfe8c 100644 --- a/src/elements/graphviz.rs +++ b/src/elements/graphviz.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; use std::ops::Range; use std::rc::Rc; +use std::sync::Arc; use std::sync::Once; +use crate::lua::kernel::CTX; use crate::parser::parser::ParserState; use crate::parser::util::Property; use crate::parser::util::PropertyMapError; @@ -16,6 +18,9 @@ use crypto::sha2::Sha512; use graphviz_rust::cmd::Format; use graphviz_rust::cmd::Layout; use graphviz_rust::exec_dot; +use mlua::Error::BadArgument; +use mlua::Function; +use mlua::Lua; use regex::Captures; use regex::Regex; @@ -111,21 +116,26 @@ impl Element for Graphviz { fn element_name(&self) -> &'static str { "Graphviz" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { + fn compile( + &self, + compiler: &Compiler, + _document: &dyn Document, + _cursor: usize, + ) -> Result { match compiler.target() { Target::HTML => { static CACHE_INIT: Once = Once::new(); CACHE_INIT.call_once(|| { - if let Some(mut con) = compiler.cache() { - if let Err(e) = Graphviz::init(&mut con) { + if let Some(con) = compiler.cache() { + if let Err(e) = Graphviz::init(con) { eprintln!("Unable to create cache table: {e}"); } } }); // TODO: Format svg in a div - if let Some(mut con) = compiler.cache() { - match self.cached(&mut con, |s| s.dot_to_svg()) { + if let Some(con) = compiler.cache() { + match self.cached(con, |s| s.dot_to_svg()) { Ok(s) => Ok(s), Err(e) => match e { CachedError::SqlErr(e) => { @@ -178,7 +188,7 @@ impl GraphRule { } impl RegexRule for GraphRule { - fn name(&self) -> &'static str { "Graph" } + fn name(&self) -> &'static str { "Graphviz" } fn previous(&self) -> Option<&'static str> { Some("Tex") } fn regexes(&self) -> &[regex::Regex] { &self.re } @@ -360,4 +370,102 @@ impl RegexRule for GraphRule { reports } + + fn register_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { + let mut bindings = vec![]; + bindings.push(( + "push".to_string(), + lua.create_function(|_, (layout, width, dot): (String, String, String)| { + let mut result = Ok(()); + + CTX.with_borrow(|ctx| { + ctx.as_ref().map(|ctx| { + let layout = match layout_from_str(layout.as_str()) { + Err(err) => { + result = Err(BadArgument { + to: Some("push".to_string()), + pos: 1, + name: Some("layout".to_string()), + cause: Arc::new(mlua::Error::external(format!( + "Unable to get layout type: {err}" + ))), + }); + return; + } + Ok(layout) => layout, + }; + + ctx.state.push( + ctx.document, + Box::new(Graphviz { + location: ctx.location.clone(), + dot, + layout, + width, + }), + ); + }) + }); + + result + }) + .unwrap(), + )); + + bindings + } +} + +#[cfg(test)] +mod tests { + use crate::parser::langparser::LangParser; + use crate::parser::parser::Parser; + use crate::parser::source::SourceFile; + use crate::validate_document; + + use super::*; + + #[test] + pub fn parse() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +[graph][width=200px, layout=neato] +Some graph... +[/graph] +[graph] +Another graph +[/graph] + "# + .to_string(), + None, + )); + let parser = LangParser::default(); + let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None); + + validate_document!(doc.content().borrow(), 0, + Graphviz { width == "200px", dot == "Some graph..." }; + Graphviz { dot == "Another graph" }; + ); + } + + #[test] + pub fn lua() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +%% +%% + "# + .to_string(), + None, + )); + let parser = LangParser::default(); + let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None); + + validate_document!(doc.content().borrow(), 0, + Graphviz { width == "200px", dot == "Some graph..." }; + Graphviz { dot == "Another graph" }; + ); + } } diff --git a/src/elements/media.rs b/src/elements/media.rs index f8acbc4..2e632b4 100644 --- a/src/elements/media.rs +++ b/src/elements/media.rs @@ -325,7 +325,7 @@ impl MediaRule { impl RegexRule for MediaRule { fn name(&self) -> &'static str { "Media" } - fn previous(&self) -> Option<&'static str> { Some("Graph") } + fn previous(&self) -> Option<&'static str> { Some("Graphviz") } fn regexes(&self) -> &[regex::Regex] { &self.re } diff --git a/src/elements/reference.rs b/src/elements/reference.rs index 625fc18..e5ec436 100644 --- a/src/elements/reference.rs +++ b/src/elements/reference.rs @@ -307,11 +307,11 @@ impl RegexRule for ReferenceRule { #[cfg(test)] mod tests { use crate::compiler::process::process_from_memory; -use crate::elements::paragraph::Paragraph; -use crate::elements::section::Section; -use crate::parser::langparser::LangParser; + use crate::elements::paragraph::Paragraph; + use crate::elements::section::Section; + use crate::parser::langparser::LangParser; use crate::parser::parser::Parser; -use crate::parser::source::SourceFile; + use crate::parser::source::SourceFile; use crate::validate_document; use super::*; @@ -366,30 +366,36 @@ use crate::parser::source::SourceFile; } #[test] - pub fn test_external() - { - let result = process_from_memory(Target::HTML, vec![ -r#" + pub fn test_external() { + let result = process_from_memory( + Target::HTML, + vec![ + r#" @html.page_title = 0 @compiler.output = a.html #{ref} Referenceable section -"#.into(), -r#" +"# + .into(), + r#" @html.page_title = 1 @compiler.output = b.html §{#ref} §{a#ref} #{ref2} Another Referenceable section -"#.into(), -r#" +"# + .into(), + r#" @html.page_title = 2 §{#ref}[caption=from 0] §{#ref2}[caption=from 1] -"#.into(), - ]).unwrap(); +"# + .into(), + ], + ) + .unwrap(); assert!(result[1].0.borrow().body.starts_with("

#refa#ref

")); assert!(result[2].0.borrow().body.starts_with("

from 0from 1

")); diff --git a/src/elements/tex.rs b/src/elements/tex.rs index f51b716..a7e985a 100644 --- a/src/elements/tex.rs +++ b/src/elements/tex.rs @@ -6,6 +6,7 @@ use std::process::Command; use std::process::Stdio; use std::rc::Rc; use std::str::FromStr; +use std::sync::Arc; use std::sync::Once; use ariadne::Fmt; @@ -14,6 +15,8 @@ use ariadne::Report; use ariadne::ReportKind; use crypto::digest::Digest; use crypto::sha2::Sha512; +use mlua::Function; +use mlua::Lua; use regex::Captures; use regex::Match; use regex::Regex; @@ -25,6 +28,7 @@ use crate::compiler::compiler::Target; use crate::document::document::Document; use crate::document::element::ElemKind; use crate::document::element::Element; +use crate::lua::kernel::CTX; use crate::parser::parser::ParserState; use crate::parser::parser::ReportColors; use crate::parser::rule::RegexRule; @@ -149,13 +153,18 @@ impl Element for Tex { fn element_name(&self) -> &'static str { "LaTeX" } - fn compile(&self, compiler: &Compiler, document: &dyn Document, _cursor: usize) -> Result { + fn compile( + &self, + compiler: &Compiler, + document: &dyn Document, + _cursor: usize, + ) -> Result { match compiler.target() { Target::HTML => { static CACHE_INIT: Once = Once::new(); CACHE_INIT.call_once(|| { - if let Some(mut con) = compiler.cache() { - if let Err(e) = FormattedTex::init(&mut con) { + if let Some(con) = compiler.cache() { + if let Err(e) = FormattedTex::init(con) { eprintln!("Unable to create cache table: {e}"); } } @@ -185,8 +194,8 @@ impl Element for Tex { Tex::format_latex(&fontsize, &preamble, &format!("{prepend}{}", self.tex)) }; - let result = if let Some(mut con) = compiler.cache() { - match latex.cached(&mut con, |s| s.latex_to_svg(&exec, &fontsize)) { + let result = if let Some(con) = compiler.cache() { + match latex.cached(con, |s| s.latex_to_svg(&exec, &fontsize)) { Ok(s) => Ok(s), Err(e) => match e { CachedError::SqlErr(e) => { @@ -427,6 +436,95 @@ impl RegexRule for TexRule { reports } + + fn register_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { + let mut bindings = vec![]; + bindings.push(( + "push_math".to_string(), + lua.create_function( + |_, (env, kind, tex, caption): (Option, String, String, Option)| { + let mut result = Ok(()); + CTX.with_borrow(|ctx| { + ctx.as_ref().map(|ctx| { + let kind = match TexKind::from_str(kind.as_str()) { + Ok(kind) => kind, + Err(err) => { + result = Err(mlua::Error::BadArgument { + to: Some("push".to_string()), + pos: 2, + name: Some("kind".to_string()), + cause: Arc::new(mlua::Error::external(format!( + "Unable to get tex kind: {err}" + ))), + }); + return; + } + }; + + ctx.state.push( + ctx.document, + Box::new(Tex { + location: ctx.location.clone(), + mathmode: true, + kind, + env: env.unwrap_or("main".to_string()), + tex, + caption, + }), + ); + }) + }); + + result + }, + ) + .unwrap(), + )); + + bindings.push(( + "push".to_string(), + lua.create_function( + |_, (env, kind, tex, caption): (Option, String, String, Option)| { + let mut result = Ok(()); + CTX.with_borrow(|ctx| { + ctx.as_ref().map(|ctx| { + let kind = match TexKind::from_str(kind.as_str()) { + Ok(kind) => kind, + Err(err) => { + result = Err(mlua::Error::BadArgument { + to: Some("push".to_string()), + pos: 2, + name: Some("kind".to_string()), + cause: Arc::new(mlua::Error::external(format!( + "Unable to get tex kind: {err}" + ))), + }); + return; + } + }; + + ctx.state.push( + ctx.document, + Box::new(Tex { + location: ctx.location.clone(), + mathmode: false, + kind, + env: env.unwrap_or("main".to_string()), + tex, + caption, + }), + ); + }) + }); + + result + }, + ) + .unwrap(), + )); + + bindings + } } #[cfg(test)] @@ -447,6 +545,9 @@ mod tests { $[kind=block, caption=Some\, text\\] 1+1=2 $ $|[env=another] Non Math \LaTeX |$ $[kind=block,env=another] e^{i\pi}=-1$ +%% +%% +%% "# .to_string(), None, @@ -458,6 +559,9 @@ $[kind=block,env=another] e^{i\pi}=-1$ Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another" }; Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" }; + Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; + Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another" }; + Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" }; ); } @@ -469,6 +573,9 @@ $[kind=block,env=another] e^{i\pi}=-1$ $[ caption=Some\, text\\] 1+1=2 $ $|[env=another, kind=inline , caption = Enclosed \]. ] Non Math \LaTeX|$ $[env=another] e^{i\pi}=-1$ +%% +%% +%% "# .to_string(), None, @@ -479,7 +586,10 @@ $[env=another] e^{i\pi}=-1$ validate_document!(doc.content().borrow(), 0, Paragraph { Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; - Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another" }; + Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another", caption == Some("Enclosed ].".to_string()) }; + Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" }; + Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; + Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another", caption == Some("Enclosed ].".to_string()) }; Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" }; }; ); diff --git a/src/parser/rule.rs b/src/parser/rule.rs index 7e251be..b7229dd 100644 --- a/src/parser/rule.rs +++ b/src/parser/rule.rs @@ -205,7 +205,7 @@ mod tests { "Blockquote", "Code", "Tex", - "Graph", + "Graphviz", "Media", "Layout", "Style",