mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 10:14:10 +02:00
quote in name handling somewhat
This commit is contained in:
parent
484fd735d1
commit
de2e940974
5 changed files with 81 additions and 9 deletions
|
|
@ -21,6 +21,8 @@ memchr = "2.5.0"
|
||||||
ahash = "0.8.3"
|
ahash = "0.8.3"
|
||||||
tf-log-parser-derive = { version = "0.1", path = "./derive" }
|
tf-log-parser-derive = { version = "0.1", path = "./derive" }
|
||||||
miette = "5.5.0"
|
miette = "5.5.0"
|
||||||
|
walkdir = "2.3.2"
|
||||||
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.4"
|
criterion = "0.4"
|
||||||
|
|
|
||||||
28
examples/dir.rs
Normal file
28
examples/dir.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use main_error::MainError;
|
||||||
|
use std::env::args;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fs;
|
||||||
|
use tf_log_parser::parse;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
fn main() -> Result<(), MainError> {
|
||||||
|
let path = args().nth(1).expect("No path provided");
|
||||||
|
for entry in WalkDir::new(path) {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.extension() == Some(OsStr::new("log")) {
|
||||||
|
print!("{} - ", path.display());
|
||||||
|
let input = match fs::read_to_string(path) {
|
||||||
|
Ok(input) => input,
|
||||||
|
Err(e) => {
|
||||||
|
println!("failed to read file: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (output, _) = parse(&input)?;
|
||||||
|
println!("{} messages", output.chat.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ mod medic;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
use crate::event::game::{RoundLengthEvent, RoundWinEvent};
|
use crate::event::game::{RoundLengthEvent, RoundWinEvent};
|
||||||
use crate::parsing::{skip, skip_matches, split_once};
|
use crate::parsing::{skip, skip_matches, split_once, split_subject_end};
|
||||||
use crate::raw_event::{against_subject_parser, RawSubject};
|
use crate::raw_event::{against_subject_parser, RawSubject};
|
||||||
use crate::{Error, Events, IResult, RawEvent, RawEventType, Result, SubjectId};
|
use crate::{Error, Events, IResult, RawEvent, RawEventType, Result, SubjectId};
|
||||||
pub use game::*;
|
pub use game::*;
|
||||||
|
|
@ -15,7 +15,7 @@ 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} in \"{params}\"")]
|
||||||
Error {
|
Error {
|
||||||
err: Box<Error>,
|
err: Box<Error>,
|
||||||
ty: RawEventType,
|
ty: RawEventType,
|
||||||
|
|
@ -167,7 +167,12 @@ pub fn param_parse_with<'a, T, P: Fn(&'a str) -> Result<T>>(
|
||||||
|
|
||||||
let input = skip(input, key.len() + 2)?; // skip space + key + quote
|
let input = skip(input, key.len() + 2)?; // skip space + key + quote
|
||||||
|
|
||||||
let (value, input) = split_once(input, b'"', 1)?;
|
// hack to handle quotes in names
|
||||||
|
let (value, input) = if key == "against" || key == "objectowner" {
|
||||||
|
split_subject_end(input, 1)?
|
||||||
|
} else {
|
||||||
|
split_once(input, b'"', 1)?
|
||||||
|
};
|
||||||
|
|
||||||
let value = parser(value)?;
|
let value = parser(value)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
use memchr::memmem::Finder;
|
||||||
use memchr::{memchr, memrchr};
|
use memchr::{memchr, memrchr};
|
||||||
|
use once_cell::unsync::Lazy;
|
||||||
|
|
||||||
pub fn split_once(input: &str, delim: u8, offset: usize) -> Result<(&str, &str)> {
|
pub fn split_once(input: &str, delim: u8, offset: usize) -> Result<(&str, &str)> {
|
||||||
debug_assert!(delim < 128); // only basic ascii
|
debug_assert!(delim < 128); // only basic ascii
|
||||||
|
|
@ -13,6 +15,25 @@ pub fn split_once(input: &str, delim: u8, offset: usize) -> Result<(&str, &str)>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static SUBJECT_END_FINDER: Lazy<Finder<'static>> = Lazy::new(|| Finder::new(br#">""#));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_subject_end<'a>(input: &'a str, offset: usize) -> Result<(&'a str, &'a str)> {
|
||||||
|
let start_offset = 1;
|
||||||
|
let end_offset = start_offset + offset;
|
||||||
|
let end = SUBJECT_END_FINDER
|
||||||
|
.with(|finder| finder.find(input.as_bytes()))
|
||||||
|
.ok_or(Error::Incomplete)?;
|
||||||
|
// safety, memchr returns indices that are inside the input length and we only split on ascii
|
||||||
|
Ok(unsafe {
|
||||||
|
(
|
||||||
|
input.get_unchecked(..end + start_offset),
|
||||||
|
input.get_unchecked(end + end_offset..),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn take_until(input: &str, delim: u8) -> (&str, &str) {
|
pub fn take_until(input: &str, delim: u8) -> (&str, &str) {
|
||||||
debug_assert!(delim < 128); // only basic ascii
|
debug_assert!(delim < 128); // only basic ascii
|
||||||
if let Some(end) = memchr(delim, input.as_bytes()) {
|
if let Some(end) = memchr(delim, input.as_bytes()) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::common::Team;
|
use crate::common::Team;
|
||||||
use crate::parsing::split_once;
|
use crate::parsing::{split_once, split_subject_end};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use crate::{SubjectError, SubjectId};
|
use crate::{SubjectError, SubjectId};
|
||||||
use chrono::{NaiveDate, NaiveDateTime};
|
use chrono::{NaiveDate, NaiveDateTime};
|
||||||
|
|
@ -120,7 +120,7 @@ fn test_split_player_subject() {
|
||||||
|
|
||||||
pub fn against_subject_parser(input: &str) -> Result<RawSubject> {
|
pub fn against_subject_parser(input: &str) -> Result<RawSubject> {
|
||||||
// "against" fields are always players, and unquoted
|
// "against" fields are always players, and unquoted
|
||||||
if input.ends_with("e>") {
|
if input.ends_with("le>") {
|
||||||
Ok(RawSubject::Console)
|
Ok(RawSubject::Console)
|
||||||
} else {
|
} else {
|
||||||
Ok(RawSubject::Player(input))
|
Ok(RawSubject::Player(input))
|
||||||
|
|
@ -128,9 +128,12 @@ pub fn against_subject_parser(input: &str) -> Result<RawSubject> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subject_parser(input: &str) -> Result<(&str, RawSubject)> {
|
pub fn subject_parser(input: &str) -> Result<(&str, RawSubject)> {
|
||||||
|
let full = input;
|
||||||
if let Some(input) = input.strip_prefix('"') {
|
if let Some(input) = input.strip_prefix('"') {
|
||||||
let (player, input) = split_once(input, b'"', 1)?;
|
let Ok((player, input)) = split_subject_end(input, 1) else {
|
||||||
if player.ends_with("e>") {
|
return Ok((full, RawSubject::Console))
|
||||||
|
};
|
||||||
|
if player.ends_with("le>") {
|
||||||
Ok((input, RawSubject::Console))
|
Ok((input, RawSubject::Console))
|
||||||
} else {
|
} else {
|
||||||
Ok((input, RawSubject::Player(player)))
|
Ok((input, RawSubject::Player(player)))
|
||||||
|
|
@ -151,6 +154,17 @@ pub fn subject_parser(input: &str) -> Result<(&str, RawSubject)> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_subject_parser() {
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
"connected",
|
||||||
|
RawSubject::Player(r#"Buddie :")<25><[U:1:123]><>"#)
|
||||||
|
),
|
||||||
|
subject_parser(r#""Buddie :")<25><[U:1:123]><>" connected"#).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Logos)]
|
#[derive(Copy, Clone, Debug, PartialEq, Logos)]
|
||||||
pub enum RawEventType {
|
pub enum RawEventType {
|
||||||
#[token(r#"joined"#)]
|
#[token(r#"joined"#)]
|
||||||
|
|
@ -194,9 +208,9 @@ pub enum RawEventType {
|
||||||
#[token(r#"triggered "killedobject""#)]
|
#[token(r#"triggered "killedobject""#)]
|
||||||
KilledObject,
|
KilledObject,
|
||||||
#[token(r#"triggered "object_detonated""#)]
|
#[token(r#"triggered "object_detonated""#)]
|
||||||
Extinguished,
|
|
||||||
#[token(r#"triggered "player_extinguished""#)]
|
|
||||||
ObjectDetonated,
|
ObjectDetonated,
|
||||||
|
#[token(r#"triggered "player_extinguished""#)]
|
||||||
|
Extinguished,
|
||||||
#[token(r#"picked up"#)]
|
#[token(r#"picked up"#)]
|
||||||
PickedUp,
|
PickedUp,
|
||||||
#[token(r#"triggered "medic_death""#)]
|
#[token(r#"triggered "medic_death""#)]
|
||||||
|
|
@ -269,6 +283,8 @@ pub enum RawEventType {
|
||||||
TournamentModeStarted,
|
TournamentModeStarted,
|
||||||
#[token(r#"triggered "flagevent""#)]
|
#[token(r#"triggered "flagevent""#)]
|
||||||
FlagEvent,
|
FlagEvent,
|
||||||
|
#[token(r#"cvars"#)]
|
||||||
|
CVars,
|
||||||
#[error]
|
#[error]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue