Added Basic HTTP stdlib

This commit is contained in:
gyoder 2025-07-11 12:19:26 -06:00
parent c8ffbb2af0
commit 4144fa2cba
6 changed files with 1370 additions and 25 deletions

1203
Cargo.lock generated

File diff suppressed because it is too large Load diff

10
cspell.json Normal file
View file

@ -0,0 +1,10 @@
{
"words": [
"htmlua",
"headermap",
"reqwest"
],
"userWords": [
"mlua"
]
}

View file

@ -8,8 +8,11 @@ anyhow = "1.0.98"
html5ever = "0.35.0"
kuchikiki = "0.8.2"
markup5ever = "0.11.0"
mlua = { version = "0.10.5", features = ["lua54", "vendored", "serialize"] }
mlua = { version = "0.10.5", features = ["lua54", "vendored", "serialize", "anyhow"] }
pulldown-cmark = "0.13.0"
reqwest = { version = "0.12.22", features = ["blocking", "json"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
syntect = "5.2.0"
tendril = "0.4.3"

View file

@ -0,0 +1,151 @@
use std::{cell::{LazyCell, RefCell}, collections::HashMap, fmt::Write, rc::Rc};
use anyhow::anyhow;
use mlua::{prelude::*, Error, Lua, Table};
use reqwest::{blocking::{Client, Response}, header::HeaderMap};
use serde::{Deserialize, Serialize};
use serde_json::to_value;
pub fn create_htmlua_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)
})?,
)?;
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)
})?,
)?;
t.set("http", create_http_lib(l)?)?;
Ok(t)
}
fn create_http_lib(l: &Lua) -> mlua::Result<Table> {
let t = l.create_table()?;
let http_client: Rc<LazyCell<Client>> = Rc::new(LazyCell::new(|| Client::builder()
.timeout(std::time::Duration::from_secs(3))
.user_agent("htmlua/0.1.0")
.build().unwrap()
));
let client = http_client.clone();
t.set(
"get",
l.create_function(move |_, url: String| {
let res = client.get(url).send().map_err(|e| Error::RuntimeError(e.to_string()))?;
let lua_res: LuaHttpResponse = TryFrom::try_from(res)?;
Ok(lua_res)
})?
)?;
let client = http_client.clone();
t.set(
"post",
l.create_function(move |_, url: String| {
let res = client.post(url).send().map_err(|e| Error::RuntimeError(e.to_string()))?;
let lua_res: LuaHttpResponse = TryFrom::try_from(res)?;
Ok(lua_res)
})?
)?;
let client = http_client.clone();
t.set(
"get_with_data",
l.create_function(move |_, (url, data): (String, HashMap<String, String>)| {
let res = client.get(url).query(&data).send().map_err(|e| Error::RuntimeError(e.to_string()))?;
let lua_res: LuaHttpResponse = TryFrom::try_from(res)?;
Ok(lua_res)
})?
)?;
let client = http_client.clone();
t.set(
"post_with_data_form",
l.create_function(move |_, (url, data): (String, HashMap<String, String>)| {
let res = client.post(url).form(&data).send().map_err(|e| Error::RuntimeError(e.to_string()))?;
let lua_res: LuaHttpResponse = TryFrom::try_from(res)?;
Ok(lua_res)
})?
)?;
let client = http_client.clone();
t.set(
"post_with_data_json",
l.create_function(move |_, (url, data): (String, HashMap<String, String>)| {
let res = client.post(url).json(&data).send().map_err(|e| Error::RuntimeError(e.to_string()))?;
let lua_res: LuaHttpResponse = TryFrom::try_from(res)?;
Ok(lua_res)
})?
)?;
//TODO: all http
t.set(
"decode_json",
l.create_function(move |l, text: String| {
let table: serde_json::Value = serde_json::from_str(&text).map_err(|e| Error::RuntimeError(e.to_string()))?;
Ok(l.to_value(&table))
})?
)?;
Ok(t)
}
#[derive(Serialize, Deserialize)]
struct LuaHttpResponse {
headers: HashMap<String, String>,
body: String,
status: u16,
}
impl TryFrom<Response> for LuaHttpResponse {
fn try_from(value: Response) -> Result<Self, Self::Error> {
Ok(LuaHttpResponse { headers: headermap_to_hashmap(value.headers()), status: value.status().as_u16(), body: value.text().map_err(|e| Error::RuntimeError(e.to_string()))?, })
}
type Error = Error;
}
fn headermap_to_hashmap(headers: &HeaderMap) -> HashMap<String, String> {
let mut map = HashMap::new();
for (name, value) in headers {
if let Ok(value_str) = value.to_str() {
map.insert(name.as_str().to_string(), value_str.to_string());
}
}
map
}
impl IntoLua for LuaHttpResponse {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
let table = lua.create_table()?;
table.set("status", self.status)?;
table.set("body", self.body)?;
let headers_table = lua.create_table()?;
for (key, value) in self.headers {
headers_table.set(key, value)?;
}
table.set("headers", headers_table)?;
Ok(LuaValue::Table(table))
}
}

View file

@ -1,3 +1,4 @@
pub mod render;
pub mod serve;
pub mod helpers;
pub mod htmlua_stdlib;

View file

@ -12,32 +12,9 @@ use syntect::{
util::LinesWithEndings,
};
use crate::helpers::read_doc_from_file;
use crate::{helpers::read_doc_from_file, htmlua_stdlib::create_htmlua_stdlib};
fn create_htmlua_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)
})?,
)?;
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(t)
}
pub fn execute_lua(document: NodeRef) -> Result<NodeRef> {
let lua = Lua::new();