use crate::dom::{LuaNode, parse_html};
use crate::rss::FeedItem;
use mlua::{Lua, Table};
#[derive(Debug)]
pub enum RunError {
Lua(mlua::Error),
MissingField(&'static str),
}
impl std::fmt::Display for RunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RunError::Lua(e) => write!(f, "method script failed: {e}"),
RunError::MissingField(field) => write!(
f,
"an item returned by the method script is missing required field `{field}`"
),
}
}
}
impl std::error::Error for RunError {}
/// Runs a method script against fetched HTML. A fresh `Lua` VM and DOM tree
/// are created per call: `mlua::Lua` and `rcdom::Handle` are not `Send`/`Sync`,
/// and the scheduler dispatches one fetch per worker thread, so nothing here
/// is meant to outlive the thread that calls this function.
///
/// The script is called with `(root, url)`: the parsed DOM and the site's
/// own fetch URL, the latter needed to resolve relative links a page emits
/// (`href="/foo"`) into absolute ones. A script that doesn't need it can
/// still write `local root = ...` — Lua drops the extra value silently. A
/// `fetch(url) -> {status=, body=}` global is also available, for a script
/// that needs a second page (e.g. an article's own page for its full body),
/// along with `parse_html(html) -> node` to walk whatever it fetches.
pub fn run_method(source: &str, url: &str, html: &str) -> Result, RunError> {
let lua = Lua::new();
crate::http_fetch::register(&lua).map_err(RunError::Lua)?;
crate::dom::register(&lua).map_err(RunError::Lua)?;
let dom = parse_html(html);
let root = LuaNode::root(&dom);
let items: Table = lua.load(source).call((root, url)).map_err(RunError::Lua)?;
let mut out = Vec::new();
for entry in items.sequence_values::
() {
let item = entry.map_err(RunError::Lua)?;
let title = item
.get::