diff options
| -rw-r--r-- | src/fexpr/grammar.lalrpop | 37 | ||||
| -rw-r--r-- | src/fexpr/mod.rs | 3 | ||||
| -rw-r--r-- | src/filters/log.rs | 2 | ||||
| -rw-r--r-- | src/filters/mod.rs | 1 | ||||
| -rw-r--r-- | src/filters/values.rs | 144 | ||||
| -rw-r--r-- | src/main.rs | 12 | 
6 files changed, 195 insertions, 4 deletions
| diff --git a/src/fexpr/grammar.lalrpop b/src/fexpr/grammar.lalrpop index 45f4fde..c2df097 100644 --- a/src/fexpr/grammar.lalrpop +++ b/src/fexpr/grammar.lalrpop @@ -4,12 +4,15 @@ use super::{      FightOutcome,      filters,      PlayerClass, + +    DateProducer, +    DurationProducer,  };  use evtclib::Boss;  use std::collections::HashSet;  use lalrpop_util::ParseError; -use chrono::{DateTime, Local, TimeZone, Utc, Weekday}; +use chrono::{DateTime, Local, TimeZone, Utc, Weekday, Duration};  use regex::{Regex, RegexBuilder};  grammar; @@ -68,6 +71,9 @@ LogPredicate: Box<dyn filters::log::LogFilter> = {      "any" "(" "player" ":" <PlayerFilter> ")" => filters::player::any(<>),      "exists" "(" "player" ":" <PlayerFilter> ")" => filters::player::any(<>), +    <Comparison<DateProducer>>, +    <Comparison<DurationProducer>>, +      "(" <LogFilter> ")",  } @@ -175,6 +181,22 @@ Date: DateTime<Utc> = {          .map(|d| d.with_timezone(&Utc)),  } +Duration: Duration = { +    duration => Duration::seconds(<>[..<>.len() - 1].parse().unwrap()), +} + +CompOp: filters::values::CompOp = { +    "<" => filters::values::CompOp::Less, +    "<=" => filters::values::CompOp::LessEqual, +    "=" => filters::values::CompOp::Equal, +    ">=" => filters::values::CompOp::GreaterEqual, +    ">" => filters::values::CompOp::Greater, +} + +Comparison<T>: Box<dyn filters::log::LogFilter> = { +    <lhs:T> <op:CompOp> <rhs:T> => filters::values::comparison(lhs, op, rhs), +} +  Comma<T>: HashSet<T> = {      <v:(<T> ",")*> <e:T> => {          let mut result = v.into_iter().collect::<HashSet<_>>(); @@ -183,6 +205,16 @@ Comma<T>: HashSet<T> = {      },  } +DateProducer: Box<dyn DateProducer> = { +    <Date> => filters::values::constant(<>), +    "-time" => filters::values::time(), +} + +DurationProducer: Box<dyn DurationProducer> = { +    <Duration> => filters::values::constant(<>), +    "-duration" => filters::values::duration(), +} +  match {      "player" => "player",      "not" => "not", @@ -194,7 +226,8 @@ match {      r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d" => datetime,      r"\d\d\d\d-\d\d-\d\d" => date, -    r"[\w]+" => word, +    r"\d+s" => duration, +    r"[[:alpha:]][\w]*" => word,      r#""[^"]*""# => string,      _ diff --git a/src/fexpr/mod.rs b/src/fexpr/mod.rs index c6a3a39..1738e44 100644 --- a/src/fexpr/mod.rs +++ b/src/fexpr/mod.rs @@ -11,6 +11,9 @@ use itertools::Itertools;  use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};  use thiserror::Error; +trait DateProducer = filters::values::Producer<Output = chrono::DateTime<chrono::Utc>>; +trait DurationProducer = filters::values::Producer<Output = chrono::Duration>; +  lalrpop_mod!(#[allow(clippy::all)] pub grammar, "/fexpr/grammar.rs");  #[derive(Debug)] diff --git a/src/filters/log.rs b/src/filters/log.rs index 8cfdcb4..9ca7d3c 100644 --- a/src/filters/log.rs +++ b/src/filters/log.rs @@ -137,7 +137,7 @@ fn time_is_between(  ///  /// This expects the filename to have the datetime in the pattern `YYYYmmdd-HHMMSS` somewhere in  /// it. -fn datetime_from_filename(name: &OsStr) -> Option<DateTime<Utc>> { +pub(crate) fn datetime_from_filename(name: &OsStr) -> Option<DateTime<Utc>> {      let date_match = DATE_REGEX.find(name.to_str()?)?;      let local_time = Local          .datetime_from_str(date_match.as_str(), "%Y%m%d-%H%M%S") diff --git a/src/filters/mod.rs b/src/filters/mod.rs index 162b6f8..e966851 100644 --- a/src/filters/mod.rs +++ b/src/filters/mod.rs @@ -5,6 +5,7 @@ use num_traits::FromPrimitive as _;  pub mod log;  pub mod player; +pub mod values;  /// Early filtering result.  /// diff --git a/src/filters/values.rs b/src/filters/values.rs new file mode 100644 index 0000000..543b59c --- /dev/null +++ b/src/filters/values.rs @@ -0,0 +1,144 @@ +use std::{ +    cmp::Ordering, +    fmt::{self, Debug}, +}; + +use chrono::{DateTime, Duration, Utc}; + +use super::{log::LogFilter, Filter}; +use crate::{EarlyLogResult, LogResult}; + +pub trait Producer: Send + Sync + Debug { +    type Output; + +    fn produce_early(&self, _early_log: &EarlyLogResult) -> Option<Self::Output> { +        None +    } + +    fn produce(&self, log: &LogResult) -> Self::Output; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum CompOp { +    Less, +    LessEqual, +    Equal, +    GreaterEqual, +    Greater, +} + +impl fmt::Display for CompOp { +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +        let symbol = match self { +            CompOp::Less => "<", +            CompOp::LessEqual => "<=", +            CompOp::Equal => "=", +            CompOp::GreaterEqual => ">=", +            CompOp::Greater => ">", +        }; +        f.pad(symbol) +    } +} + +impl CompOp { +    pub fn matches(self, cmp: Ordering) -> bool { +        match cmp { +            Ordering::Less => self == CompOp::Less || self == CompOp::LessEqual, +            Ordering::Equal => { +                self == CompOp::LessEqual || self == CompOp::Equal || self == CompOp::GreaterEqual +            } +            Ordering::Greater => self == CompOp::Greater || self == CompOp::GreaterEqual, +        } +    } +} + +struct Comparator<V>( +    Box<dyn Producer<Output = V>>, +    CompOp, +    Box<dyn Producer<Output = V>>, +); + +impl<V> Debug for Comparator<V> { +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +        write!(f, "({:?} {} {:?})", self.0, self.1, self.2) +    } +} + +impl<V> Filter<EarlyLogResult, LogResult> for Comparator<V> +where +    V: Ord, +{ +    fn filter(&self, log: &LogResult) -> bool { +        let lhs = self.0.produce(log); +        let rhs = self.2.produce(log); +        self.1.matches(lhs.cmp(&rhs)) +    } +} + +pub fn comparison<V: 'static>( +    lhs: Box<dyn Producer<Output = V>>, +    op: CompOp, +    rhs: Box<dyn Producer<Output = V>>, +) -> Box<dyn LogFilter> +where +    V: Ord, +{ +    Box::new(Comparator(lhs, op, rhs)) +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct ConstantProducer<V>(V); + +impl<V: Send + Sync + Debug + Clone> Producer for ConstantProducer<V> { +    type Output = V; +    fn produce_early(&self, _: &EarlyLogResult) -> Option<Self::Output> { +        Some(self.0.clone()) +    } + +    fn produce(&self, _: &LogResult) -> Self::Output { +        self.0.clone() +    } +} + +pub fn constant<V: Send + Sync + Debug + Clone + 'static>( +    value: V, +) -> Box<dyn Producer<Output = V>> { +    Box::new(ConstantProducer(value)) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +struct TimeProducer; + +impl Producer for TimeProducer { +    type Output = DateTime<Utc>; + +    fn produce_early(&self, early_log: &EarlyLogResult) -> Option<Self::Output> { +        early_log +            .log_file +            .file_name() +            .and_then(super::log::datetime_from_filename) +    } + +    fn produce(&self, log: &LogResult) -> Self::Output { +        log.time +    } +} + +pub fn time() -> Box<dyn Producer<Output = DateTime<Utc>>> { +    Box::new(TimeProducer) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +struct DurationProducer; + +impl Producer for DurationProducer { +    type Output = Duration; + +    fn produce(&self, log: &LogResult) -> Self::Output { +        log.duration +    } +} + +pub fn duration() -> Box<dyn Producer<Output = Duration>> { +    Box::new(DurationProducer) +} diff --git a/src/main.rs b/src/main.rs index ba834ce..9ed67cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::str::FromStr;  use std::sync::atomic::{AtomicBool, Ordering};  use anyhow::{anyhow, Context, Error, Result}; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{DateTime, Duration, TimeZone, Utc};  use colored::Colorize;  use log::debug;  use regex::Regex; @@ -156,6 +156,8 @@ pub struct LogResult {      log_file: PathBuf,      /// The time of the recording.      time: DateTime<Utc>, +    /// The duration of the fight. +    duration: Duration,      /// The boss.      boss: Option<Boss>,      /// A vector of all participating players. @@ -550,9 +552,17 @@ fn extract_info(path: &Path, log: &Log) -> LogResult {          .collect::<Vec<Player>>();      players.sort(); +    let duration = log +        .local_end_timestamp() +        .and_then(|end| log.local_start_timestamp().map(|start| end - start)) +        .map(|x| x as i64) +        .map(Duration::seconds) +        .unwrap_or_else(Duration::zero); +      LogResult {          log_file: path.to_path_buf(),          time: Utc.timestamp(i64::from(log.local_end_timestamp().unwrap_or(0)), 0), +        duration,          boss,          players,          outcome: get_fight_outcome(log), | 
