mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 18:24:09 +02:00
some event parsing
This commit is contained in:
parent
d1b3525b2a
commit
e83f7928aa
7 changed files with 282 additions and 61 deletions
|
|
@ -12,3 +12,10 @@ 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"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "bench"
|
||||||
|
harness = false
|
||||||
11
benches/bench.rs
Normal file
11
benches/bench.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
use tf_log_parser::parse;
|
||||||
|
|
||||||
|
pub fn parse_benchmark(c: &mut Criterion) {
|
||||||
|
let input = read_to_string("test_data/log_2892242.log").unwrap();
|
||||||
|
c.bench_function("parse 2892242", |b| b.iter(|| parse(black_box(&input))));
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, parse_benchmark);
|
||||||
|
criterion_main!(benches);
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
|
use crate::event::{param_parse_with, quoted, u_int};
|
||||||
use crate::raw_event::{subject_parser, RawSubject};
|
use crate::raw_event::{subject_parser, RawSubject};
|
||||||
use nom::bytes::complete::tag;
|
|
||||||
use nom::character::complete::digit1;
|
|
||||||
use nom::error::ErrorKind;
|
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -11,12 +9,7 @@ pub struct HealedEvent<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
|
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
|
||||||
let (input, _) = tag("against ")(input)?;
|
let (input, subject) = param_parse_with("against", subject_parser)(input)?;
|
||||||
let (input, subject) = subject_parser(input)?;
|
let (input, amount) = param_parse_with("healing", quoted(u_int))(input)?;
|
||||||
let (input, _) = tag(r#" (healing ""#)(input)?;
|
|
||||||
let (input, raw_amount) = digit1(input)?;
|
|
||||||
let amount = raw_amount
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw_amount, ErrorKind::Digit)))?;
|
|
||||||
Ok((input, HealedEvent { subject, amount }))
|
Ok((input, HealedEvent { subject, amount }))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
103
src/event/mod.rs
103
src/event/mod.rs
|
|
@ -1,3 +1,104 @@
|
||||||
mod medic;
|
mod medic;
|
||||||
|
mod player;
|
||||||
|
|
||||||
pub use medic::{healed_event_parser, HealedEvent};
|
pub use medic::*;
|
||||||
|
use nom::bytes::complete::{tag, take_while};
|
||||||
|
use nom::character::complete::{alpha1, digit1};
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::error::ErrorKind;
|
||||||
|
use nom::IResult;
|
||||||
|
pub use player::*;
|
||||||
|
|
||||||
|
struct ParamIter<'a> {
|
||||||
|
input: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ParamIter<'a> {
|
||||||
|
pub fn new(input: &'a str) -> Self {
|
||||||
|
ParamIter { input }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ParamIter<'a> {
|
||||||
|
type Item = (&'a str, &'a str);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let (input, res) = param_pair_parse(self.input).ok()?;
|
||||||
|
self.input = input;
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_pair_parse(input: &str) -> IResult<&str, (&str, &str)> {
|
||||||
|
let (input, open_tag) = opt(tag("("))(input)?;
|
||||||
|
|
||||||
|
let (input, key) = alpha1(input)?;
|
||||||
|
let (input, _) = tag(r#" ""#)(input)?;
|
||||||
|
let (input, value) = take_while(|c| c != '"')(input)?;
|
||||||
|
let (input, _) = tag(r#"""#)(input)?;
|
||||||
|
|
||||||
|
if open_tag.is_some() {
|
||||||
|
let (_input, _) = tag("(")(input)?;
|
||||||
|
}
|
||||||
|
Ok((input, (key, value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quoted<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
|
||||||
|
parser: P,
|
||||||
|
) -> impl Fn(&'a str) -> IResult<&'a str, T> {
|
||||||
|
move |input| {
|
||||||
|
let (input, _) = tag(r#"""#)(input)?;
|
||||||
|
let (input, res) = parser(input)?;
|
||||||
|
let (input, _) = tag(r#"""#)(input)?;
|
||||||
|
Ok((input, res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_parse<'a>(key: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str> {
|
||||||
|
param_parse_with(key, quoted(take_while(|c| c != '"')))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_parse_with<'a, T, P: Fn(&'a str) -> IResult<&'a str, T>>(
|
||||||
|
key: &'a str,
|
||||||
|
parser: P,
|
||||||
|
) -> impl Fn(&'a str) -> IResult<&'a str, T> {
|
||||||
|
move |input: &str| {
|
||||||
|
let (input, open_tag) = opt(tag("("))(input)?;
|
||||||
|
|
||||||
|
let (input, _) = tag(key)(input)?;
|
||||||
|
let (input, _) = tag(r#" "#)(input)?;
|
||||||
|
|
||||||
|
let (input, value) = parser(input)?;
|
||||||
|
|
||||||
|
if open_tag.is_some() {
|
||||||
|
let (_input, _) = tag(")")(input)?;
|
||||||
|
}
|
||||||
|
Ok((input.trim_start(), value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int(input: &str) -> IResult<&str, i32> {
|
||||||
|
let (input, sign) = opt(tag("-"))(input)?;
|
||||||
|
let (input, raw) = digit1(input)?;
|
||||||
|
let val: i32 = raw
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw, ErrorKind::Digit)))?;
|
||||||
|
Ok((input, if sign.is_some() { -val } else { val }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn u_int(input: &str) -> IResult<&str, u32> {
|
||||||
|
let (input, raw) = digit1(input)?;
|
||||||
|
let val = raw
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| nom::Err::Error(nom::error::Error::new(raw, ErrorKind::Digit)))?;
|
||||||
|
Ok((input, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position(input: &str) -> IResult<&str, (i32, i32, i32)> {
|
||||||
|
let (input, x) = int(input)?;
|
||||||
|
let (input, _) = tag(" ")(input)?;
|
||||||
|
let (input, y) = int(input)?;
|
||||||
|
let (input, _) = tag(" ")(input)?;
|
||||||
|
let (input, z) = int(input)?;
|
||||||
|
Ok((input, (x, y, z)))
|
||||||
|
}
|
||||||
|
|
|
||||||
103
src/event/player.rs
Normal file
103
src/event/player.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::event::{param_parse, param_parse_with, position, quoted, u_int, ParamIter};
|
||||||
|
use crate::raw_event::{subject_parser, RawSubject};
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::IResult;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShotFiredEvent<'a> {
|
||||||
|
pub weapon: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shot_fired_event_parser(input: &str) -> IResult<&str, ShotFiredEvent> {
|
||||||
|
let (input, weapon) = opt(param_parse("weapon"))(input)?;
|
||||||
|
Ok((input, ShotFiredEvent { weapon }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ShotHitEvent<'a> {
|
||||||
|
pub weapon: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shot_hit_event_parser(input: &str) -> IResult<&str, ShotFiredEvent> {
|
||||||
|
let (input, weapon) = opt(param_parse("weapon"))(input)?;
|
||||||
|
Ok((input, ShotFiredEvent { weapon }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DamageEvent<'a> {
|
||||||
|
pub target: RawSubject<'a>,
|
||||||
|
pub damage: Option<NonZeroU32>,
|
||||||
|
pub real_damage: Option<NonZeroU32>,
|
||||||
|
pub weapon: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn damage_event_parser(input: &str) -> IResult<&str, DamageEvent> {
|
||||||
|
let (input, target) = param_parse_with("against", subject_parser)(input)?;
|
||||||
|
let mut event = DamageEvent {
|
||||||
|
target,
|
||||||
|
damage: None,
|
||||||
|
real_damage: None,
|
||||||
|
weapon: None,
|
||||||
|
};
|
||||||
|
for (key, value) in ParamIter::new(input) {
|
||||||
|
match key {
|
||||||
|
"damage" => event.damage = NonZeroU32::new(quoted(u_int)(value)?.1),
|
||||||
|
"realdamage" => event.real_damage = NonZeroU32::new(quoted(u_int)(value)?.1),
|
||||||
|
"weapon" => event.weapon = Some(value.trim_matches('"')),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(("", event))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct KillEvent<'a> {
|
||||||
|
pub target: RawSubject<'a>,
|
||||||
|
pub weapon: &'a str,
|
||||||
|
pub attacker_position: Option<(i32, i32, i32)>,
|
||||||
|
pub victim_position: Option<(i32, i32, i32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_event_parser(input: &str) -> IResult<&str, KillEvent> {
|
||||||
|
let (input, target) = param_parse_with("against", subject_parser)(input)?;
|
||||||
|
let (input, weapon) = param_parse("with")(input)?;
|
||||||
|
let mut event = KillEvent {
|
||||||
|
target,
|
||||||
|
weapon,
|
||||||
|
attacker_position: None,
|
||||||
|
victim_position: None,
|
||||||
|
};
|
||||||
|
for (key, value) in ParamIter::new(input) {
|
||||||
|
match key {
|
||||||
|
"attacker_position" => event.attacker_position = Some(position(value)?.1),
|
||||||
|
"victim_position" => event.victim_position = Some(position(value)?.1),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(("", event))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct KillAssistEvent<'a> {
|
||||||
|
pub target: RawSubject<'a>,
|
||||||
|
pub attacker_position: Option<(i32, i32, i32)>,
|
||||||
|
pub victim_position: Option<(i32, i32, i32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_assist_event_parser(input: &str) -> IResult<&str, KillAssistEvent> {
|
||||||
|
let (input, target) = param_parse_with("against", subject_parser)(input)?;
|
||||||
|
let mut event = KillAssistEvent {
|
||||||
|
target,
|
||||||
|
attacker_position: None,
|
||||||
|
victim_position: None,
|
||||||
|
};
|
||||||
|
for (key, value) in ParamIter::new(input) {
|
||||||
|
match key {
|
||||||
|
"attacker_position" => event.attacker_position = Some(position(value)?.1),
|
||||||
|
"victim_position" => event.victim_position = Some(position(value)?.1),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(("", event))
|
||||||
|
}
|
||||||
54
src/lib.rs
54
src/lib.rs
|
|
@ -1,9 +1,10 @@
|
||||||
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
|
pub use crate::common::{SteamId3, SubjectData, SubjectError, SubjectId};
|
||||||
use crate::module::EventHandler;
|
use crate::module::{ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent};
|
||||||
use crate::raw_event::RawSubject;
|
use crate::raw_event::RawSubject;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
pub use raw_event::{RawEvent, RawEventType};
|
pub use raw_event::{RawEvent, RawEventType};
|
||||||
use std::collections::BTreeMap;
|
use serde::Serialize;
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
|
@ -66,6 +67,10 @@ impl SubjectMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse(log: &str) -> Result<LogOutput, Error<LogHandler>> {
|
||||||
|
parse_with_handler::<LogHandler>(log)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_with_handler<Handler: EventHandler>(
|
pub fn parse_with_handler<Handler: EventHandler>(
|
||||||
log: &str,
|
log: &str,
|
||||||
) -> Result<Handler::Output, Error<Handler>> {
|
) -> Result<Handler::Output, Error<Handler>> {
|
||||||
|
|
@ -98,3 +103,48 @@ pub fn parse_with_handler<Handler: EventHandler>(
|
||||||
|
|
||||||
Ok(handler.finish(&subjects))
|
Ok(handler.finish(&subjects))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LogHandler {
|
||||||
|
chat: ChatHandler,
|
||||||
|
heal_spread: HealSpreadHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize)]
|
||||||
|
pub struct LogOutput {
|
||||||
|
chat: Vec<ChatMessage>,
|
||||||
|
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum LogError {
|
||||||
|
#[error("{0}")]
|
||||||
|
HealSpread(#[from] InvalidHealEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventHandler for LogHandler {
|
||||||
|
type Output = LogOutput;
|
||||||
|
type Error = LogError;
|
||||||
|
|
||||||
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
|
self.chat.does_handle(ty) || self.heal_spread.does_handle(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
event: &RawEvent,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.chat.handle(time, subject, event).unwrap();
|
||||||
|
self.heal_spread.handle(time, subject, event)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self, subjects: &SubjectMap) -> Self::Output {
|
||||||
|
LogOutput {
|
||||||
|
chat: self.chat.finish(subjects),
|
||||||
|
heal_spread: self.heal_spread.finish(subjects),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
52
src/main.rs
52
src/main.rs
|
|
@ -7,59 +7,15 @@ use std::io::stdout;
|
||||||
use tf_log_parser::module::{
|
use tf_log_parser::module::{
|
||||||
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent,
|
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent,
|
||||||
};
|
};
|
||||||
use tf_log_parser::{parse_with_handler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap};
|
use tf_log_parser::{
|
||||||
use thiserror::Error;
|
parse, parse_with_handler, LogHandler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap,
|
||||||
|
};
|
||||||
#[derive(Default)]
|
|
||||||
struct LogHandler {
|
|
||||||
chat: ChatHandler,
|
|
||||||
heal_spread: HealSpreadHandler,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Serialize)]
|
|
||||||
struct LogOutput {
|
|
||||||
chat: Vec<ChatMessage>,
|
|
||||||
heal_spread: HashMap<SteamId3, HashMap<SteamId3, u32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
enum LogError {
|
|
||||||
#[error("{0}")]
|
|
||||||
HealSpread(#[from] InvalidHealEvent),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventHandler for LogHandler {
|
|
||||||
type Output = LogOutput;
|
|
||||||
type Error = LogError;
|
|
||||||
|
|
||||||
fn does_handle(&self, ty: RawEventType) -> bool {
|
|
||||||
self.chat.does_handle(ty) || self.heal_spread.does_handle(ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle(
|
|
||||||
&mut self,
|
|
||||||
time: u32,
|
|
||||||
subject: SubjectId,
|
|
||||||
event: &RawEvent,
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
self.chat.handle(time, subject, event).unwrap();
|
|
||||||
self.heal_spread.handle(time, subject, event)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(self, subjects: &SubjectMap) -> Self::Output {
|
|
||||||
LogOutput {
|
|
||||||
chat: self.chat.finish(subjects),
|
|
||||||
heal_spread: self.heal_spread.finish(subjects),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 log = parse_with_handler::<LogHandler>(&content)?;
|
let log = parse(&content)?;
|
||||||
|
|
||||||
serde_json::to_writer_pretty(stdout().lock(), &log).unwrap();
|
serde_json::to_writer_pretty(stdout().lock(), &log).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue