//! 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, playerclass::PlayerClass, EarlyLogResult, LogResult, Player}, log::LogFilter, Filter, Inclusion, }; use std::{collections::HashSet, convert::TryFrom}; use evtclib::{Agent, AgentKind}; use regex::Regex; /// Filter type for filters that operate on single players. pub trait PlayerFilter = Filter; /// 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. #[derive(Debug)] struct AllPlayers(Box); impl Filter for AllPlayers { fn filter_early(&self, early_log: &EarlyLogResult) -> Inclusion { let mut result = Inclusion::Include; for agent in &early_log.evtc.agents { if !agent.is_player() { continue; } let agent = Agent::try_from(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) -> Box { 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) -> Box { !all(!player_filter) } /// A flag indicating which fields should be searched. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum SearchField { /// Only search the account name. Account, /// Only search the character name. Character, /// Only search the guild name or tag. Guild, } /// Filter that filters players according to their name. /// /// The given SearchField determines in which field something should be searched. #[derive(Debug, Clone)] struct NameFilter(SearchField, Regex); impl Filter for NameFilter { fn filter_early(&self, agent: &Agent) -> Inclusion { if self.0 == SearchField::Guild { return Inclusion::Unknown; } if let AgentKind::Player(ref player) = agent.kind() { let field = match self.0 { SearchField::Account => player.account_name(), SearchField::Character => player.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), } } } /// Construct a `PlayerFilter` that searches the given `field` with the given `regex`. pub fn name(field: SearchField, regex: Regex) -> Box { Box::new(NameFilter(field, regex)) } /// Construct a `PlayerFilter` that searches the character name with the given `regex`. pub fn character(regex: Regex) -> Box { name(SearchField::Character, regex) } /// Construct a `PlayerFilter` that searches the account name with the given `regex`. pub fn account(regex: Regex) -> Box { name(SearchField::Account, regex) } #[derive(Clone, Debug)] struct ClassFilter(HashSet); impl Filter for ClassFilter { fn filter_early(&self, agent: &Agent) -> Inclusion { if let AgentKind::Player(ref player) = agent.kind() { self.0.contains(&player.into()).into() } else { Inclusion::Unknown } } fn filter(&self, player: &Player) -> bool { self.0.contains(&player.profession) } } /// Construct a `PlayerFilter` that matches only the given classes. pub fn class(classes: HashSet) -> Box { Box::new(ClassFilter(classes)) }