mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 10:14:10 +02:00
more consistant quote handling
This commit is contained in:
parent
c79166cd03
commit
acf70c03d4
11 changed files with 131 additions and 109 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tf_log_parser::{parse, GameEvent, LineSplit, RawEvent};
|
use tf_log_parser::{parse, EventHandler, GameEvent, LineSplit, LogHandler, RawEvent, SubjectMap};
|
||||||
|
|
||||||
pub fn parse_benchmark(c: &mut Criterion) {
|
pub fn parse_benchmark(c: &mut Criterion) {
|
||||||
let input = read_to_string("test_data/log_2892242.log").unwrap();
|
let input = read_to_string("test_data/log_2892242.log").unwrap();
|
||||||
|
|
@ -34,8 +35,32 @@ pub fn parse_raw(c: &mut Criterion) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(c: &mut Criterion) {
|
||||||
|
let input = read_to_string("test_data/log_2892242.log").unwrap();
|
||||||
|
let events: Vec<_> = input
|
||||||
|
.split("L ")
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.flat_map(RawEvent::parse)
|
||||||
|
.map(|raw| (GameEvent::parse(&raw).unwrap(), raw))
|
||||||
|
.collect();
|
||||||
|
c.bench_function("handle events 2892242", |b| {
|
||||||
|
let mut handler = LogHandler::default();
|
||||||
|
let mut subjects =
|
||||||
|
SubjectMap::<<LogHandler as EventHandler>::PerSubjectData>::with_capacity(32);
|
||||||
|
let mut start_time: Option<NaiveDateTime> = None;
|
||||||
|
b.iter(|| {
|
||||||
|
black_box(&events)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(event, raw_event)| {
|
||||||
|
black_box(handler.process(&raw_event, &event, &mut start_time, &mut subjects))
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
criterion_group!(
|
criterion_group!(
|
||||||
name = benches;
|
name = benches;
|
||||||
config = Criterion::default().measurement_time(Duration::from_secs(10));
|
config = Criterion::default().measurement_time(Duration::from_secs(10));
|
||||||
targets = parse_benchmark, parse_raw, parse_event);
|
targets = parse_benchmark, parse_raw, parse_event, handle_event);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,8 @@ impl Derivable for Event {
|
||||||
return err("optional fields can't be unnamed", ¶m.field_name);
|
return err("optional fields can't be unnamed", ¶m.field_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
let parser = if param.quoted {
|
|
||||||
quote_spanned!(field_name.span() => quoted(parse_field))
|
|
||||||
} else {
|
|
||||||
quote_spanned!(field_name.span() => parse_field)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(quote_spanned!(
|
Ok(quote_spanned!(
|
||||||
field_name.span() => #param_name => event.#field_name = #parser(value)?.1
|
field_name.span() => #param_name => event.#field_name = parse_field(value)?.1
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
@ -194,7 +188,8 @@ impl EventParam {
|
||||||
if optional && skip_after > 0 {
|
if optional && skip_after > 0 {
|
||||||
return err("skip_after can't be used with optional fields", input);
|
return err("skip_after can't be used with optional fields", input);
|
||||||
}
|
}
|
||||||
let quoted = contains_attribute(&input.attrs, &["event", "quoted"]);
|
let quoted =
|
||||||
|
get_attribute_value(&input.attrs, &["event", "quoted"]).unwrap_or(param_name.is_none());
|
||||||
|
|
||||||
Ok(EventParam {
|
Ok(EventParam {
|
||||||
span: input.span(),
|
span: input.span(),
|
||||||
|
|
@ -210,13 +205,16 @@ impl EventParam {
|
||||||
self.span
|
self.span
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parser(&self) -> TokenStream {
|
fn field_parser(&self) -> TokenStream {
|
||||||
let field_parser = if self.quoted {
|
if self.quoted {
|
||||||
quote_spanned!(self.span() => quoted(parse_field))
|
quote_spanned!(self.span() => quoted(parse_field))
|
||||||
} else {
|
} else {
|
||||||
quote_spanned!(self.span() => parse_field)
|
quote_spanned!(self.span() => parse_field)
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parser(&self) -> TokenStream {
|
||||||
|
let field_parser = self.field_parser();
|
||||||
if let Some(param_name) = &self.param_name {
|
if let Some(param_name) = &self.param_name {
|
||||||
quote_spanned!(self.span() => param_parse_with(#param_name, #field_parser))
|
quote_spanned!(self.span() => param_parse_with(#param_name, #field_parser))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ fn main() -> Result<(), MainError> {
|
||||||
|
|
||||||
let events: Vec<_> = LineSplit::new(&input)
|
let events: Vec<_> = LineSplit::new(&input)
|
||||||
.flat_map(RawEvent::parse)
|
.flat_map(RawEvent::parse)
|
||||||
.flat_map(|raw| GameEvent::parse(&raw))
|
.map(|raw| GameEvent::parse(&raw).unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
println!("{} events parsed", events.len());
|
println!("{} events parsed", events.len());
|
||||||
|
|
|
||||||
|
|
@ -362,8 +362,10 @@ pub fn skip_matches(input: &str, char: u8) -> (&str, bool) {
|
||||||
|
|
||||||
pub fn find_between_end(input: &str, start: u8, end: u8) -> Option<&str> {
|
pub fn find_between_end(input: &str, start: u8, end: u8) -> Option<&str> {
|
||||||
debug_assert!(start < 128 && end < 128); // only basic ascii
|
debug_assert!(start < 128 && end < 128); // only basic ascii
|
||||||
let end = memrchr(end, input.as_bytes())?;
|
let bytes = input.as_bytes();
|
||||||
let start = memrchr(start, &input.as_bytes()[0..end])?;
|
let end = memrchr(end, bytes)?;
|
||||||
|
// safety, memchr returns indices that are inside the input
|
||||||
|
let start = memrchr(start, unsafe { &bytes.get_unchecked(0..end) })?;
|
||||||
// safety, memchr returns indices that are inside the input length and we only split on ascii
|
// safety, memchr returns indices that are inside the input length and we only split on ascii
|
||||||
Some(unsafe { input.get_unchecked((start + 1)..end) })
|
Some(unsafe { input.get_unchecked((start + 1)..end) })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,6 @@ impl<'a> Event<'a> for PointCapturedEvent<'a> {
|
||||||
|
|
||||||
#[derive(Debug, Event)]
|
#[derive(Debug, Event)]
|
||||||
pub struct CurrentScoreEvent {
|
pub struct CurrentScoreEvent {
|
||||||
#[event(unnamed)]
|
|
||||||
pub score: u8,
|
pub score: u8,
|
||||||
#[event(name = "with")]
|
#[event(name = "with")]
|
||||||
pub players: u8,
|
pub players: u8,
|
||||||
|
|
@ -121,7 +120,6 @@ pub struct GameOverEvent<'a> {
|
||||||
|
|
||||||
#[derive(Debug, Event)]
|
#[derive(Debug, Event)]
|
||||||
pub struct FinalScoreEvent {
|
pub struct FinalScoreEvent {
|
||||||
#[event(unnamed)]
|
|
||||||
pub score: u8,
|
pub score: u8,
|
||||||
#[event(name = "with")]
|
#[event(name = "with")]
|
||||||
pub players: u8,
|
pub players: u8,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::event::{param_parse_with, parse_field, quoted, ParamIter};
|
use crate::event::{param_parse_with, parse_field, ParamIter};
|
||||||
use crate::raw_event::RawSubject;
|
use crate::raw_event::RawSubject;
|
||||||
use crate::{Event, IResult};
|
use crate::{Event, IResult};
|
||||||
|
|
||||||
|
|
@ -7,7 +7,6 @@ pub struct HealedEvent<'a> {
|
||||||
#[event(name = "against")]
|
#[event(name = "against")]
|
||||||
pub target: RawSubject<'a>,
|
pub target: RawSubject<'a>,
|
||||||
#[event(name = "healing")]
|
#[event(name = "healing")]
|
||||||
#[event(quoted)]
|
|
||||||
pub amount: u32,
|
pub amount: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
116
src/event/mod.rs
116
src/event/mod.rs
|
|
@ -16,20 +16,25 @@ use std::str::FromStr;
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum GameEventError {
|
pub enum GameEventError {
|
||||||
#[error("malformed game event({ty:?}): {err}")]
|
#[error("malformed game event({ty:?}): {err}")]
|
||||||
Error { err: Box<Error>, ty: RawEventType },
|
Error {
|
||||||
|
err: Box<Error>,
|
||||||
|
ty: RawEventType,
|
||||||
|
params: String,
|
||||||
|
},
|
||||||
#[error("incomplete event body({0:?})")]
|
#[error("incomplete event body({0:?})")]
|
||||||
Incomplete(RawEventType),
|
Incomplete(RawEventType),
|
||||||
}
|
}
|
||||||
|
|
||||||
trait GameEventErrTrait<T> {
|
trait GameEventErrTrait<T> {
|
||||||
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError>;
|
fn with_raw(self, raw: &RawEvent) -> Result<T, GameEventError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> GameEventErrTrait<T> for IResult<'a, T> {
|
impl<'a, T> GameEventErrTrait<T> for IResult<'a, T> {
|
||||||
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError> {
|
fn with_raw(self, raw: &RawEvent) -> Result<T, GameEventError> {
|
||||||
self.map_err(|err| GameEventError::Error {
|
self.map_err(|err| GameEventError::Error {
|
||||||
err: Box::new(err),
|
err: Box::new(err),
|
||||||
ty,
|
ty: raw.ty,
|
||||||
|
params: raw.params.to_string(),
|
||||||
})
|
})
|
||||||
.map(|(_rest, t)| t)
|
.map(|(_rest, t)| t)
|
||||||
}
|
}
|
||||||
|
|
@ -99,100 +104,90 @@ pub enum GameEvent<'a> {
|
||||||
impl<'a> GameEvent<'a> {
|
impl<'a> GameEvent<'a> {
|
||||||
pub fn parse(raw: &RawEvent<'a>) -> Result<GameEvent<'a>, GameEventError> {
|
pub fn parse(raw: &RawEvent<'a>) -> Result<GameEvent<'a>, GameEventError> {
|
||||||
Ok(match raw.ty {
|
Ok(match raw.ty {
|
||||||
RawEventType::ShotFired => {
|
RawEventType::ShotFired => GameEvent::ShotFired(parse_event(raw.params).with_raw(raw)?),
|
||||||
GameEvent::ShotFired(parse_event(raw.params).with_type(raw.ty)?)
|
RawEventType::ShotHit => GameEvent::ShotHit(parse_event(raw.params).with_raw(raw)?),
|
||||||
}
|
RawEventType::Damage => GameEvent::Damage(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::ShotHit => GameEvent::ShotHit(parse_event(raw.params).with_type(raw.ty)?),
|
RawEventType::Killed => GameEvent::Kill(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::Damage => GameEvent::Damage(parse_event(raw.params).with_type(raw.ty)?),
|
|
||||||
RawEventType::Killed => GameEvent::Kill(parse_event(raw.params).with_type(raw.ty)?),
|
|
||||||
RawEventType::KillAssist => {
|
RawEventType::KillAssist => {
|
||||||
GameEvent::KillAssist(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::KillAssist(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::SayTeam => GameEvent::SayTeam(raw.params.trim_matches('"')),
|
RawEventType::SayTeam => GameEvent::SayTeam(raw.params.trim_matches('"')),
|
||||||
RawEventType::Say => GameEvent::Say(raw.params.trim_matches('"')),
|
RawEventType::Say => GameEvent::Say(raw.params.trim_matches('"')),
|
||||||
RawEventType::Healed => GameEvent::Healed(parse_event(raw.params).with_type(raw.ty)?),
|
RawEventType::Healed => GameEvent::Healed(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::ChargeDeployed => {
|
RawEventType::ChargeDeployed => {
|
||||||
GameEvent::ChargeDeployed(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::ChargeDeployed(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::ChargeEnd => {
|
RawEventType::ChargeEnd => {
|
||||||
GameEvent::ChargeEnded(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::ChargeEnded(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::UberAdvantageLost => {
|
RawEventType::UberAdvantageLost => {
|
||||||
GameEvent::AdvantageLost(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::AdvantageLost(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::FirstHealAfterSpawn => {
|
RawEventType::FirstHealAfterSpawn => {
|
||||||
GameEvent::FirstHeal(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::FirstHeal(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::ChargeReady => GameEvent::ChargeReady,
|
RawEventType::ChargeReady => GameEvent::ChargeReady,
|
||||||
RawEventType::MedicDeath => {
|
RawEventType::MedicDeath => {
|
||||||
GameEvent::MedicDeath(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::MedicDeath(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::MedicDeathEx => {
|
RawEventType::MedicDeathEx => {
|
||||||
GameEvent::MedicDeathEx(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::MedicDeathEx(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::Spawned => GameEvent::Spawned(parse_event(raw.params).with_type(raw.ty)?),
|
RawEventType::Spawned => GameEvent::Spawned(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::ChangedRole => {
|
RawEventType::ChangedRole => {
|
||||||
GameEvent::RoleChange(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::RoleChange(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::RoundStart => GameEvent::RoundStart,
|
RawEventType::RoundStart => GameEvent::RoundStart,
|
||||||
RawEventType::RoundLength => {
|
RawEventType::RoundLength => {
|
||||||
GameEvent::RoundLength(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::RoundLength(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
|
||||||
RawEventType::RoundWin => {
|
|
||||||
GameEvent::RoundWin(parse_event(raw.params).with_type(raw.ty)?)
|
|
||||||
}
|
}
|
||||||
|
RawEventType::RoundWin => GameEvent::RoundWin(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::RoundOvertime => GameEvent::RoundOverTime,
|
RawEventType::RoundOvertime => GameEvent::RoundOverTime,
|
||||||
RawEventType::LogFileStarted => {
|
RawEventType::LogFileStarted => {
|
||||||
GameEvent::LogFileStarted(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::LogFileStarted(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
|
||||||
RawEventType::Connected => {
|
|
||||||
GameEvent::Connected(parse_event(raw.params).with_type(raw.ty)?)
|
|
||||||
}
|
}
|
||||||
|
RawEventType::Connected => GameEvent::Connected(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::Disconnected => {
|
RawEventType::Disconnected => {
|
||||||
GameEvent::Disconnect(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::Disconnect(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::SteamIdValidated => GameEvent::SteamIdValidated,
|
RawEventType::SteamIdValidated => GameEvent::SteamIdValidated,
|
||||||
RawEventType::Entered => GameEvent::Entered,
|
RawEventType::Entered => GameEvent::Entered,
|
||||||
RawEventType::Joined => GameEvent::Joined(parse_event(raw.params).with_type(raw.ty)?),
|
RawEventType::Joined => GameEvent::Joined(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::Suicide => GameEvent::Suicide(parse_event(raw.params).with_type(raw.ty)?),
|
RawEventType::Suicide => GameEvent::Suicide(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::PickedUp => {
|
RawEventType::PickedUp => GameEvent::PickedUp(parse_event(raw.params).with_raw(raw)?),
|
||||||
GameEvent::PickedUp(parse_event(raw.params).with_type(raw.ty)?)
|
|
||||||
}
|
|
||||||
RawEventType::Domination => {
|
RawEventType::Domination => {
|
||||||
GameEvent::Domination(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::Domination(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::EmptyUber => GameEvent::EmptyUber,
|
RawEventType::EmptyUber => GameEvent::EmptyUber,
|
||||||
RawEventType::Revenge => GameEvent::Revenge(parse_event(raw.params).with_type(raw.ty)?),
|
RawEventType::Revenge => GameEvent::Revenge(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::TournamentStart => {
|
RawEventType::TournamentStart => {
|
||||||
GameEvent::TournamentModeStarted(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::TournamentModeStarted(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::CaptureBlocked => {
|
RawEventType::CaptureBlocked => {
|
||||||
GameEvent::CaptureBlocked(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::CaptureBlocked(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::PointCaptured => {
|
RawEventType::PointCaptured => {
|
||||||
GameEvent::PointCaptured(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::PointCaptured(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::CurrentScore => {
|
RawEventType::CurrentScore => {
|
||||||
GameEvent::CurrentScore(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::CurrentScore(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::PlayerBuiltObject => {
|
RawEventType::PlayerBuiltObject => {
|
||||||
GameEvent::BuiltObject(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::BuiltObject(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::PlayerKilledObject => {
|
RawEventType::PlayerKilledObject => {
|
||||||
GameEvent::KilledObject(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::KilledObject(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::PlayerExtinguished => {
|
RawEventType::PlayerExtinguished => {
|
||||||
GameEvent::Extinguished(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::Extinguished(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
|
||||||
RawEventType::GameOver => {
|
|
||||||
GameEvent::GameOver(parse_event(raw.params).with_type(raw.ty)?)
|
|
||||||
}
|
}
|
||||||
|
RawEventType::GameOver => GameEvent::GameOver(parse_event(raw.params).with_raw(raw)?),
|
||||||
RawEventType::FinalScore => {
|
RawEventType::FinalScore => {
|
||||||
GameEvent::FinalScore(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::FinalScore(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
RawEventType::LogFileClosed => GameEvent::LogFileClosed,
|
RawEventType::LogFileClosed => GameEvent::LogFileClosed,
|
||||||
RawEventType::ObjectDetonated => {
|
RawEventType::ObjectDetonated => {
|
||||||
GameEvent::ObjectDetonated(parse_event(raw.params).with_type(raw.ty)?)
|
GameEvent::ObjectDetonated(parse_event(raw.params).with_raw(raw)?)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
todo!("{:?} not parsed yet", raw.ty);
|
todo!("{:?} not parsed yet", raw.ty);
|
||||||
|
|
@ -224,8 +219,7 @@ impl<'a> Iterator for ParamIter<'a> {
|
||||||
fn param_pair_parse(input: &str) -> IResult<'_, (&str, &str)> {
|
fn param_pair_parse(input: &str) -> IResult<'_, (&str, &str)> {
|
||||||
let (input, open_tag) = skip_matches(input, b'(');
|
let (input, open_tag) = skip_matches(input, b'(');
|
||||||
|
|
||||||
let (key, input) = split_once(input, b' ', 0)?;
|
let (key, input) = split_once(input, b' ', 2)?;
|
||||||
let input = skip(input, 2)?;
|
|
||||||
let (value, input) = split_once(input, b'"', 1)?;
|
let (value, input) = split_once(input, b'"', 1)?;
|
||||||
|
|
||||||
let input = if open_tag { skip(input, 1)? } else { input };
|
let input = if open_tag { skip(input, 1)? } else { input };
|
||||||
|
|
@ -252,22 +246,16 @@ pub fn param_parse_with<'a, T, P: Fn(&'a str) -> IResult<'a, T>>(
|
||||||
parser: P,
|
parser: P,
|
||||||
) -> impl Fn(&'a str) -> IResult<'a, T> {
|
) -> impl Fn(&'a str) -> IResult<'a, T> {
|
||||||
move |input: &str| {
|
move |input: &str| {
|
||||||
debug_assert!(input.as_bytes()[0] != b' ');
|
|
||||||
|
|
||||||
let (input, has_open) = skip_matches(input, b'(');
|
let (input, has_open) = skip_matches(input, b'(');
|
||||||
|
|
||||||
let input = skip(input, key.len() + 1)?; // skip space + key
|
let input = skip(input, key.len() + 2)?; // skip space + key + quote
|
||||||
|
|
||||||
let (input, value) = parser(input)?;
|
let (value, input) = split_once(input, b'"', 1)?;
|
||||||
|
|
||||||
|
let (_, value) = parser(value)?;
|
||||||
|
|
||||||
let input = skip(input, has_open as usize)?;
|
let input = skip(input, has_open as usize)?;
|
||||||
|
|
||||||
debug_assert!(
|
|
||||||
input.is_empty() || input.as_bytes()[0] == b' ',
|
|
||||||
"\"{}\" starts with space",
|
|
||||||
input
|
|
||||||
);
|
|
||||||
|
|
||||||
let input = skip(input, 1).unwrap_or(input);
|
let input = skip(input, 1).unwrap_or(input);
|
||||||
Ok((input, value))
|
Ok((input, value))
|
||||||
}
|
}
|
||||||
|
|
@ -299,15 +287,9 @@ pub trait EventField<'a>: Sized + 'a {
|
||||||
|
|
||||||
impl<'a> EventField<'a> for &'a str {
|
impl<'a> EventField<'a> for &'a str {
|
||||||
fn parse_field(input: &'a str) -> IResult<Self> {
|
fn parse_field(input: &'a str) -> IResult<Self> {
|
||||||
let (input, quoted) = skip_matches(input, b'"');
|
|
||||||
if quoted {
|
|
||||||
let (value, input) = split_once(input, b'"', 1)?;
|
|
||||||
Ok((input, value))
|
|
||||||
} else {
|
|
||||||
Ok(("", input))
|
Ok(("", input))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> EventField<'a> for i32 {
|
impl<'a> EventField<'a> for i32 {
|
||||||
fn parse_field(input: &'a str) -> IResult<Self> {
|
fn parse_field(input: &'a str) -> IResult<Self> {
|
||||||
|
|
@ -369,7 +351,7 @@ impl<'a> EventField<'a> for Option<NonZeroU32> {
|
||||||
|
|
||||||
impl<'a> EventField<'a> for RawSubject<'a> {
|
impl<'a> EventField<'a> for RawSubject<'a> {
|
||||||
fn parse_field(input: &'a str) -> IResult<Self> {
|
fn parse_field(input: &'a str) -> IResult<Self> {
|
||||||
against_subject_parser(input)
|
Ok(("", against_subject_parser(input)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::common::{Class, Team};
|
use crate::common::{Class, Team};
|
||||||
use crate::event::{param_parse_with, parse_field, ParamIter};
|
use crate::event::{param_parse_with, parse_field, quoted, ParamIter};
|
||||||
use crate::raw_event::RawSubject;
|
use crate::raw_event::RawSubject;
|
||||||
use crate::{Error, Event, IResult};
|
use crate::{Error, Event, IResult};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
@ -38,6 +38,7 @@ pub struct KillEvent<'a> {
|
||||||
|
|
||||||
#[derive(Debug, Event)]
|
#[derive(Debug, Event)]
|
||||||
pub struct KillAssistEvent<'a> {
|
pub struct KillAssistEvent<'a> {
|
||||||
|
#[event(name = "against")]
|
||||||
pub target: RawSubject<'a>,
|
pub target: RawSubject<'a>,
|
||||||
pub attacker_position: Option<(i32, i32, i32)>,
|
pub attacker_position: Option<(i32, i32, i32)>,
|
||||||
pub victim_position: Option<(i32, i32, i32)>,
|
pub victim_position: Option<(i32, i32, i32)>,
|
||||||
|
|
@ -57,7 +58,7 @@ pub struct RoleChangeEvent {
|
||||||
|
|
||||||
#[derive(Debug, Event)]
|
#[derive(Debug, Event)]
|
||||||
pub struct ConnectedEvent {
|
pub struct ConnectedEvent {
|
||||||
#[event(name = "to")]
|
#[event(name = "address")]
|
||||||
pub address: SocketAddr,
|
pub address: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
16
src/lib.rs
16
src/lib.rs
|
|
@ -10,7 +10,6 @@ pub use event::{Event, EventMeta, GameEvent};
|
||||||
use memchr::memmem::{find_iter, FindIter};
|
use memchr::memmem::{find_iter, FindIter};
|
||||||
pub use raw_event::{RawEvent, RawEventType};
|
pub use raw_event::{RawEvent, RawEventType};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
pub use tf_log_parser_derive::Event;
|
pub use tf_log_parser_derive::Event;
|
||||||
|
|
@ -78,22 +77,9 @@ pub fn parse_with_handler<Handler: EventHandler>(
|
||||||
let raw_event = event_res?;
|
let raw_event = event_res?;
|
||||||
let should_handle = Handler::does_handle(raw_event.ty);
|
let should_handle = Handler::does_handle(raw_event.ty);
|
||||||
if should_handle || start_time.is_none() {
|
if should_handle || start_time.is_none() {
|
||||||
let event_time: NaiveDateTime = raw_event.date.try_into().unwrap();
|
|
||||||
let match_time = match start_time {
|
|
||||||
Some(start_time) => (event_time - start_time).num_seconds() as u32,
|
|
||||||
None => {
|
|
||||||
start_time = Some(event_time);
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if should_handle {
|
if should_handle {
|
||||||
let event = GameEvent::parse(&raw_event)?;
|
let event = GameEvent::parse(&raw_event)?;
|
||||||
let (subject, data) = subjects.insert(&raw_event.subject)?;
|
handler.process(&raw_event, &event, &mut start_time, &mut subjects)?;
|
||||||
let meta = EventMeta {
|
|
||||||
time: match_time,
|
|
||||||
subject,
|
|
||||||
};
|
|
||||||
handler.handle(&meta, subject, data, &event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::common::SubjectId;
|
use crate::common::SubjectId;
|
||||||
use crate::event::{EventMeta, GameEvent};
|
use crate::event::{EventMeta, GameEvent};
|
||||||
use crate::raw_event::RawEventType;
|
use crate::raw_event::RawEventType;
|
||||||
use crate::{SubjectData, SubjectMap};
|
use crate::{Error, RawEvent, SubjectData, SubjectMap};
|
||||||
pub use chat::{ChatMessage, ChatMessages, ChatType};
|
pub use chat::{ChatMessage, ChatMessages, ChatType};
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
pub use classstats::{ClassStats, ClassStatsHandler};
|
pub use classstats::{ClassStats, ClassStatsHandler};
|
||||||
pub use healspread::HealSpread;
|
pub use healspread::HealSpread;
|
||||||
pub use lobbysettings::{
|
pub use lobbysettings::{
|
||||||
|
|
@ -10,6 +11,7 @@ pub use lobbysettings::{
|
||||||
};
|
};
|
||||||
pub use medicstats::{MedicStats, MedicStatsBuilder};
|
pub use medicstats::{MedicStats, MedicStatsBuilder};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
|
|
@ -25,6 +27,30 @@ pub trait EventHandler: Default {
|
||||||
|
|
||||||
fn does_handle(ty: RawEventType) -> bool;
|
fn does_handle(ty: RawEventType) -> bool;
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
raw_event: &RawEvent,
|
||||||
|
event: &GameEvent,
|
||||||
|
start_time: &mut Option<NaiveDateTime>,
|
||||||
|
subjects: &mut SubjectMap<Self::PerSubjectData>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let event_time: NaiveDateTime = raw_event.date.try_into().unwrap();
|
||||||
|
let match_time = match start_time {
|
||||||
|
Some(start_time) => (event_time - *start_time).num_seconds() as u32,
|
||||||
|
None => {
|
||||||
|
*start_time = Some(event_time);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (subject, data) = subjects.insert(&raw_event.subject)?;
|
||||||
|
let meta = EventMeta {
|
||||||
|
time: match_time,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
self.handle(&meta, subject, data, &event);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn handle(
|
fn handle(
|
||||||
&mut self,
|
&mut self,
|
||||||
meta: &EventMeta,
|
meta: &EventMeta,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::common::{split_once, Team};
|
use crate::common::{split_once, Team};
|
||||||
use crate::{Error, IResult, Result};
|
use crate::{Error, Result};
|
||||||
use crate::{SubjectError, SubjectId};
|
use crate::{SubjectError, SubjectId};
|
||||||
use chrono::{NaiveDate, NaiveDateTime};
|
use chrono::{NaiveDate, NaiveDateTime};
|
||||||
use logos::{Lexer, Logos};
|
use logos::{Lexer, Logos};
|
||||||
|
|
@ -109,8 +109,13 @@ fn test_split_player_subject() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn against_subject_parser(input: &str) -> IResult<RawSubject> {
|
pub fn against_subject_parser(input: &str) -> Result<RawSubject> {
|
||||||
subject_parser(input).map_err(|_| Error::Incomplete)
|
// "against" fields are always players, and unquoted
|
||||||
|
if input.ends_with("e>") {
|
||||||
|
Ok(RawSubject::Console)
|
||||||
|
} else {
|
||||||
|
Ok(RawSubject::Player(input))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subject_parser(input: &str) -> Result<(&str, RawSubject)> {
|
pub fn subject_parser(input: &str) -> Result<(&str, RawSubject)> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue