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_json = "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 nom::bytes::complete::tag;
|
||||
use nom::character::complete::digit1;
|
||||
use nom::error::ErrorKind;
|
||||
use nom::IResult;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -11,12 +9,7 @@ pub struct HealedEvent<'a> {
|
|||
}
|
||||
|
||||
pub fn healed_event_parser(input: &str) -> IResult<&str, HealedEvent> {
|
||||
let (input, _) = tag("against ")(input)?;
|
||||
let (input, subject) = subject_parser(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)))?;
|
||||
let (input, subject) = param_parse_with("against", subject_parser)(input)?;
|
||||
let (input, amount) = param_parse_with("healing", quoted(u_int))(input)?;
|
||||
Ok((input, HealedEvent { subject, amount }))
|
||||
}
|
||||
|
|
|
|||
103
src/event/mod.rs
103
src/event/mod.rs
|
|
@ -1,3 +1,104 @@
|
|||
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};
|
||||
use crate::module::EventHandler;
|
||||
use crate::module::{ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent};
|
||||
use crate::raw_event::RawSubject;
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use raw_event::{RawEvent, RawEventType};
|
||||
use std::collections::BTreeMap;
|
||||
use serde::Serialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
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>(
|
||||
log: &str,
|
||||
) -> Result<Handler::Output, Error<Handler>> {
|
||||
|
|
@ -98,3 +103,48 @@ pub fn parse_with_handler<Handler: EventHandler>(
|
|||
|
||||
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::{
|
||||
ChatHandler, ChatMessage, EventHandler, HealSpreadHandler, InvalidHealEvent,
|
||||
};
|
||||
use tf_log_parser::{parse_with_handler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap};
|
||||
use thiserror::Error;
|
||||
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
}
|
||||
use tf_log_parser::{
|
||||
parse, parse_with_handler, LogHandler, RawEvent, RawEventType, SteamId3, SubjectId, SubjectMap,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), MainError> {
|
||||
let path = args().skip(1).next().expect("No path provided");
|
||||
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();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue