add: plugins
This commit is contained in:
parent
149025f9e4
commit
d71dc7e7ca
7 changed files with 161 additions and 47 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4,4 +4,4 @@ version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bberry"
|
name = "bberry"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bberry"
|
name = "bberry"
|
||||||
description = "lisp-like dsl which \"compiles\" into html"
|
description = "lisp-like dsl which \"compiles\" into html"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["trisuaso"]
|
authors = ["trisuaso"]
|
||||||
repository = "https://trisua.com/t/bberry.git"
|
repository = "https://trisua.com/t/bberry.git"
|
||||||
|
|
4
examples/plugins.lisp
Normal file
4
examples/plugins.lisp
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
(h1 (say_hi)) ; plugins with no parameters
|
||||||
|
(say_hi_with_params (text "jeff")) ; plugins with parameters
|
||||||
|
(pre (say_hi_multiple (text "abcd") (text "xyz"))) ; mutliple parameters in plugins
|
||||||
|
(pre (code (read_file (text ".gitignore")))) ; slightly more advanced plugins
|
|
@ -1,8 +1,14 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub trait Render {
|
pub trait Render<'a> {
|
||||||
/// Render into HTML.
|
/// Render into HTML.
|
||||||
fn render(&self) -> String;
|
fn render(
|
||||||
|
self,
|
||||||
|
plugins: &mut HashMap<String, Box<dyn FnMut(Element) -> Element + 'a>>,
|
||||||
|
) -> String;
|
||||||
|
|
||||||
|
/// Render into HTML with no plugins.
|
||||||
|
fn render_safe(self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SELF_CLOSING_TAGS: &[&str] = &["img", "br", "hr", "input", "meta", "link"];
|
pub const SELF_CLOSING_TAGS: &[&str] = &["img", "br", "hr", "input", "meta", "link"];
|
||||||
|
@ -16,8 +22,11 @@ pub struct Element {
|
||||||
pub children: Vec<Element>,
|
pub children: Vec<Element>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Element {
|
impl<'a> Render<'a> for Element {
|
||||||
fn render(&self) -> String {
|
fn render(
|
||||||
|
self,
|
||||||
|
plugins: &mut HashMap<String, Box<dyn FnMut(Element) -> Element + 'a>>,
|
||||||
|
) -> String {
|
||||||
if self.tag == "text" {
|
if self.tag == "text" {
|
||||||
return self.attrs.get("content").unwrap().to_owned();
|
return self.attrs.get("content").unwrap().to_owned();
|
||||||
} else if self.tag == "v" {
|
} else if self.tag == "v" {
|
||||||
|
@ -27,10 +36,12 @@ impl Render for Element {
|
||||||
let mut inner: String = String::new();
|
let mut inner: String = String::new();
|
||||||
|
|
||||||
for element in &self.children {
|
for element in &self.children {
|
||||||
inner.push_str(&element.render());
|
inner.push_str(&element.clone().render(plugins));
|
||||||
}
|
}
|
||||||
|
|
||||||
return inner;
|
return inner;
|
||||||
|
} else if let Some(f) = plugins.get_mut(&self.tag) {
|
||||||
|
return f(self).render(plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
let closing = format!("</{}>", self.tag);
|
let closing = format!("</{}>", self.tag);
|
||||||
|
@ -59,7 +70,7 @@ impl Render for Element {
|
||||||
let mut inner: String = String::new();
|
let mut inner: String = String::new();
|
||||||
|
|
||||||
for element in &self.children {
|
for element in &self.children {
|
||||||
inner.push_str(&element.render());
|
inner.push_str(&element.clone().render(plugins));
|
||||||
}
|
}
|
||||||
|
|
||||||
inner
|
inner
|
||||||
|
@ -71,4 +82,15 @@ impl Render for Element {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_safe(self) -> String {
|
||||||
|
self.render(&mut {
|
||||||
|
let mut h = HashMap::new();
|
||||||
|
h.insert(
|
||||||
|
"#null".to_string(),
|
||||||
|
Box::new(|x: Element| x.to_owned()) as _,
|
||||||
|
);
|
||||||
|
h
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,34 +101,7 @@ pub fn expr_parser(buf: &str) -> Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
// special elements
|
// special elements
|
||||||
if element.tag == "#" {
|
if (element.tag == "attr") | (element.tag == ":") | (element.tag.is_empty()) {
|
||||||
// parse as tuple
|
|
||||||
// tuples can only contain strings
|
|
||||||
let len = buf.matches("s\"").collect::<Vec<&str>>().len();
|
|
||||||
let mut values: Vec<String> = Vec::new();
|
|
||||||
let mut last_len: usize = 0;
|
|
||||||
|
|
||||||
for _ in 0..len {
|
|
||||||
let mut buffer: String = String::new();
|
|
||||||
|
|
||||||
string_parser(
|
|
||||||
buf.replace("# ", "")
|
|
||||||
.replace("s\"", "\"")
|
|
||||||
.chars()
|
|
||||||
.skip(last_len),
|
|
||||||
&mut buffer,
|
|
||||||
);
|
|
||||||
last_len = buffer.len() + 2;
|
|
||||||
|
|
||||||
values.push(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, v) in values.iter().enumerate() {
|
|
||||||
element.attrs.insert(i.to_string(), v.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
} else if (element.tag == "attr") | (element.tag == ":") | (element.tag.is_empty()) {
|
|
||||||
let mut chars = (&buf[i..buf.len()]).to_string();
|
let mut chars = (&buf[i..buf.len()]).to_string();
|
||||||
|
|
||||||
if element.tag.is_empty() {
|
if element.tag.is_empty() {
|
||||||
|
@ -264,6 +237,6 @@ pub fn element_parser(value: &str) -> (Element, usize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a full document.
|
/// Parse a full document.
|
||||||
pub fn document(value: &str) -> (Element, usize) {
|
pub fn document(value: &str) -> Element {
|
||||||
element_parser(&format!("(null? [{value}])"))
|
element_parser(&format!("(null? [{value}])")).0
|
||||||
}
|
}
|
||||||
|
|
82
src/lib.rs
82
src/lib.rs
|
@ -1,17 +1,24 @@
|
||||||
pub mod core;
|
pub mod core;
|
||||||
|
pub mod macros;
|
||||||
pub use core::parser::document as parse;
|
pub use core::parser::document as parse;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{core::element::Render, parse};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
core::element::{Element, Render},
|
||||||
|
parse, read_param, text,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_escape() {
|
fn string_escape() {
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
"string_escape.html",
|
"string_escape.html",
|
||||||
parse(&std::fs::read_to_string("examples/string_escape.lisp").unwrap())
|
parse(&std::fs::read_to_string("examples/string_escape.lisp").unwrap()).render_safe(),
|
||||||
.0
|
|
||||||
.render(),
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -22,13 +29,70 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn boilerplate() {
|
fn plugins() {
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
"boilerplate.html",
|
"plugins.html",
|
||||||
parse(&std::fs::read_to_string("examples/boilerplate.lisp").unwrap())
|
parse(&std::fs::read_to_string("examples/plugins.lisp").unwrap()).render(&mut {
|
||||||
.0
|
let mut h = HashMap::new();
|
||||||
.render(),
|
|
||||||
|
h.insert(
|
||||||
|
"say_hi".to_string(),
|
||||||
|
Box::new(|_| text!("Hello, world!")) as _,
|
||||||
|
);
|
||||||
|
|
||||||
|
h.insert(
|
||||||
|
"say_hi_with_params".to_string(),
|
||||||
|
Box::new(|e: Element| text!(read_param!(e, 0))) as _,
|
||||||
|
);
|
||||||
|
|
||||||
|
h.insert(
|
||||||
|
"say_hi_multiple".to_string(),
|
||||||
|
Box::new(|e: Element| {
|
||||||
|
let mut content: String = String::new();
|
||||||
|
|
||||||
|
for child in e.children {
|
||||||
|
let text = child.attrs.get("content").unwrap();
|
||||||
|
content += &format!("{text}, ");
|
||||||
|
}
|
||||||
|
|
||||||
|
text!(content)
|
||||||
|
}) as _,
|
||||||
|
);
|
||||||
|
|
||||||
|
h.insert(
|
||||||
|
"read_file".to_string(),
|
||||||
|
Box::new(|e: Element| {
|
||||||
|
text!(std::fs::read_to_string(read_param!(e, 0)).unwrap())
|
||||||
|
}) as _,
|
||||||
|
);
|
||||||
|
|
||||||
|
h
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boilerplate() {
|
||||||
|
std::fs::write(
|
||||||
|
"boilerplate.html",
|
||||||
|
parse(&std::fs::read_to_string("examples/boilerplate.lisp").unwrap()).render_safe(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boilerplate_speed() {
|
||||||
|
let start = SystemTime::now();
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
"boilerplate.html",
|
||||||
|
parse(&std::fs::read_to_string("examples/boilerplate.lisp").unwrap()).render_safe(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let duration = start.elapsed().unwrap();
|
||||||
|
println!("took: {}μs", duration.as_micros());
|
||||||
|
assert!(duration < Duration::from_micros(500))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
51
src/macros.rs
Normal file
51
src/macros.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! text {
|
||||||
|
($t:literal) => {
|
||||||
|
$crate::core::element::Element {
|
||||||
|
tag: "text".to_string(),
|
||||||
|
children: Vec::new(),
|
||||||
|
attrs: {
|
||||||
|
let mut a = HashMap::new();
|
||||||
|
a.insert("content".to_string(), $t.to_string());
|
||||||
|
a
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($t:ident) => {
|
||||||
|
$crate::core::element::Element {
|
||||||
|
tag: "text".to_string(),
|
||||||
|
children: Vec::new(),
|
||||||
|
attrs: {
|
||||||
|
let mut a = HashMap::new();
|
||||||
|
a.insert("content".to_string(), $t.to_string());
|
||||||
|
a
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($t:expr) => {
|
||||||
|
$crate::core::element::Element {
|
||||||
|
tag: "text".to_string(),
|
||||||
|
children: Vec::new(),
|
||||||
|
attrs: {
|
||||||
|
let mut a = HashMap::new();
|
||||||
|
a.insert("content".to_string(), ($t).to_string());
|
||||||
|
a
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! read_param {
|
||||||
|
($element:ident, $id:literal) => {
|
||||||
|
$element
|
||||||
|
.children
|
||||||
|
.get($id)
|
||||||
|
.unwrap()
|
||||||
|
.attrs
|
||||||
|
.get("content")
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue