diff options
| -rw-r--r-- | raidgrep.1.asciidoc | 8 | ||||
| -rw-r--r-- | src/fexpr/grammar.lalrpop | 13 | ||||
| -rw-r--r-- | src/fexpr/mod.rs | 4 | ||||
| -rw-r--r-- | src/filters/player.rs | 26 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/playerclass.rs | 6 | 
6 files changed, 55 insertions, 3 deletions
| diff --git a/raidgrep.1.asciidoc b/raidgrep.1.asciidoc index 78dcf78..1cbdf85 100644 --- a/raidgrep.1.asciidoc +++ b/raidgrep.1.asciidoc @@ -139,6 +139,14 @@ The following predicates have to be wrapped in either a *any(player: ...)* or      Shorthand that matches if the character or the account name match the given      regular expression. +*-class* 'CLASSES':: +    Match the player if they have one of the given classes. Note that a core +    class won't match its elite specializations, so _Guardian_ won't match +    _Dragonhunter_. + +    + +    Names can be comma separated, in which case the player must have any of the +    listed classes. +  === Boss Names  Bosses can be referred to by their official name, although if that name diff --git a/src/fexpr/grammar.lalrpop b/src/fexpr/grammar.lalrpop index 654722b..f91da19 100644 --- a/src/fexpr/grammar.lalrpop +++ b/src/fexpr/grammar.lalrpop @@ -3,6 +3,7 @@ use super::{      FErrorKind,      FightOutcome,      filters, +    PlayerClass,      SearchField,  };  use evtclib::Boss; @@ -77,6 +78,8 @@ PlayerPredicate: Box<dyn filters::player::PlayerFilter> = {          filters::player::account(<>.clone())          | filters::player::character(<>), +    "-class" <Comma<PlayerClass>> => filters::player::class(<>), +      "(" <PlayerFilter> ")",  } @@ -135,6 +138,16 @@ Boss: Boss = {      }),  } +PlayerClass: PlayerClass = { +    <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User { +        error: FError { +            location: l, +            data: w.into(), +            kind: FErrorKind::InvalidClass, +        } +    }), +} +  Date: DateTime<Utc> = {      <l:@L> <d:datetime> =>? Local.datetime_from_str(d, "%Y-%m-%d %H:%M:%S")          .map_err(|error| ParseError::User { diff --git a/src/fexpr/mod.rs b/src/fexpr/mod.rs index 2bdbfe7..452d66c 100644 --- a/src/fexpr/mod.rs +++ b/src/fexpr/mod.rs @@ -3,7 +3,7 @@  //! This module contains methods to parse a given string into an abstract filter tree, check its  //! type and convert it to a [`Filter`][super::filters::Filter].  // Make it available in the grammar mod. -use super::{filters, FightOutcome, SearchField}; +use super::{filters, playerclass::PlayerClass, FightOutcome, SearchField};  use std::{error, fmt}; @@ -44,6 +44,8 @@ pub enum FErrorKind {      InvalidTimestamp(#[from] chrono::format::ParseError),      #[error("invalid boss name")]      InvalidBoss, +    #[error("invalid class name")] +    InvalidClass,  }  /// Shortcut to create a new parser and parse the given input. diff --git a/src/filters/player.rs b/src/filters/player.rs index 3af2be2..2b14eb0 100644 --- a/src/filters/player.rs +++ b/src/filters/player.rs @@ -3,12 +3,12 @@  //! Additionally, it provides methods to lift a player filter to a log filter with [`any`][any] and  //! [`all`][all].  use super::{ -    super::{guilds, EarlyLogResult, LogResult, Player, SearchField}, +    super::{guilds, playerclass::PlayerClass, EarlyLogResult, LogResult, Player, SearchField},      log::LogFilter,      Filter, Inclusion,  }; -use std::convert::TryFrom; +use std::{collections::HashSet, convert::TryFrom};  use evtclib::{Agent, AgentKind}; @@ -113,3 +113,25 @@ pub fn character(regex: Regex) -> Box<dyn PlayerFilter> {  pub fn account(regex: Regex) -> Box<dyn PlayerFilter> {      name(SearchField::Account, regex)  } + +#[derive(Clone, Debug)] +struct ClassFilter(HashSet<PlayerClass>); + +impl Filter<Agent, Player> 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<PlayerClass>) -> Box<dyn PlayerFilter> { +    Box::new(ClassFilter(classes)) +} diff --git a/src/main.rs b/src/main.rs index d9f0817..bf4c472 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,6 +43,7 @@ const APP_NAME: &str = "raidgrep";  ///     -character REGEX        True if the character name matches the regex.  ///     -account REGEX          True if the account name matches the regex.  ///     -name REGEX             True if either character or account name match. +///     -class CLASSES          True if the player has one of the listed classes.  ///  ///     -success                Only include successful logs.  ///     -wipe                   Only include failed logs. diff --git a/src/playerclass.rs b/src/playerclass.rs index 77b9794..247e8b1 100644 --- a/src/playerclass.rs +++ b/src/playerclass.rs @@ -59,6 +59,12 @@ impl From<(Profession, Option<EliteSpec>)> for PlayerClass {      }  } +impl From<&evtclib::Player> for PlayerClass { +    fn from(player: &evtclib::Player) -> Self { +        (player.profession(), player.elite()).into() +    } +} +  impl fmt::Display for PlayerClass {      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {          use EliteSpec::*; | 
