mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 18:24:09 +02:00
more consistant quote handling
This commit is contained in:
parent
c79166cd03
commit
acf70c03d4
11 changed files with 131 additions and 109 deletions
|
|
@ -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> {
|
||||
debug_assert!(start < 128 && end < 128); // only basic ascii
|
||||
let end = memrchr(end, input.as_bytes())?;
|
||||
let start = memrchr(start, &input.as_bytes()[0..end])?;
|
||||
let bytes = input.as_bytes();
|
||||
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
|
||||
Some(unsafe { input.get_unchecked((start + 1)..end) })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ impl<'a> Event<'a> for PointCapturedEvent<'a> {
|
|||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct CurrentScoreEvent {
|
||||
#[event(unnamed)]
|
||||
pub score: u8,
|
||||
#[event(name = "with")]
|
||||
pub players: u8,
|
||||
|
|
@ -121,7 +120,6 @@ pub struct GameOverEvent<'a> {
|
|||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct FinalScoreEvent {
|
||||
#[event(unnamed)]
|
||||
pub score: u8,
|
||||
#[event(name = "with")]
|
||||
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::{Event, IResult};
|
||||
|
||||
|
|
@ -7,7 +7,6 @@ pub struct HealedEvent<'a> {
|
|||
#[event(name = "against")]
|
||||
pub target: RawSubject<'a>,
|
||||
#[event(name = "healing")]
|
||||
#[event(quoted)]
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
|
|
|
|||
118
src/event/mod.rs
118
src/event/mod.rs
|
|
@ -16,20 +16,25 @@ use std::str::FromStr;
|
|||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GameEventError {
|
||||
#[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:?})")]
|
||||
Incomplete(RawEventType),
|
||||
}
|
||||
|
||||
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> {
|
||||
fn with_type(self, ty: RawEventType) -> Result<T, GameEventError> {
|
||||
fn with_raw(self, raw: &RawEvent) -> Result<T, GameEventError> {
|
||||
self.map_err(|err| GameEventError::Error {
|
||||
err: Box::new(err),
|
||||
ty,
|
||||
ty: raw.ty,
|
||||
params: raw.params.to_string(),
|
||||
})
|
||||
.map(|(_rest, t)| t)
|
||||
}
|
||||
|
|
@ -99,100 +104,90 @@ pub enum GameEvent<'a> {
|
|||
impl<'a> GameEvent<'a> {
|
||||
pub fn parse(raw: &RawEvent<'a>) -> Result<GameEvent<'a>, GameEventError> {
|
||||
Ok(match raw.ty {
|
||||
RawEventType::ShotFired => {
|
||||
GameEvent::ShotFired(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::ShotHit => GameEvent::ShotHit(parse_event(raw.params).with_type(raw.ty)?),
|
||||
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::ShotFired => GameEvent::ShotFired(parse_event(raw.params).with_raw(raw)?),
|
||||
RawEventType::ShotHit => GameEvent::ShotHit(parse_event(raw.params).with_raw(raw)?),
|
||||
RawEventType::Damage => GameEvent::Damage(parse_event(raw.params).with_raw(raw)?),
|
||||
RawEventType::Killed => GameEvent::Kill(parse_event(raw.params).with_raw(raw)?),
|
||||
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::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 => {
|
||||
GameEvent::ChargeDeployed(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::ChargeDeployed(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::ChargeEnd => {
|
||||
GameEvent::ChargeEnded(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::ChargeEnded(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::UberAdvantageLost => {
|
||||
GameEvent::AdvantageLost(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::AdvantageLost(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
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::MedicDeath => {
|
||||
GameEvent::MedicDeath(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::MedicDeath(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
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 => {
|
||||
GameEvent::RoleChange(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::RoleChange(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::RoundStart => GameEvent::RoundStart,
|
||||
RawEventType::RoundLength => {
|
||||
GameEvent::RoundLength(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::RoundWin => {
|
||||
GameEvent::RoundWin(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_raw(raw)?),
|
||||
RawEventType::RoundOvertime => GameEvent::RoundOverTime,
|
||||
RawEventType::LogFileStarted => {
|
||||
GameEvent::LogFileStarted(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::Connected => {
|
||||
GameEvent::Connected(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_raw(raw)?),
|
||||
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::Entered => GameEvent::Entered,
|
||||
RawEventType::Joined => GameEvent::Joined(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::Suicide => GameEvent::Suicide(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::PickedUp => {
|
||||
GameEvent::PickedUp(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_raw(raw)?),
|
||||
RawEventType::PickedUp => GameEvent::PickedUp(parse_event(raw.params).with_raw(raw)?),
|
||||
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::Revenge => GameEvent::Revenge(parse_event(raw.params).with_type(raw.ty)?),
|
||||
RawEventType::Revenge => GameEvent::Revenge(parse_event(raw.params).with_raw(raw)?),
|
||||
RawEventType::TournamentStart => {
|
||||
GameEvent::TournamentModeStarted(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::TournamentModeStarted(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::CaptureBlocked => {
|
||||
GameEvent::CaptureBlocked(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::CaptureBlocked(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::PointCaptured => {
|
||||
GameEvent::PointCaptured(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::PointCaptured(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::CurrentScore => {
|
||||
GameEvent::CurrentScore(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::CurrentScore(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::PlayerBuiltObject => {
|
||||
GameEvent::BuiltObject(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::BuiltObject(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::PlayerKilledObject => {
|
||||
GameEvent::KilledObject(parse_event(raw.params).with_type(raw.ty)?)
|
||||
GameEvent::KilledObject(parse_event(raw.params).with_raw(raw)?)
|
||||
}
|
||||
RawEventType::PlayerExtinguished => {
|
||||
GameEvent::Extinguished(parse_event(raw.params).with_type(raw.ty)?)
|
||||
}
|
||||
RawEventType::GameOver => {
|
||||
GameEvent::GameOver(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_raw(raw)?),
|
||||
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::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);
|
||||
|
|
@ -224,8 +219,7 @@ impl<'a> Iterator for ParamIter<'a> {
|
|||
fn param_pair_parse(input: &str) -> IResult<'_, (&str, &str)> {
|
||||
let (input, open_tag) = skip_matches(input, b'(');
|
||||
|
||||
let (key, input) = split_once(input, b' ', 0)?;
|
||||
let input = skip(input, 2)?;
|
||||
let (key, input) = split_once(input, b' ', 2)?;
|
||||
let (value, input) = split_once(input, b'"', 1)?;
|
||||
|
||||
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,
|
||||
) -> impl Fn(&'a str) -> IResult<'a, T> {
|
||||
move |input: &str| {
|
||||
debug_assert!(input.as_bytes()[0] != 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)?;
|
||||
|
||||
debug_assert!(
|
||||
input.is_empty() || input.as_bytes()[0] == b' ',
|
||||
"\"{}\" starts with space",
|
||||
input
|
||||
);
|
||||
|
||||
let input = skip(input, 1).unwrap_or(input);
|
||||
Ok((input, value))
|
||||
}
|
||||
|
|
@ -299,13 +287,7 @@ pub trait EventField<'a>: Sized + 'a {
|
|||
|
||||
impl<'a> EventField<'a> for &'a str {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +351,7 @@ impl<'a> EventField<'a> for Option<NonZeroU32> {
|
|||
|
||||
impl<'a> EventField<'a> for RawSubject<'a> {
|
||||
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::event::{param_parse_with, parse_field, ParamIter};
|
||||
use crate::event::{param_parse_with, parse_field, quoted, ParamIter};
|
||||
use crate::raw_event::RawSubject;
|
||||
use crate::{Error, Event, IResult};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -38,6 +38,7 @@ pub struct KillEvent<'a> {
|
|||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct KillAssistEvent<'a> {
|
||||
#[event(name = "against")]
|
||||
pub target: RawSubject<'a>,
|
||||
pub attacker_position: Option<(i32, i32, i32)>,
|
||||
pub victim_position: Option<(i32, i32, i32)>,
|
||||
|
|
@ -57,7 +58,7 @@ pub struct RoleChangeEvent {
|
|||
|
||||
#[derive(Debug, Event)]
|
||||
pub struct ConnectedEvent {
|
||||
#[event(name = "to")]
|
||||
#[event(name = "address")]
|
||||
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};
|
||||
pub use raw_event::{RawEvent, RawEventType};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
use std::num::ParseIntError;
|
||||
pub use tf_log_parser_derive::Event;
|
||||
|
|
@ -78,22 +77,9 @@ pub fn parse_with_handler<Handler: EventHandler>(
|
|||
let raw_event = event_res?;
|
||||
let should_handle = Handler::does_handle(raw_event.ty);
|
||||
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 {
|
||||
let event = GameEvent::parse(&raw_event)?;
|
||||
let (subject, data) = subjects.insert(&raw_event.subject)?;
|
||||
let meta = EventMeta {
|
||||
time: match_time,
|
||||
subject,
|
||||
};
|
||||
handler.handle(&meta, subject, data, &event);
|
||||
handler.process(&raw_event, &event, &mut start_time, &mut subjects)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use crate::common::SubjectId;
|
||||
use crate::event::{EventMeta, GameEvent};
|
||||
use crate::raw_event::RawEventType;
|
||||
use crate::{SubjectData, SubjectMap};
|
||||
use crate::{Error, RawEvent, SubjectData, SubjectMap};
|
||||
pub use chat::{ChatMessage, ChatMessages, ChatType};
|
||||
use chrono::NaiveDateTime;
|
||||
pub use classstats::{ClassStats, ClassStatsHandler};
|
||||
pub use healspread::HealSpread;
|
||||
pub use lobbysettings::{
|
||||
|
|
@ -10,6 +11,7 @@ pub use lobbysettings::{
|
|||
};
|
||||
pub use medicstats::{MedicStats, MedicStatsBuilder};
|
||||
use serde::Serialize;
|
||||
use std::convert::TryInto;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
mod chat;
|
||||
|
|
@ -25,6 +27,30 @@ pub trait EventHandler: Default {
|
|||
|
||||
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(
|
||||
&mut self,
|
||||
meta: &EventMeta,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::common::{split_once, Team};
|
||||
use crate::{Error, IResult, Result};
|
||||
use crate::{Error, Result};
|
||||
use crate::{SubjectError, SubjectId};
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use logos::{Lexer, Logos};
|
||||
|
|
@ -109,8 +109,13 @@ fn test_split_player_subject() {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn against_subject_parser(input: &str) -> IResult<RawSubject> {
|
||||
subject_parser(input).map_err(|_| Error::Incomplete)
|
||||
pub fn against_subject_parser(input: &str) -> Result<RawSubject> {
|
||||
// "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)> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue