mod full_text;
mod highlight;
mod html;
mod js;
mod merge;
mod sanitize;
mod select;
mod simplify_html;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::{feed::Feed, util::Result};
#[async_trait::async_trait]
pub trait FeedFilter {
async fn run(&self, feed: &mut Feed) -> Result<()>;
}
#[async_trait::async_trait]
pub trait FeedFilterConfig {
type Filter: FeedFilter;
async fn build(self) -> Result<Self::Filter>;
}
#[derive(Clone)]
pub struct BoxedFilter(Arc<dyn FeedFilter + Send + Sync>);
#[async_trait::async_trait]
impl FeedFilter for BoxedFilter {
async fn run(&self, feed: &mut Feed) -> Result<()> {
self.0.run(feed).await
}
}
impl BoxedFilter {
fn from<T>(filter: T) -> Self
where
T: FeedFilter + Send + Sync + 'static,
{
Self(Arc::new(filter))
}
}
pub struct Filters {
filters: Vec<BoxedFilter>,
}
impl Filters {
pub async fn from_config(filter_configs: Vec<FilterConfig>) -> Result<Self> {
let mut filters = Vec::new();
for filter_config in filter_configs {
let filter = filter_config.build().await?;
filters.push(filter);
}
Ok(Self { filters })
}
pub async fn process(&self, feed: &mut Feed) -> Result<()> {
self.process_partial(feed, self.filters.len()).await
}
pub async fn process_partial(
&self,
feed: &mut Feed,
limit_filters: usize,
) -> Result<()> {
for filter in self.filters.iter().take(limit_filters) {
filter.run(feed).await?;
}
Ok(())
}
}
macro_rules! define_filters {
($($variant:ident => $config:ty);* ;) => {
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum FilterConfig {
$(
$variant($config),
)*
}
impl FilterConfig {
#[cfg(test)]
pub fn parse_yaml(input: &str) -> Result<Box<dyn std::any::Any>> {
#[derive(Deserialize)]
struct Dummy {
#[serde(flatten)]
config: FilterConfig
}
use crate::util::ConfigError;
let config: Dummy = serde_yaml::from_str(input).map_err(ConfigError::from)?;
match config.config {
$(FilterConfig::$variant(config) => {
Ok(Box::new(config))
})*
}
}
pub async fn build(self) -> Result<BoxedFilter> {
match self {
$(FilterConfig::$variant(config) => {
let filter = config.build().await?;
Ok(BoxedFilter::from(filter))
})*
}
}
}
}
}
define_filters!(
Js => js::JsConfig;
FullText => full_text::FullTextConfig;
SimplifyHtml => simplify_html::SimplifyHtmlConfig;
RemoveElement => html::RemoveElementConfig;
KeepElement => html::KeepElementConfig;
Split => html::SplitConfig;
Sanitize => sanitize::SanitizeConfig;
KeepOnly => select::KeepOnlyConfig;
Discard => select::DiscardConfig;
Highlight => highlight::HighlightConfig;
Merge => merge::MergeConfig;
);