diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/filters.rs | 361 | ||||
| -rw-r--r-- | src/filters/log.rs | 95 | ||||
| -rw-r--r-- | src/filters/mod.rs | 213 | ||||
| -rw-r--r-- | src/filters/player.rs | 109 | ||||
| -rw-r--r-- | src/main.rs | 28 | 
5 files changed, 433 insertions, 373 deletions
| diff --git a/src/filters.rs b/src/filters.rs deleted file mode 100644 index 7da19ec..0000000 --- a/src/filters.rs +++ /dev/null @@ -1,361 +0,0 @@ -#![allow(clippy::new_ret_no_self)] -use std::collections::HashSet; -use std::ops; - -use evtclib::raw::parser::PartialEvtc; -use evtclib::statistics::gamedata::Boss; -use evtclib::{Agent, AgentName}; - -use num_derive::FromPrimitive; -use num_traits::FromPrimitive as _; - -use regex::Regex; - -use super::{guilds, FightOutcome, LogResult, SearchField, Weekday}; - -use chrono::{Datelike, NaiveDateTime}; - -/// Early filtering result. -/// -/// This implements a [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic), -/// similar to SQL's `NULL`. -#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] -#[repr(i8)] -pub enum Inclusion { -    /// The log should be included. -    Include = 1, -    /// The state is yet unknown. -    Unknown = 0, -    /// The log should be excluded. -    Exclude = -1, -} - -impl ops::Not for Inclusion { -    type Output = Self; - -    fn not(self) -> Self::Output { -        Inclusion::from_i8(-(self as i8)).unwrap() -    } -} - -impl ops::BitAnd<Inclusion> for Inclusion { -    type Output = Self; - -    fn bitand(self, rhs: Inclusion) -> Self::Output { -        Inclusion::from_i8((self as i8).min(rhs as i8)).unwrap() -    } -} - -impl ops::BitOr<Inclusion> for Inclusion { -    type Output = Self; - -    fn bitor(self, rhs: Inclusion) -> Self::Output { -        Inclusion::from_i8((self as i8).max(rhs as i8)).unwrap() -    } -} - -impl From<bool> for Inclusion { -    fn from(data: bool) -> Self { -        if data { -            Inclusion::Include -        } else { -            Inclusion::Exclude -        } -    } -} - -/// The main filter trait. -/// -/// Filters are usually handled as a `Box<dyn Filter>`. -pub trait Filter: Send + Sync { -    /// Determine early (before processing all events) whether the log stands a chance to be -    /// included. -    /// -    /// Note that you can return [Inclusion::Unkown] if this filter cannot determine yet a definite -    /// answer. -    fn filter_early(&self, _: &PartialEvtc) -> Inclusion { -        Inclusion::Unknown -    } - -    /// Return whether the log should be included, according to this filter. -    fn filter(&self, log: &LogResult) -> bool; -} - -#[derive(Debug, Clone, Copy)] -pub struct Const(pub bool); - -impl Const { -    pub fn new(output: bool) -> Box<dyn Filter> { -        Box::new(Const(output)) -    } -} - -impl Filter for Const { -    fn filter_early(&self, _: &PartialEvtc) -> Inclusion { -        self.0.into() -    } - -    fn filter(&self, _: &LogResult) -> bool { -        self.0 -    } -} - -struct AndFilter(Box<dyn Filter>, Box<dyn Filter>); - -impl Filter for AndFilter { -    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { -        let lhs = self.0.filter_early(partial_evtc); -        // Short circuit behaviour -        if lhs == Inclusion::Exclude { -            Inclusion::Exclude -        } else { -            lhs & self.1.filter_early(partial_evtc) -        } -    } - -    fn filter(&self, log: &LogResult) -> bool { -        self.0.filter(log) && self.1.filter(log) -    } -} - -impl ops::BitAnd<Box<dyn Filter>> for Box<dyn Filter> { -    type Output = Box<dyn Filter>; - -    fn bitand(self, rhs: Box<dyn Filter>) -> Self::Output { -        Box::new(AndFilter(self, rhs)) -    } -} - -struct OrFilter(Box<dyn Filter>, Box<dyn Filter>); - -impl Filter for OrFilter { -    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { -        let lhs = self.0.filter_early(partial_evtc); -        // Short circuit behaviour -        if lhs == Inclusion::Include { -            Inclusion::Include -        } else { -            lhs | self.1.filter_early(partial_evtc) -        } -    } - -    fn filter(&self, log: &LogResult) -> bool { -        self.0.filter(log) || self.1.filter(log) -    } -} - -impl ops::BitOr<Box<dyn Filter>> for Box<dyn Filter> { -    type Output = Box<dyn Filter>; - -    fn bitor(self, rhs: Box<dyn Filter>) -> Self::Output { -        Box::new(OrFilter(self, rhs)) -    } -} - -struct NotFilter(Box<dyn Filter>); - -impl Filter for NotFilter { -    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { -        !self.0.filter_early(partial_evtc) -    } - -    fn filter(&self, log: &LogResult) -> bool { -        !self.0.filter(log) -    } -} - -impl ops::Not for Box<dyn Filter> { -    type Output = Box<dyn Filter>; - -    fn not(self) -> Self::Output { -        Box::new(NotFilter(self)) -    } -} - -// From here onwards, we have the specific filters - -/// Filter that filters according to the name. -/// -/// The given SearchField determines in which field something should be searched. -#[derive(Debug, Clone)] -pub struct NameFilter(SearchField, Regex); - -impl NameFilter { -    pub fn new(field: SearchField, regex: Regex) -> Box<dyn Filter> { -        Box::new(NameFilter(field, regex)) -    } -} - -impl Filter for NameFilter { -    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { -        if self.0 == SearchField::Guild { -            return Inclusion::Unknown; -        } - -        for player in &partial_evtc.agents { -            let fancy = Agent::from_raw(player); -            if let Ok(AgentName::Player { -                ref account_name, -                ref character_name, -                .. -            }) = fancy.as_ref().map(Agent::name) -            { -                let field = match self.0 { -                    SearchField::Account => account_name, -                    SearchField::Character => character_name, -                    _ => unreachable!("We already checked for Guild earlier"), -                }; -                if self.1.is_match(field) { -                    return Inclusion::Include; -                } -            } -        } -        Inclusion::Exclude -    } - -    fn filter(&self, log: &LogResult) -> bool { -        for player in &log.players { -            match self.0 { -                SearchField::Account if self.1.is_match(&player.account_name) => return true, -                SearchField::Character if self.1.is_match(&player.character_name) => return true, -                SearchField::Guild => { -                    let guild_ok = player -                        .guild_id -                        .as_ref() -                        .and_then(|id| guilds::lookup(id)) -                        .map(|guild| self.1.is_match(guild.tag()) || self.1.is_match(guild.name())) -                        .unwrap_or(false); -                    if guild_ok { -                        return true; -                    } -                } -                _ => (), -            } -        } -        false -    } -} - -#[derive(Debug, Clone)] -pub struct BossFilter(HashSet<Boss>); - -impl BossFilter { -    pub fn new(bosses: HashSet<Boss>) -> Box<dyn Filter> { -        Box::new(BossFilter(bosses)) -    } -} - -impl Filter for BossFilter { -    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { -        let boss = Boss::from_u16(partial_evtc.header.combat_id); -        boss.map(|b| self.0.contains(&b).into()) -            .unwrap_or(Inclusion::Include) -    } - -    fn filter(&self, log: &LogResult) -> bool { -        let boss = Boss::from_u16(log.boss_id); -        boss.map(|b| self.0.contains(&b)).unwrap_or(false) -    } -} - -#[derive(Debug, Clone)] -pub struct OutcomeFilter(HashSet<FightOutcome>); - -impl OutcomeFilter { -    pub fn new(outcomes: HashSet<FightOutcome>) -> Box<dyn Filter> { -        Box::new(OutcomeFilter(outcomes)) -    } -} - -impl Filter for OutcomeFilter { -    fn filter(&self, log: &LogResult) -> bool { -        self.0.contains(&log.outcome) -    } -} - -#[derive(Debug, Clone)] -pub struct WeekdayFilter(HashSet<Weekday>); - -impl WeekdayFilter { -    pub fn new(weekdays: HashSet<Weekday>) -> Box<dyn Filter> { -        Box::new(WeekdayFilter(weekdays)) -    } -} - -impl Filter for WeekdayFilter { -    fn filter(&self, log: &LogResult) -> bool { -        self.0.contains(&log.time.weekday()) -    } -} - -#[derive(Debug, Clone)] -pub struct TimeFilter(Option<NaiveDateTime>, Option<NaiveDateTime>); - -impl TimeFilter { -    pub fn new(after: Option<NaiveDateTime>, before: Option<NaiveDateTime>) -> Box<dyn Filter> { -        Box::new(TimeFilter(after, before)) -    } -} - -impl Filter for TimeFilter { -    fn filter(&self, log: &LogResult) -> bool { -        let after_ok = match self.0 { -            Some(time) => time <= log.time, -            None => true, -        }; -        let before_ok = match self.1 { -            Some(time) => time >= log.time, -            None => true, -        }; - -        after_ok && before_ok -    } -} - -#[cfg(test)] -mod tests { -    use super::*; - -    #[test] -    fn test_inclusion_not() { -        use Inclusion::*; - -        assert_eq!(!Exclude, Include); -        assert_eq!(!Include, Exclude); -        assert_eq!(!Unknown, Unknown); -    } - -    #[test] -    fn test_inclusion_and() { -        use Inclusion::*; - -        assert_eq!(Exclude & Exclude, Exclude); -        assert_eq!(Exclude & Unknown, Exclude); -        assert_eq!(Exclude & Include, Exclude); - -        assert_eq!(Unknown & Exclude, Exclude); -        assert_eq!(Unknown & Unknown, Unknown); -        assert_eq!(Unknown & Include, Unknown); - -        assert_eq!(Include & Exclude, Exclude); -        assert_eq!(Include & Unknown, Unknown); -        assert_eq!(Include & Include, Include); -    } - -    #[test] -    fn test_inclusion_or() { -        use Inclusion::*; - -        assert_eq!(Exclude | Exclude, Exclude); -        assert_eq!(Exclude | Unknown, Unknown); -        assert_eq!(Exclude | Include, Include); - -        assert_eq!(Unknown | Exclude, Unknown); -        assert_eq!(Unknown | Unknown, Unknown); -        assert_eq!(Unknown | Include, Include); - -        assert_eq!(Include | Exclude, Include); -        assert_eq!(Include | Unknown, Include); -        assert_eq!(Include | Include, Include); -    } -} diff --git a/src/filters/log.rs b/src/filters/log.rs new file mode 100644 index 0000000..ded4c44 --- /dev/null +++ b/src/filters/log.rs @@ -0,0 +1,95 @@ +//! This module contains specific filters that operate on log files. +//! +//! This is the "base unit", as each file corresponds to one log. Filters on other items (such as +//! players) have to be lifted into log filters first. +use super::{ +    super::{FightOutcome, LogResult, Weekday}, +    Filter, Inclusion, +}; + +use std::collections::HashSet; + +use evtclib::raw::parser::PartialEvtc; +use evtclib::statistics::gamedata::Boss; + +use chrono::{Datelike, NaiveDateTime}; +use num_traits::FromPrimitive as _; + +/// Filter trait used for filters that operate on complete logs. +pub trait LogFilter = Filter<PartialEvtc, LogResult>; + +#[derive(Debug, Clone)] +pub struct BossFilter(HashSet<Boss>); + +impl BossFilter { +    pub fn new(bosses: HashSet<Boss>) -> Box<dyn LogFilter> { +        Box::new(BossFilter(bosses)) +    } +} + +impl Filter<PartialEvtc, LogResult> for BossFilter { +    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { +        let boss = Boss::from_u16(partial_evtc.header.combat_id); +        boss.map(|b| self.0.contains(&b).into()) +            .unwrap_or(Inclusion::Include) +    } + +    fn filter(&self, log: &LogResult) -> bool { +        let boss = Boss::from_u16(log.boss_id); +        boss.map(|b| self.0.contains(&b)).unwrap_or(false) +    } +} + +#[derive(Debug, Clone)] +pub struct OutcomeFilter(HashSet<FightOutcome>); + +impl OutcomeFilter { +    pub fn new(outcomes: HashSet<FightOutcome>) -> Box<dyn LogFilter> { +        Box::new(OutcomeFilter(outcomes)) +    } +} + +impl Filter<PartialEvtc, LogResult> for OutcomeFilter { +    fn filter(&self, log: &LogResult) -> bool { +        self.0.contains(&log.outcome) +    } +} + +#[derive(Debug, Clone)] +pub struct WeekdayFilter(HashSet<Weekday>); + +impl WeekdayFilter { +    pub fn new(weekdays: HashSet<Weekday>) -> Box<dyn LogFilter> { +        Box::new(WeekdayFilter(weekdays)) +    } +} + +impl Filter<PartialEvtc, LogResult> for WeekdayFilter { +    fn filter(&self, log: &LogResult) -> bool { +        self.0.contains(&log.time.weekday()) +    } +} + +#[derive(Debug, Clone)] +pub struct TimeFilter(Option<NaiveDateTime>, Option<NaiveDateTime>); + +impl TimeFilter { +    pub fn new(after: Option<NaiveDateTime>, before: Option<NaiveDateTime>) -> Box<dyn LogFilter> { +        Box::new(TimeFilter(after, before)) +    } +} + +impl Filter<PartialEvtc, LogResult> for TimeFilter { +    fn filter(&self, log: &LogResult) -> bool { +        let after_ok = match self.0 { +            Some(time) => time <= log.time, +            None => true, +        }; +        let before_ok = match self.1 { +            Some(time) => time >= log.time, +            None => true, +        }; + +        after_ok && before_ok +    } +} diff --git a/src/filters/mod.rs b/src/filters/mod.rs new file mode 100644 index 0000000..62dc04b --- /dev/null +++ b/src/filters/mod.rs @@ -0,0 +1,213 @@ +#![allow(clippy::new_ret_no_self)] +use std::ops; + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive as _; + +pub mod log; +pub mod player; + +/// Early filtering result. +/// +/// This implements a [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic), +/// similar to SQL's `NULL`. +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] +#[repr(i8)] +pub enum Inclusion { +    /// The log should be included. +    Include = 1, +    /// The state is yet unknown. +    Unknown = 0, +    /// The log should be excluded. +    Exclude = -1, +} + +impl ops::Not for Inclusion { +    type Output = Self; + +    fn not(self) -> Self::Output { +        Inclusion::from_i8(-(self as i8)).unwrap() +    } +} + +impl ops::BitAnd<Inclusion> for Inclusion { +    type Output = Self; + +    fn bitand(self, rhs: Inclusion) -> Self::Output { +        Inclusion::from_i8((self as i8).min(rhs as i8)).unwrap() +    } +} + +impl ops::BitOr<Inclusion> for Inclusion { +    type Output = Self; + +    fn bitor(self, rhs: Inclusion) -> Self::Output { +        Inclusion::from_i8((self as i8).max(rhs as i8)).unwrap() +    } +} + +impl From<bool> for Inclusion { +    fn from(data: bool) -> Self { +        if data { +            Inclusion::Include +        } else { +            Inclusion::Exclude +        } +    } +} + +/// The main filter trait. +/// +/// Filters are usually handled as a `Box<dyn Filter>`. +pub trait Filter<Early, Late>: Send + Sync { +    /// Determine early (before processing all events) whether the log stands a chance to be +    /// included. +    /// +    /// Note that you can return [Inclusion::Unkown] if this filter cannot determine yet a definite +    /// answer. +    fn filter_early(&self, _: &Early) -> Inclusion { +        Inclusion::Unknown +    } + +    /// Return whether the log should be included, according to this filter. +    fn filter(&self, _: &Late) -> bool; +} + +#[derive(Debug, Clone, Copy)] +pub struct Const(pub bool); + +impl Const { +    pub fn new<E, L>(output: bool) -> Box<dyn Filter<E, L>> { +        Box::new(Const(output)) +    } +} + +impl<E, L> Filter<E, L> for Const { +    fn filter_early(&self, _: &E) -> Inclusion { +        self.0.into() +    } + +    fn filter(&self, _: &L) -> bool { +        self.0 +    } +} + +struct AndFilter<E, L>(Box<dyn Filter<E, L>>, Box<dyn Filter<E, L>>); + +impl<E, L> Filter<E, L> for AndFilter<E, L> { +    fn filter_early(&self, partial_evtc: &E) -> Inclusion { +        let lhs = self.0.filter_early(partial_evtc); +        // Short circuit behaviour +        if lhs == Inclusion::Exclude { +            Inclusion::Exclude +        } else { +            lhs & self.1.filter_early(partial_evtc) +        } +    } + +    fn filter(&self, log: &L) -> bool { +        self.0.filter(log) && self.1.filter(log) +    } +} + +impl<E: 'static, L: 'static> ops::BitAnd<Box<dyn Filter<E, L>>> for Box<dyn Filter<E, L>> { +    type Output = Box<dyn Filter<E, L>>; + +    fn bitand(self, rhs: Box<dyn Filter<E, L>>) -> Self::Output { +        Box::new(AndFilter(self, rhs)) +    } +} + +struct OrFilter<E, L>(Box<dyn Filter<E, L>>, Box<dyn Filter<E, L>>); + +impl<E, L> Filter<E, L> for OrFilter<E, L> { +    fn filter_early(&self, partial_evtc: &E) -> Inclusion { +        let lhs = self.0.filter_early(partial_evtc); +        // Short circuit behaviour +        if lhs == Inclusion::Include { +            Inclusion::Include +        } else { +            lhs | self.1.filter_early(partial_evtc) +        } +    } + +    fn filter(&self, log: &L) -> bool { +        self.0.filter(log) || self.1.filter(log) +    } +} + +impl<E: 'static, L: 'static> ops::BitOr<Box<dyn Filter<E, L>>> for Box<dyn Filter<E, L>> { +    type Output = Box<dyn Filter<E, L>>; + +    fn bitor(self, rhs: Box<dyn Filter<E, L>>) -> Self::Output { +        Box::new(OrFilter(self, rhs)) +    } +} + +struct NotFilter<E, L>(Box<dyn Filter<E, L>>); + +impl<E, L> Filter<E, L> for NotFilter<E, L> { +    fn filter_early(&self, partial_evtc: &E) -> Inclusion { +        !self.0.filter_early(partial_evtc) +    } + +    fn filter(&self, log: &L) -> bool { +        !self.0.filter(log) +    } +} + +impl<E: 'static, L: 'static> ops::Not for Box<dyn Filter<E, L>> { +    type Output = Box<dyn Filter<E, L>>; + +    fn not(self) -> Self::Output { +        Box::new(NotFilter(self)) +    } +} + +#[cfg(test)] +mod tests { +    use super::*; + +    #[test] +    fn test_inclusion_not() { +        use Inclusion::*; + +        assert_eq!(!Exclude, Include); +        assert_eq!(!Include, Exclude); +        assert_eq!(!Unknown, Unknown); +    } + +    #[test] +    fn test_inclusion_and() { +        use Inclusion::*; + +        assert_eq!(Exclude & Exclude, Exclude); +        assert_eq!(Exclude & Unknown, Exclude); +        assert_eq!(Exclude & Include, Exclude); + +        assert_eq!(Unknown & Exclude, Exclude); +        assert_eq!(Unknown & Unknown, Unknown); +        assert_eq!(Unknown & Include, Unknown); + +        assert_eq!(Include & Exclude, Exclude); +        assert_eq!(Include & Unknown, Unknown); +        assert_eq!(Include & Include, Include); +    } + +    #[test] +    fn test_inclusion_or() { +        use Inclusion::*; + +        assert_eq!(Exclude | Exclude, Exclude); +        assert_eq!(Exclude | Unknown, Unknown); +        assert_eq!(Exclude | Include, Include); + +        assert_eq!(Unknown | Exclude, Unknown); +        assert_eq!(Unknown | Unknown, Unknown); +        assert_eq!(Unknown | Include, Include); + +        assert_eq!(Include | Exclude, Include); +        assert_eq!(Include | Unknown, Include); +        assert_eq!(Include | Include, Include); +    } +} diff --git a/src/filters/player.rs b/src/filters/player.rs new file mode 100644 index 0000000..6cc7713 --- /dev/null +++ b/src/filters/player.rs @@ -0,0 +1,109 @@ +//! This module contains filters pertaining to a single player. +//! +//! Additionally, it provides methods to lift a player filter to a log filter with [`any`][any] and +//! [`all`][all]. +use super::{ +    super::{guilds, LogResult, Player, SearchField}, +    log::LogFilter, +    Filter, Inclusion, +}; + +use evtclib::raw::parser::PartialEvtc; +use evtclib::{Agent, AgentName}; + +use regex::Regex; + +/// Filter type for filters that operate on single players. +pub trait PlayerFilter = Filter<Agent, Player>; + +/// Struct that lifts a [`PlayerFilter`](traitalias.PlayerFilter.html) to a +/// [`LogFilter`](../log/traitalias.LogFilter.html) by requiring all players to match. +/// +/// This struct will short-circuit once the result is known. +struct AllPlayers(Box<dyn PlayerFilter>); + +impl Filter<PartialEvtc, LogResult> for AllPlayers { +    fn filter_early(&self, partial_evtc: &PartialEvtc) -> Inclusion { +        let mut result = Inclusion::Include; +        for agent in &partial_evtc.agents { +            if !agent.is_player() { +                continue; +            } + +            let agent = Agent::from_raw(agent); +            if let Ok(agent) = agent { +                result = result & self.0.filter_early(&agent); +            } else { +                result = result & Inclusion::Unknown; +            } +            // Short circuit +            if result == Inclusion::Exclude { +                return result; +            } +        } +        result +    } + +    fn filter(&self, log: &LogResult) -> bool { +        log.players.iter().all(|p| self.0.filter(p)) +    } +} + +/// Construct a filter that requires the given `player_filter` to match for all players in a log. +pub fn all(player_filter: Box<dyn PlayerFilter>) -> Box<dyn LogFilter> { +    Box::new(AllPlayers(player_filter)) +} + +/// Construct a filter that requires the given `player_filter` to match for any player in a log. +pub fn any(player_filter: Box<dyn PlayerFilter>) -> Box<dyn LogFilter> { +    !all(!player_filter) +} + +/// Filter that filters players according to their name. +/// +/// The given SearchField determines in which field something should be searched. +#[derive(Debug, Clone)] +pub struct NameFilter(SearchField, Regex); + +impl NameFilter { +    pub fn new(field: SearchField, regex: Regex) -> Box<dyn PlayerFilter> { +        Box::new(NameFilter(field, regex)) +    } +} + +impl Filter<Agent, Player> for NameFilter { +    fn filter_early(&self, agent: &Agent) -> Inclusion { +        if self.0 == SearchField::Guild { +            return Inclusion::Unknown; +        } + +        if let AgentName::Player { +            ref account_name, +            ref character_name, +            .. +        } = agent.name() +        { +            let field = match self.0 { +                SearchField::Account => account_name, +                SearchField::Character => character_name, +                _ => unreachable!("We already checked for Guild earlier"), +            }; +            self.1.is_match(field).into() +        } else { +            Inclusion::Unknown +        } +    } + +    fn filter(&self, player: &Player) -> bool { +        match self.0 { +            SearchField::Account => self.1.is_match(&player.account_name), +            SearchField::Character => self.1.is_match(&player.character_name), +            SearchField::Guild => player +                .guild_id +                .as_ref() +                .and_then(|id| guilds::lookup(id)) +                .map(|guild| self.1.is_match(guild.tag()) || self.1.is_match(guild.name())) +                .unwrap_or(false), +        } +    } +} diff --git a/src/main.rs b/src/main.rs index 41df732..bea03f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![feature(trait_alias)]  use std::collections::HashMap;  use std::fs::File;  use std::io::{BufReader, Read, Seek}; @@ -15,7 +16,7 @@ use walkdir::{DirEntry, WalkDir};  use evtclib::{AgentKind, AgentName, EventKind, Log};  mod filters; -use filters::{Filter, Inclusion}; +use filters::{log::LogFilter, Inclusion};  mod guilds;  mod logger;  mod output; @@ -260,21 +261,24 @@ fn is_log_file(entry: &DirEntry) -> bool {          .unwrap_or(false)  } -fn build_filter(opt: &Opt) -> Box<dyn Filter> { -    let mut filter = filters::Const::new(false); -    for field in opt.field.values() { -        filter = filter | filters::NameFilter::new(*field, opt.expression.clone()); -    } +fn build_filter(opt: &Opt) -> Box<dyn LogFilter> { +    let player_filter = opt +        .field +        .values() +        .iter() +        .map(|field| filters::player::NameFilter::new(*field, opt.expression.clone())) +        .fold(filters::Const::new(false), |a, f| a | f); +    let mut filter = filters::player::any(player_filter);      if opt.invert {          filter = !filter;      }      filter = filter -        & filters::BossFilter::new(opt.bosses.values().clone()) -        & filters::OutcomeFilter::new(opt.outcome.values().clone()) -        & filters::WeekdayFilter::new(opt.weekdays.values().clone()) -        & filters::TimeFilter::new(opt.after, opt.before); +        & filters::log::BossFilter::new(opt.bosses.values().clone()) +        & filters::log::OutcomeFilter::new(opt.outcome.values().clone()) +        & filters::log::WeekdayFilter::new(opt.weekdays.values().clone()) +        & filters::log::TimeFilter::new(opt.after, opt.before);      filter  } @@ -282,7 +286,7 @@ fn build_filter(opt: &Opt) -> Box<dyn Filter> {  /// Run the grep search with the given options.  fn grep(opt: &Opt) -> Result<()> {      let pipeline = &output::build_pipeline(opt); -    let filter: &dyn Filter = &*build_filter(opt); +    let filter: &dyn LogFilter = &*build_filter(opt);      rayon::scope(|s| {          let walker = WalkDir::new(&opt.path);          for entry in walker { @@ -309,7 +313,7 @@ fn grep(opt: &Opt) -> Result<()> {  /// If the log matches, returns `Ok(Some(..))`.  /// If the log doesn't match, returns `Ok(None)`.  /// If there was a fatal error, returns `Err(..)`. -fn search_log(entry: &DirEntry, filter: &dyn Filter) -> Result<Option<LogResult>> { +fn search_log(entry: &DirEntry, filter: &dyn LogFilter) -> Result<Option<LogResult>> {      let file_stream = BufReader::new(File::open(entry.path())?);      let is_zip = entry          .file_name() | 
