This commit is contained in:
Robin Appelman 2025-06-02 23:14:57 +02:00
commit 95119b9347
13 changed files with 157 additions and 161 deletions

View file

@ -2,10 +2,11 @@
name = "tf-log-parser"
version = "0.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018"
edition = "2021"
license = "MIT OR Apache-2.0"
exclude = [ "test_data" ]
exclude = ["test_data"]
repository = "https://codeberg.org/icewind/tf-log-parser"
rust-version = "1.80.1"
[dependencies]
steamid-ng = "1.0.0"

View file

@ -1,7 +1,8 @@
use chrono::NaiveDateTime;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use criterion::{criterion_group, criterion_main, Criterion};
use flate2::read::GzDecoder;
use std::fs::File;
use std::hint::black_box;
use std::io::Read;
use std::path::Path;
use std::time::Duration;
@ -57,7 +58,7 @@ pub fn handle_event(c: &mut Criterion) {
black_box(&events)
.iter()
.flat_map(|(event, raw_event)| {
black_box(handler.process(&raw_event, &event, &mut start_time, &mut subjects))
black_box(handler.process(raw_event, event, &mut start_time, &mut subjects))
})
.count();
})

View file

@ -11,7 +11,7 @@ pub fn parse_benchmark() {
pub fn parse_raw() {
black_box(
LineSplit::new(black_box(&LOG))
LineSplit::new(black_box(LOG))
.filter(|line| !line.is_empty())
.flat_map(RawEvent::parse)
.count(),

122
derive/Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "ahash"
@ -23,6 +23,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -64,18 +70,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.23"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits 0.2.15",
"serde",
"time",
"wasm-bindgen",
"winapi",
"windows-link",
]
[[package]]
@ -140,22 +145,22 @@ dependencies = [
[[package]]
name = "enum-iterator"
version = "1.4.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "706d9e7cf1c7664859d79cd524e4e53ea2b67ea03c98cc2870c5e539695d597e"
checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "1.2.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "355f93763ef7b0ae1c43c4d8eccc9d5848d84ad1a1d8ce61c421d1ac85a19d05"
checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.101",
]
[[package]]
@ -181,7 +186,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi",
]
[[package]]
@ -312,25 +317,24 @@ dependencies = [
[[package]]
name = "miette"
version = "5.5.0"
version = "7.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afd9b301defa984bbdbe112b4763e093ed191750a0d914a78c1106b2d0fe703"
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
dependencies = [
"cfg-if",
"miette-derive",
"once_cell",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "5.5.0"
version = "7.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97c2401ab7ac5282ca5c8b518a87635b1a93762b0b90b9990c509888eeccba29"
checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.101",
]
[[package]]
@ -426,9 +430,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "paste"
version = "1.0.11"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "proc-macro-error"
@ -456,18 +460,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.55"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0dd4be24fcdcfeaa12a432d588dc59bbad6cad3510c67e74a2b6b2fc950564"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
@ -512,31 +516,32 @@ checksum = "5d5e082f6ea090deaf0e6dd04b68360fd5cddb152af6ce8927c9d25db299f98c"
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.101",
]
[[package]]
name = "serde_json"
version = "1.0.93"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
@ -553,7 +558,7 @@ dependencies = [
"regex",
"serde",
"serde_derive",
"thiserror",
"thiserror 1.0.38",
]
[[package]]
@ -565,7 +570,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.12",
"syn 2.0.101",
]
[[package]]
@ -576,7 +581,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.101",
]
[[package]]
@ -592,9 +597,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.12"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@ -627,7 +632,7 @@ dependencies = [
"serde_json",
"steamid-ng",
"tf-log-parser-derive",
"thiserror",
"thiserror 2.0.12",
"walkdir",
]
@ -639,7 +644,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta",
"syn 2.0.12",
"syn 2.0.101",
"tf-log-parser",
]
@ -649,7 +654,16 @@ version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.38",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@ -664,14 +678,14 @@ dependencies = [
]
[[package]]
name = "time"
version = "0.1.45"
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
@ -682,9 +696,9 @@ checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7"
[[package]]
name = "unicode-width"
version = "0.1.10"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "version_check"
@ -702,12 +716,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -798,3 +806,9 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"

View file

