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]]
|
||||
name = "bberry"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "bberry"
|
||||
description = "lisp-like dsl which \"compiles\" into html"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
authors = ["trisuaso"]
|
||||
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;
|
||||
|
||||
pub trait Render {
|
||||
pub trait Render<'a> {
|
||||
/// 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"];
|
||||
|
@ -16,8 +22,11 @@ pub struct Element {
|
|||
pub children: Vec<Element>,
|
||||
}
|
||||
|
||||
impl Render for Element {
|
||||
fn render(&self) -> String {
|
||||
impl<'a> Render<'a> for Element {
|
||||
fn render(
|
||||
self,
|
||||
plugins: &mut HashMap<String, Box<dyn FnMut(Element) -> Element + 'a>>,
|
||||
) -> String {
|
||||
if self.tag == "text" {
|
||||
return self.attrs.get("content").unwrap().to_owned();
|
||||
} else if self.tag == "v" {
|
||||
|
@ -27,10 +36,12 @@ impl Render for Element {
|
|||
let mut inner: String = String::new();
|
||||
|
||||
for element in &self.children {
|
||||
inner.push_str(&element.render());
|
||||
inner.push_str(&element.clone().render(plugins));
|
||||
}
|
||||
|
||||
return inner;
|
||||
} else if let Some(f) = plugins.get_mut(&self.tag) {
|
||||
return f(self).render(plugins);
|
||||
}
|
||||
|
||||
let closing = format!("</{}>", self.tag);
|
||||
|
@ -59,7 +70,7 @@ impl Render for Element {
|
|||
let mut inner: String = String::new();
|
||||
|
||||
for element in &self.children {
|
||||
inner.push_str(&element.render());
|
||||
inner.push_str(&element.clone().render(plugins));
|
||||
}
|
||||
|
||||
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
|
||||
if element.tag == "#" {
|
||||
// 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()) {
|
||||
if (element.tag == "attr") | (element.tag == ":") | (element.tag.is_empty()) {
|
||||
let mut chars = (&buf[i..buf.len()]).to_string();
|
||||
|
||||
if element.tag.is_empty() {
|
||||
|
@ -264,6 +237,6 @@ pub fn element_parser(value: &str) -> (Element, usize) {
|
|||
}
|
||||
|
||||
/// Parse a full document.
|
||||
pub fn document(value: &str) -> (Element, usize) {
|
||||
element_parser(&format!("(null? [{value}])"))
|
||||
pub fn document(value: &str) -> Element {
|
||||
element_parser(&format!("(null? [{value}])")).0
|
||||
}
|
||||
|
|
82
src/lib.rs
82
src/lib.rs
|
@ -1,17 +1,24 @@
|
|||
pub mod core;
|
||||
pub mod macros;
|
||||
pub use core::parser::document as parse;
|
||||
|
||||
#[cfg(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]
|
||||
fn string_escape() {
|
||||
std::fs::write(
|
||||
"string_escape.html",
|
||||
parse(&std::fs::read_to_string("examples/string_escape.lisp").unwrap())
|
||||
.0
|
||||
.render(),
|
||||
parse(&std::fs::read_to_string("examples/string_escape.lisp").unwrap()).render_safe(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -22,13 +29,70 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn boilerplate() {
|
||||
fn plugins() {
|
||||
std::fs::write(
|
||||
"boilerplate.html",
|
||||
parse(&std::fs::read_to_string("examples/boilerplate.lisp").unwrap())
|
||||
.0
|
||||
.render(),
|
||||
"plugins.html",
|
||||
parse(&std::fs::read_to_string("examples/plugins.lisp").unwrap()).render(&mut {
|
||||
let mut h = HashMap::new();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#[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