aboutsummaryrefslogtreecommitdiff
path: root/htmlua-parser
diff options
context:
space:
mode:
Diffstat (limited to 'htmlua-parser')
-rw-r--r--htmlua-parser/Cargo.toml10
-rw-r--r--htmlua-parser/src/lib.rs109
2 files changed, 119 insertions, 0 deletions
diff --git a/htmlua-parser/Cargo.toml b/htmlua-parser/Cargo.toml
new file mode 100644
index 0000000..8a18618
--- /dev/null
+++ b/htmlua-parser/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "htmlua-parser"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+anyhow = "1.0.98"
+kuchikiki = "0.8.2"
+mlua = { version = "0.10.5", features = ["lua54", "vendored", "serialize"] }
+tendril = "0.4.3"
diff --git a/htmlua-parser/src/lib.rs b/htmlua-parser/src/lib.rs
new file mode 100644
index 0000000..80a21dd
--- /dev/null
+++ b/htmlua-parser/src/lib.rs
@@ -0,0 +1,109 @@
+#![warn(clippy::pedantic)]
+
+use std::cell::RefCell;
+use std::fmt::Write;
+use std::rc::Rc;
+
+use anyhow::Result;
+use anyhow::anyhow;
+use kuchikiki::{
+ NodeRef,
+ traits::TendrilSink,
+};
+use mlua::{Lua, Table};
+use tendril::fmt;
+use tendril::{Atomicity, Tendril};
+
+fn create_luahtml_stdlib(l: &Lua, stdout: &Rc<RefCell<String>>) -> mlua::Result<Table> {
+ let t = l.create_table()?;
+
+ // This cannot be the best way to do this
+
+ let stdout_println = stdout.clone();
+ t.set(
+ "println",
+ l.create_function(move |_, text: String| {
+ let mut stdout_ref = stdout_println.borrow_mut();
+ writeln!(stdout_ref, "{text}").map_err(mlua::Error::external)?;
+ Ok(())
+ })?,
+ )?;
+
+ let stdout_print = stdout.clone();
+ t.set(
+ "print",
+ l.create_function(move |_, text: String| {
+ let mut stdout_ref = stdout_print.borrow_mut();
+ write!(stdout_ref, "{text}").map_err(mlua::Error::external)?;
+ Ok(())
+ })?,
+ )?;
+ Ok(t)
+}
+
+pub fn parse<F: fmt::Format, A: Atomicity, T: Into<Tendril<F, A>>>(to_parse: T) -> Result<NodeRef>
+where
+ tendril::Tendril<tendril::fmt::UTF8>: std::convert::From<tendril::Tendril<F, A>>,
+{
+ let document = kuchikiki::parse_html().one(to_parse.into());
+ let lua = Lua::new();
+ let globals = lua.globals();
+
+ let stdout: Rc<RefCell<String>> = Rc::new(RefCell::new(String::new()));
+
+ let htmlua_table = create_luahtml_stdlib(&lua, &stdout)
+ .map_err(|e| anyhow::anyhow!("Failed to create Lua stdlib: {}", e))?;
+
+ globals
+ .set("htmlua", htmlua_table)
+ .map_err(|e| anyhow::anyhow!("Failed to set global: {}", e))?;
+
+ let lua_elements: Vec<_> = match document.select("lua") {
+ Ok(e) => e.collect(),
+ Err(()) => return Err(anyhow!("Unable to find Lua")),
+ };
+
+ for node in lua_elements {
+ if let Some(text_node) = node.as_node().first_child() {
+ if let Some(lua_code) = text_node.as_text() {
+ stdout.borrow_mut().clear();
+ lua.load(lua_code.borrow().as_str())
+ .exec()
+ .map_err(|e| anyhow::anyhow!("Failed to execute Lua: {}", e))?;
+ node.as_node()
+ .insert_before(NodeRef::new_text(stdout.borrow().as_str()));
+ node.as_node().detach();
+ }
+ }
+ }
+
+ Ok(document)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn base() -> Result<()> {
+ let page = r#"
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>Basic HTML Page</title>
+ </head>
+ <body>
+ <h1>Hello World</h1>
+ <p>This is a paragraph.</p>
+ <span><lua>
+ htmlua.println("Test from Lua!")
+ </lua></span>
+ </body>
+ </html>"#;
+ let d = parse(page)?;
+ let text = d.select_first("span").unwrap().as_node().text_contents();
+ assert_eq!(text, "Test from Lua!\n");
+ assert!(d.select_first("lua").is_err());
+ Ok(())
+ }
+}