@ -110,15 +110,15 @@ fn micros_as_sec(micros: usize) -> f32 {
micros as f32 / 1_000_000.0
}
struct PanicCanary<F: Fn() -> ()>(F);
struct PanicCanary<F: Fn()>(F);
impl<F: Fn() -> ()> Drop for PanicCanary<F> {
impl<F: Fn()> Drop for PanicCanary<F> {
fn drop(&mut self) {
(self.0)()
}
}
fn on_panic<F: Fn() -> (), P: Fn() -> ()>(f: F, panic: P) {
fn on_panic<F: Fn(), P: Fn()>(f: F, panic: P) {
let canary = PanicCanary(panic);
f();
forget(canary)

View file

@ -14,21 +14,16 @@ use std::ops::{Index, IndexMut};
use std::str::FromStr;
use steamid_ng::{AccountType, Instance, SteamID, Universe};
#[derive(Serialize, Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash)]
#[derive(Serialize, Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash, Default)]
pub enum Team {
Red,
Blue,
#[default]
Spectator,
}
impl EventFieldFromStr for Team {}
impl Default for Team {
fn default() -> Self {
Team::Spectator
}
}
impl Team {
pub fn as_str(self) -> &'static str {
match self {
@ -58,7 +53,9 @@ impl FromStr for Team {
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence, Serialize)]
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence, Serialize, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum Class {
Scout,
@ -70,6 +67,7 @@ pub enum Class {
Medic,
Sniper,
Spy,
#[default]
Unknown,
}
@ -92,12 +90,6 @@ impl Class {
}
}
impl Default for Class {
fn default() -> Self {
Class::Unknown
}
}
impl FromStr for Class {
type Err = ();
@ -242,7 +234,7 @@ impl TryFrom<&RawSubject<'_>> for SubjectId {
}
}
let Ok((_, user_id, steam_id, _)) = split_player_subject(raw) else {
return Ok(SubjectId::MalformedPlayer(hash_player_str(*raw)));
return Ok(SubjectId::MalformedPlayer(hash_player_str(raw)));
};
if let Ok(steam_id) = SteamID::from_steam2(steam_id) {
SubjectId::Player(steam_id.account_id())
@ -250,7 +242,7 @@ impl TryFrom<&RawSubject<'_>> for SubjectId {
user_id
.parse()
.map(SubjectId::Bot)
.unwrap_or_else(|_| SubjectId::MalformedPlayer(hash_player_str(*raw)))
.unwrap_or_else(|_| SubjectId::MalformedPlayer(hash_player_str(raw)))
}
}
RawSubject::Team(team) => SubjectId::Team(*team),
@ -290,7 +282,7 @@ impl SubjectData {
SubjectData::Bot { user_id, .. } => SubjectId::Bot(*user_id),
SubjectData::Console => SubjectId::Console,
SubjectData::World => SubjectId::World,
SubjectData::MalformedPlayer(raw) => SubjectId::MalformedPlayer(hash_player_str(&raw)),
SubjectData::MalformedPlayer(raw) => SubjectId::MalformedPlayer(hash_player_str(raw)),
}
}
}
@ -347,7 +339,7 @@ pub struct SteamId3(pub SteamID);
impl PartialOrd<Self> for SteamId3 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
(u64::from(self.0)).partial_cmp(&u64::from(other.0))
Some(self.cmp(other))
}
}

View file

