aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel <kingdread@gmx.de>2020-04-17 15:18:20 +0200
committerDaniel <kingdread@gmx.de>2020-04-17 15:18:20 +0200
commitba491c8a5f6c8c2fa86b12dacf9d80f92da9168a (patch)
treee448ef79962fa06f0653d01e0716c8a8a14baa56
parentc381bfd0972d8f8e080c74d63df66486e514f35f (diff)
downloadraidgrep-ba491c8a5f6c8c2fa86b12dacf9d80f92da9168a.tar.gz
raidgrep-ba491c8a5f6c8c2fa86b12dacf9d80f92da9168a.tar.bz2
raidgrep-ba491c8a5f6c8c2fa86b12dacf9d80f92da9168a.zip
split off player filters and log filters
As it turns out, we can easily re-use the existing Filter machinery to generalize over LogFilters (which operate on LogResults) and PlayerFilters (which operate on Players). The feature trait_aliases is not strictly needed but makes the function signatures a bit nicer and easier to read, and it reduces the chances of an error (e.g. by using Filter<&PartialEvtc, ...>).
-rw-r--r--src/filters.rs361
-rw-r--r--src/filters/log.rs95
-rw-r--r--src/filters/mod.rs213
-rw-r--r--src/filters/player.rs109
-rw-r--r--src/main.rs28
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()