handler macro

This commit is contained in:
Robin Appelman 2021-08-08 01:06:19 +02:00
commit e846fcceae
5 changed files with 91 additions and 56 deletions

View file

@ -7,11 +7,12 @@ edition = "2018"
steamid-ng = "1" steamid-ng = "1"
nom = "6" nom = "6"
enum-iterator = "0.7" enum-iterator = "0.7"
chrono = "0.4" chrono = { version = "0.4", features = ["serde"] }
thiserror = "1" thiserror = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
main_error = "0.1" main_error = "0.1"
paste = "1"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View file

@ -1,16 +1,22 @@
use main_error::MainError; use main_error::MainError;
use std::env::args; use std::env::args;
use std::fs; use std::fs;
use tf_log_parser::module::{ChatHandler, HandlerStack, LobbySettingsHandler, OptionalHandler}; use tf_log_parser::module::{ChatHandler, LobbySettingsHandler};
use tf_log_parser::parse_with_handler; use tf_log_parser::{handler, parse_with_handler};
type Handler = HandlerStack<ChatHandler, OptionalHandler<LobbySettingsHandler>>; handler!(Handler {
chat: ChatHandler,
lobby_settings: LobbySettingsHandler
});
fn main() -> Result<(), MainError> { fn main() -> Result<(), MainError> {
let path = args().skip(1).next().expect("No path provided"); let path = args().skip(1).next().expect("No path provided");
let content = fs::read_to_string(path)?; let content = fs::read_to_string(path)?;
let (chat, lobby_settings) = parse_with_handler::<Handler>(&content)?; let HandlerOutput {
chat,
lobby_settings,
} = parse_with_handler::<Handler>(&content)?;
if let Ok(Some(settings)) = lobby_settings { if let Ok(Some(settings)) = lobby_settings {
println!("Lobby settings: {:#?}", settings); println!("Lobby settings: {:#?}", settings);

View file

@ -1,13 +1,12 @@
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId}; pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
use crate::event::{GameEvent, GameEventError}; use crate::event::GameEventError;
use crate::module::{ pub use crate::module::EventHandler;
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, MedicStats, MedicStatsHandler, use crate::module::{ChatHandler, HealSpreadHandler, MedicStatsHandler};
};
use crate::raw_event::RawSubject; use crate::raw_event::RawSubject;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub use event::GameEvent;
pub use raw_event::{RawEvent, RawEventType}; pub use raw_event::{RawEvent, RawEventType};
use serde::Serialize; use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Index; use std::ops::Index;
@ -15,6 +14,7 @@ use thiserror::Error;
mod common; mod common;
mod event; mod event;
#[macro_use]
pub mod module; pub mod module;
mod raw_event; mod raw_event;
@ -62,7 +62,7 @@ impl SubjectMap {
} }
} }
pub fn parse(log: &str) -> Result<LogOutput, Error> { pub fn parse(log: &str) -> Result<<LogHandler as EventHandler>::Output, Error> {
parse_with_handler::<LogHandler>(log) parse_with_handler::<LogHandler>(log)
} }
@ -99,46 +99,8 @@ pub fn parse_with_handler<Handler: EventHandler>(log: &str) -> Result<Handler::O
Ok(handler.finish(&subjects)) Ok(handler.finish(&subjects))
} }
#[derive(Default)] handler!(LogHandler {
pub struct LogHandler {
chat: ChatHandler, chat: ChatHandler,
heal_spread: HealSpreadHandler, heal_spread: HealSpreadHandler,
medic_stats: MedicStatsHandler, medic_stats: MedicStatsHandler,
} });
#[derive(Default, Serialize)]
pub struct LogOutput {
chat: Vec<ChatMessage>,
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
medic_stats: HashMap<SteamId3, MedicStats>,
}
#[derive(Error, Debug)]
pub enum LogError {
#[error("{0}")]
MalformedEvent(#[from] GameEventError),
}
impl EventHandler for LogHandler {
type Output = LogOutput;
fn does_handle(&self, ty: RawEventType) -> bool {
self.chat.does_handle(ty)
|| self.heal_spread.does_handle(ty)
|| self.medic_stats.does_handle(ty)
}
fn handle(&mut self, time: u32, subject: SubjectId, event: &GameEvent) {
self.chat.handle(time, subject, event);
self.heal_spread.handle(time, subject, event);
self.medic_stats.handle(time, subject, event);
}
fn finish(self, subjects: &SubjectMap) -> Self::Output {
LogOutput {
chat: self.chat.finish(subjects),
heal_spread: self.heal_spread.finish(subjects),
medic_stats: self.medic_stats.finish(subjects),
}
}
}

View file

@ -4,12 +4,13 @@ use crate::module::EventHandler;
use crate::raw_event::RawEventType; use crate::raw_event::RawEventType;
use crate::SubjectMap; use crate::SubjectMap;
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc}; use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
use serde::{Serialize, Serializer};
use std::num::ParseIntError; use std::num::ParseIntError;
use std::str::{FromStr, ParseBoolError}; use std::str::{FromStr, ParseBoolError};
use steamid_ng::SteamID; use steamid_ng::SteamID;
use thiserror::Error; use thiserror::Error;
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum GameType { pub enum GameType {
Sixes, Sixes,
Highlander, Highlander,
@ -27,7 +28,7 @@ impl FromStr for GameType {
} }
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub enum Location { pub enum Location {
Europe, Europe,
NorthAmerica, NorthAmerica,
@ -45,7 +46,7 @@ impl FromStr for Location {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Serialize)]
pub struct LobbyLeader { pub struct LobbyLeader {
name: String, name: String,
steam_id: SteamID, steam_id: SteamID,
@ -70,7 +71,7 @@ impl FromStr for LobbyLeader {
} }
} }
#[derive(Debug)] #[derive(Debug, Serialize)]
pub struct Settings { pub struct Settings {
id: u32, id: u32,
leader: LobbyLeader, leader: LobbyLeader,
@ -127,6 +128,15 @@ pub enum LobbySettingsError {
InvalidDate(#[from] chrono::ParseError), InvalidDate(#[from] chrono::ParseError),
} }
impl Serialize for LobbySettingsError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
format!("{}", self).serialize(serializer)
}
}
pub enum LobbySettingsHandler { pub enum LobbySettingsHandler {
NotAvailable, NotAvailable,
Active(Settings), Active(Settings),

View file

@ -46,3 +46,59 @@ impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head,
(self.head.finish(subjects), self.tail.finish(subjects)) (self.head.finish(subjects), self.tail.finish(subjects))
} }
} }
#[macro_export]
macro_rules! handler {
($name:ident {$($child:ident: $ty:path),*}) => {
handler!($name { $($child: $ty,)* } );
};
($name:ident {$($child:ident: $ty:path,)*}) => {
paste::paste! {
#[derive(Default)]
pub struct $name {
$($child: $ty),*
}
pub struct [<$name Output>] {
$($child: <$ty as $crate::EventHandler>::Output),*
}
impl serde::Serialize for [<$name Output>] {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct(concat!("$name", "output"), 3)?;
$(state.serialize_field("$child", &self.$child)?;)*
state.end()
}
}
impl $crate::EventHandler for $name {
type Output = [<$name Output>];
fn does_handle(&self, ty: $crate::RawEventType) -> bool {
#[allow(unused_imports)]
use $crate::EventHandler;
$(self.$child.does_handle(ty))||*
}
fn handle(&mut self, time: u32, subject: $crate::SubjectId, event: &$crate::GameEvent) {
#[allow(unused_imports)]
use $crate::EventHandler;
$(self.$child.handle(time, subject, event);)*
}
fn finish(self, subjects: &$crate::SubjectMap) -> Self::Output {
#[allow(unused_imports)]
use $crate::EventHandler;
Self::Output {
$($child: self.$child.finish(subjects),)*
}
}
}
}
};
}