@ -2,7 +2,6 @@ mod game;
mod medic;
mod player;
use crate::event::game::{RoundLengthEvent, RoundWinEvent};
use crate::parsing::{skip, skip_matches, split_once, split_subject_end};
use crate::raw_event::{against_subject_parser, RawSubject};
use crate::{Error, Events, IResult, RawEvent, RawEventType, Result, SubjectId};
@ -29,7 +28,7 @@ trait GameEventErrTrait<T> {
fn with_raw(self, raw: &RawEvent) -> Result<T, GameEventError>;
}
impl<'a, T> GameEventErrTrait<T> for Result<T> {
impl<T> GameEventErrTrait<T> for Result<T> {
fn with_raw(self, raw: &RawEvent) -> Result<T, GameEventError> {
self.map_err(|err| GameEventError::Error {
err: Box::new(err),
@ -275,7 +274,7 @@ impl<'a, T: EventField<'a>> EventField<'a> for (T, T, T) {
impl<'a> EventField<'a> for Option<NonZeroU32> {
fn parse_field(input: &'a str) -> Result<Self> {
u32::parse_field(input).map(|int| NonZeroU32::new(int))
u32::parse_field(input).map(NonZeroU32::new)
}
}

View file

@ -4,7 +4,7 @@ use crate::module::{
ChatMessages, ClassStatsHandler, HealSpread, MedicStatsBuilder, PlayerHandler,
};
pub use crate::subjectmap::SubjectMap;
use chrono::{Duration, NaiveDate, NaiveDateTime};
use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime};
pub(crate) use error::ResultExt;
pub use error::{Error, IResult, Result};
pub use event::{Event, EventMeta, GameEvent};
@ -23,31 +23,24 @@ pub(crate) mod parsing;
pub mod raw_event;
mod subjectmap;
pub type PerSubjectMap = BTreeMap<SteamId3, <LogHandler as EventHandler>::PerSubjectOutput>;
pub fn parse(
log: &str,
) -> Result<
(
<LogHandler as EventHandler>::GlobalOutput,
BTreeMap<SteamId3, <LogHandler as EventHandler>::PerSubjectOutput>,
),
Error,
> {
) -> Result<(<LogHandler as EventHandler>::GlobalOutput, PerSubjectMap), Error> {
parse_with_handler::<LogHandler>(log)
}
pub fn raw_events<'a>(log: &'a str) -> impl Iterator<Item = Result<RawEvent<'a>>> + 'a {
pub fn raw_events(log: &str) -> impl Iterator<Item = Result<RawEvent>> {
LineSplit::new(log).map(RawEvent::parse)
}
pub type PerSubjectMapForHandler<Handler> =
BTreeMap<SteamId3, <Handler as EventHandler>::PerSubjectOutput>;
pub fn parse_with_handler<Handler: EventHandler>(
log: &str,
) -> Result<
(
Handler::GlobalOutput,
BTreeMap<SteamId3, Handler::PerSubjectOutput>,
),
Error,
> {
) -> Result<(Handler::GlobalOutput, PerSubjectMapForHandler<Handler>), Error> {
let mut events = raw_events(log);
let mut handler = Handler::default();
@ -63,43 +56,40 @@ pub fn parse_with_handler<Handler: EventHandler>(
Err(e) => return Err(e),
};
let should_handle = Handler::does_handle(raw_event.ty);
if should_handle || start_time.is_none() {
if should_handle {
let event = match GameEvent::parse(&raw_event) {
Ok(event) => event,
Err(e) => {
let old_date: NaiveDateTime = raw_event
.date
.try_into()
.unwrap_or_else(|_| NaiveDateTime::from_timestamp(0, 0));
if should_handle && start_time.is_none() {
let event = match GameEvent::parse(&raw_event) {
Ok(event) => event,
Err(e) => {
let old_date: NaiveDateTime = raw_event
.date
.try_into()
.unwrap_or_else(|_| DateTime::from_timestamp(0, 0).unwrap().naive_utc());
// truncated newline during log combining, ignore error
if contains_line_start(raw_event.params, &old_date.date()) {
// truncated newline during log combining, ignore error
if contains_line_start(raw_event.params, &old_date.date()) {
continue;
}
let Some(next) = events.next() else {
// log is truncated
break;
};
if let Ok(next) = next {
let new_date: NaiveDateTime = next.date.try_into().unwrap_or_else(|_| {
DateTime::from_timestamp(0, 0).unwrap().naive_utc()
});
// truncated lines during log combining, ignore error
if new_date.signed_duration_since(old_date) > Duration::seconds(60) {
continue;
}
let Some(next) = events.next() else {
// log is truncated
break;
};
if let Ok(next) = next {
let new_date: NaiveDateTime = next
.date
.try_into()
.unwrap_or_else(|_| NaiveDateTime::from_timestamp(0, 0));
// truncated lines during log combining, ignore error
if new_date.signed_duration_since(old_date) > Duration::seconds(60) {
continue;
}
}
return Err(e.into());
}
};
handler.process(&raw_event, &event, &mut start_time, &mut subjects)?;
}
return Err(e.into());
}
};
handler.process(&raw_event, &event, &mut start_time, &mut subjects)?;
}
}

View file

@ -19,11 +19,7 @@ impl PlayerSpecificData for HealSpread {
fn handle_event(&mut self, _meta: &EventMeta, _subject: SubjectId, event: &GameEvent) {
if let GameEvent::Healed(heal_event) = event {
if let Some(Ok(target_subject)) = heal_event
.target
.as_ref()
.map(|target| SubjectId::try_from(target))
{
if let Some(Ok(target_subject)) = heal_event.target.as_ref().map(SubjectId::try_from) {
if let Some(target_steam_id) = target_subject.steam_id() {
let healed = self.0.entry(SteamId3(target_steam_id)).or_default();
*healed += heal_event.amount

View file

@ -3,7 +3,7 @@ use crate::event::GameEvent;
use crate::module::GlobalData;
use crate::raw_event::RawEventType;
use crate::{EventMeta, SubjectMap};
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, TimeZone, Utc};
use serde::{Serialize, Serializer};
use std::num::ParseIntError;
use std::str::{FromStr, ParseBoolError};
@ -108,7 +108,11 @@ impl Settings {
balancing: false,
restriction: "".to_string(),
mumble_required: false,
date: Utc.ymd(1, 1, 1).and_hms(0, 0, 0),
date: NaiveDate::from_ymd_opt(1, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap()
.and_utc(),
server: "".to_string(),
}
}
@ -143,18 +147,14 @@ impl Serialize for LobbySettingsError {
}
}
#[derive(Default)]
pub enum LobbySettingsHandler {
#[default]
NotAvailable,
Active(Settings),
Err(LobbySettingsError),
}
impl Default for LobbySettingsHandler {
fn default() -> Self {
LobbySettingsHandler::NotAvailable
}
}
impl LobbySettingsHandler {
fn try_handle(&mut self, msg: &str) -> Result<(), LobbySettingsError> {
match self {
@ -230,9 +230,9 @@ impl GlobalData for LobbySettingsHandler {
fn get_timezone(date: &str) -> Result<FixedOffset, LobbySettingsError> {
if date.contains("CEST") {
Ok(FixedOffset::east(2 * 60 * 60))
Ok(FixedOffset::east_opt(2 * 60 * 60).unwrap())
} else if date.contains("CET") {
Ok(FixedOffset::east(60 * 60))
Ok(FixedOffset::east_opt(60 * 60).unwrap())
} else {
Err(LobbySettingsError::UnknownTimezone(date.into()))
}

View file

@ -47,7 +47,7 @@ pub trait EventHandler: Default {
time: match_time,
subject,
};
self.handle(&meta, subject, data, &event);
self.handle(&meta, subject, data, event);
Ok(())
}

View file

@ -20,7 +20,7 @@ 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)> {
pub fn split_subject_end(input: &str, offset: usize) -> Result<(&str, &str)> {
let start_offset = 1;
let end_offset = start_offset + offset;
debug_assert!(offset <= 2);
@ -51,11 +51,9 @@ pub fn skip(input: &str, count: usize) -> Result<&str> {
}
pub fn skip_matches(input: &str, char: u8) -> (&str, bool) {
if input.as_bytes().get(0) == Some(&char) {
// safety, we verified that the input has a length of at least 1
(unsafe { input.get_unchecked(1..) }, true)
} else {
(input, false)
match input.split_at_checked(1) {
Some((first, rest)) if first.as_bytes() == [char] => (rest, true),
_ => (input, false),
}
}
@ -64,7 +62,7 @@ pub fn find_between_end(input: &str, start: u8, end: u8) -> Option<&str> {
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) })?;
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) })
}

View file

@ -52,12 +52,14 @@ impl<'a> TryFrom<RawDate<'a>> for NaiveDateTime {
fn try_from(RawDate(raw): RawDate<'a>) -> Result<Self, Self::Error> {
Ok(
NaiveDate::from_ymd(raw[6..10].parse()?, raw[0..2].parse()?, raw[3..5].parse()?)
.and_hms(
NaiveDate::from_ymd_opt(raw[6..10].parse()?, raw[0..2].parse()?, raw[3..5].parse()?)
.unwrap()
.and_hms_opt(
raw[13..15].parse()?,
raw[16..18].parse()?,
raw[19..21].parse()?,
),
)
.unwrap(),
)
}
}
@ -66,7 +68,10 @@ impl<'a> TryFrom<RawDate<'a>> for NaiveDateTime {
fn test_parse_date() {
let raw = RawDate("08/06/2018 - 21:13:57");
assert_eq!(
NaiveDate::from_ymd(2018, 08, 06).and_hms(21, 13, 57),
NaiveDate::from_ymd_opt(2018, 8, 6)
.unwrap()
.and_hms_opt(21, 13, 57)
.unwrap(),
raw.try_into().unwrap()
);
}
@ -86,7 +91,7 @@ impl Default for RawSubject<'static> {
}
}
impl<'a> RawSubject<'a> {
impl RawSubject<'_> {
pub fn id(&self) -> Result<SubjectId, SubjectError> {
self.try_into()
}