Added bindings & tests

This commit is contained in:
ef3d0c3e 2024-08-14 11:09:42 +02:00
parent 35408f03b1
commit b3b99c4069
10 changed files with 321 additions and 52 deletions

4
src/cache/cache.rs vendored
View file

@ -23,7 +23,7 @@ pub trait Cached {
fn key(&self) -> <Self as Cached>::Key;
fn init(con: &mut Connection) -> Result<(), rusqlite::Error> {
fn init(con: &Connection) -> Result<(), rusqlite::Error> {
con.execute(<Self as Cached>::sql_table(), ()).map(|_| ())
}
@ -38,7 +38,7 @@ pub trait Cached {
/// Note that on error, [`f`] may still have been called
fn cached<E, F>(
&self,
con: &mut Connection,
con: &Connection,
f: F,
) -> Result<<Self as Cached>::Value, CachedError<E>>
where

View file

@ -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<RefCell<Connection>>,
cache: Option<&'a Connection>,
reference_count: RefCell<HashMap<String, HashMap<String, usize>>>,
sections_counter: RefCell<Vec<usize>>,
unresolved_references: RefCell<Vec<(usize, CrossReference)>>,
}
impl Compiler {
pub fn new(target: Target, db_path: Option<String>) -> 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<RefMut<'_, Connection>> {
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 {

View file

@ -221,6 +221,8 @@ mod tests {
use rand::rngs::OsRng;
use rand::RngCore;
use crate::compiler::process::process_from_memory;
use super::*;
#[test]
@ -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,
),
]);
}
}

View file

@ -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();

View file

@ -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) => {

View file

@ -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<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();
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#"
%<nml.graphviz.push("neato", "200px", "Some graph...")>%
%<nml.graphviz.push("dot", "", "Another 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" };
);
}
}

View file

@ -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 }

View file

@ -366,15 +366,17 @@ use crate::parser::source::SourceFile;
}
#[test]
pub fn test_external()
{
let result = process_from_memory(Target::HTML, vec![
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(),
"#
.into(),
r#"
@html.page_title = 1
@compiler.output = b.html
@ -382,14 +384,18 @@ r#"
§{#ref}
§{a#ref}
#{ref2} Another Referenceable section
"#.into(),
"#
.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("<div class=\"content\"><p><a href=\"a.html#Referenceable_section\">#ref</a><a href=\"a.html#Referenceable_section\">a#ref</a></p>"));
assert!(result[2].0.borrow().body.starts_with("<div class=\"content\"><p><a href=\"a.html#Referenceable_section\">from 0</a><a href=\"b.html#Another_Referenceable_section\">from 1</a></p>"));

View file

@ -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<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();
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, String, Option<String>)| {
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, String, Option<String>)| {
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$
%<nml.tex.push_math(nil, "block", "1+1=2", "Some, text\\")>%
%<nml.tex.push("another", "block", "Non Math \\LaTeX", nil)>%
%<nml.tex.push_math("another", "block", "e^{i\\pi}=-1", nil)>%
"#
.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$
%<nml.tex.push_math("main", "inline", "1+1=2", "Some, text\\")>%
%<nml.tex.push("another", "inline", "Non Math \\LaTeX", "Enclosed ].")>%
%<nml.tex.push_math("another", "inline", "e^{i\\pi}=-1", nil)>%
"#
.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" };
};
);

View file

@ -205,7 +205,7 @@ mod tests {
"Blockquote",
"Code",
"Tex",
"Graph",
"Graphviz",
"Media",
"Layout",
"Style",