scrapelect 0.3.2

Interpreter for scrapelect, a CSS-inspired web scraping DSL
Documentation
use std::collections::BTreeMap;

use super::{
    value::{EValue, PValue, Pipeline, TryFromValue},
    ElementContext,
};

pub mod builtin;

pub use scrapelect_filter_proc_macro::{filter_fn, Args};

pub trait Args<'doc>: Sized {
    fn try_deserialize<'ast>(args: BTreeMap<&'ast str, EValue<'doc>>) -> anyhow::Result<Self>;
}

impl<'a> Args<'a> for () {
    fn try_deserialize<'ast>(args: BTreeMap<&'ast str, EValue<'a>>) -> anyhow::Result<Self> {
        if !args.is_empty() {
            anyhow::bail!("Found unexpected arguments `{args:?}`");
        }

        Ok(())
    }
}

pub trait Filter {
    type Value<'doc>: TryFromValue<Pipeline<'doc>>;
    type Args<'doc>: Args<'doc>;

    fn apply<'doc>(
        value: Self::Value<'doc>,
        args: Self::Args<'doc>,
        ctx: &mut ElementContext<'_, 'doc>,
    ) -> anyhow::Result<PValue<'doc>>;
}

pub trait FilterDyn {
    fn apply<'ast, 'doc>(
        &self,
        value: PValue<'doc>,
        args: BTreeMap<&'ast str, EValue<'doc>>,
        ctx: &mut ElementContext<'ast, 'doc>,
    ) -> anyhow::Result<PValue<'doc>>;
}

impl<F: Filter> FilterDyn for F {
    #[inline]
    fn apply<'ast, 'doc>(
        &self,
        value: PValue<'doc>,
        args: BTreeMap<&'ast str, EValue<'doc>>,
        ctx: &mut ElementContext<'ast, 'doc>,
    ) -> anyhow::Result<PValue<'doc>> {
        F::apply(value.try_unwrap()?, F::Args::try_deserialize(args)?, ctx)
    }
}

#[cfg(not(feature = "filter_doc"))]
pub fn dispatch_filter<'ast, 'doc>(
    name: &str,
    value: PValue<'doc>,
    args: BTreeMap<&'ast str, EValue<'doc>>,
    ctx: &mut ElementContext<'ast, 'doc>,
) -> anyhow::Result<PValue<'doc>> {
    match builtin::FILTERS.get(name) {
        Some(filter) => filter.apply(value, args, ctx),
        None => anyhow::bail!("unrecognized filter `{name}`"),
    }
}