mirror of
https://codeberg.org/icewind/tf-log-parser.git
synced 2026-06-03 18:24:09 +02:00
handlers
This commit is contained in:
parent
dfc702f43d
commit
ca61f4ea6a
8 changed files with 531 additions and 11 deletions
|
|
@ -8,3 +8,7 @@ steamid-ng = "1"
|
||||||
nom = "6"
|
nom = "6"
|
||||||
enum-iterator = "0.7"
|
enum-iterator = "0.7"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
thiserror = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
main_error = "0.1"
|
||||||
24
examples/lobby_chat.rs
Normal file
24
examples/lobby_chat.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
use main_error::MainError;
|
||||||
|
use std::env::args;
|
||||||
|
use std::fs;
|
||||||
|
use tf_log_parser::module::{ChatHandler, HandlerStack, LobbySettingsHandler, OptionalHandler};
|
||||||
|
use tf_log_parser::parse_with_handler;
|
||||||
|
|
||||||
|
type Handler = HandlerStack<ChatHandler, OptionalHandler<LobbySettingsHandler>>;
|
||||||
|
|
||||||
|
fn main() -> Result<(), MainError> {
|
||||||
|
let path = args().skip(1).next().expect("No path provided");
|
||||||
|
let content = fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
let (chat, lobby_settings) = parse_with_handler::<Handler>(&content)?;
|
||||||
|
|
||||||
|
if let Ok(Some(settings)) = lobby_settings {
|
||||||
|
println!("Lobby settings: {:#?}", settings);
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in chat {
|
||||||
|
println!("{}: {}", message.time, message.message);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
42
src/common.rs
Normal file
42
src/common.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::raw_event::RawSubject;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub enum Team {
|
||||||
|
Red,
|
||||||
|
Blue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Team {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"Blue" => Ok(Team::Blue),
|
||||||
|
"Red" => Ok(Team::Red),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optimized subject id
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub enum SubjectId {
|
||||||
|
Player(u8),
|
||||||
|
Team(Team),
|
||||||
|
System,
|
||||||
|
World,
|
||||||
|
Console,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&RawSubject<'_>> for SubjectId {
|
||||||
|
fn from(raw: &RawSubject) -> Self {
|
||||||
|
match raw {
|
||||||
|
RawSubject::Player { user_id, .. } => SubjectId::Player(user_id.parse().unwrap()),
|
||||||
|
RawSubject::Team(team) => SubjectId::Team(team.parse().unwrap()),
|
||||||
|
RawSubject::System(_) => SubjectId::System,
|
||||||
|
RawSubject::Console => SubjectId::Console,
|
||||||
|
RawSubject::World => SubjectId::World,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/lib.rs
66
src/lib.rs
|
|
@ -1,9 +1,65 @@
|
||||||
|
use crate::module::EventHandler;
|
||||||
|
use crate::raw_event::RawEvent;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
pub mod module;
|
||||||
mod raw_event;
|
mod raw_event;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[derive(Error)]
|
||||||
mod tests {
|
pub enum Error<Handler: EventHandler> {
|
||||||
#[test]
|
#[error("Malformed logfile: {0}")]
|
||||||
fn it_works() {
|
Malformed(String),
|
||||||
assert_eq!(2 + 2, 4);
|
#[error("{0}")]
|
||||||
|
HandlerError(Handler::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: EventHandler> Debug for Error<Handler> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Malformed(e) => e.fmt(f),
|
||||||
|
Error::HandlerError(e) => e.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: EventHandler> From<nom::error::Error<&'_ str>> for Error<Handler> {
|
||||||
|
fn from(e: nom::error::Error<&str>) -> Self {
|
||||||
|
Error::Malformed(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_with_handler<Handler: EventHandler>(
|
||||||
|
log: &str,
|
||||||
|
) -> Result<Handler::Output, Error<Handler>> {
|
||||||
|
let events = log
|
||||||
|
.lines()
|
||||||
|
.filter(|line| line.starts_with("L "))
|
||||||
|
.map(RawEvent::parse);
|
||||||
|
|
||||||
|
let mut handler = Handler::default();
|
||||||
|
|
||||||
|
let mut start_time: Option<DateTime<Utc>> = None;
|
||||||
|
|
||||||
|
for event_res in events {
|
||||||
|
let event = event_res?;
|
||||||
|
if handler.does_handle(event.ty) || start_time.is_none() {
|
||||||
|
let event_time: DateTime<Utc> = (&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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler
|
||||||
|
.handle(match_time, (&event.subject).into(), &event)
|
||||||
|
.map_err(Error::HandlerError)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(handler.finish())
|
||||||
|
}
|
||||||
|
|
|
||||||
59
src/module/chat.rs
Normal file
59
src/module/chat.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::common::SubjectId;
|
||||||
|
use crate::module::EventHandler;
|
||||||
|
use crate::raw_event::{RawEvent, RawEventType};
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
pub struct ChatMessage {
|
||||||
|
pub time: u32,
|
||||||
|
pub subject: SubjectId,
|
||||||
|
pub message: String,
|
||||||
|
pub chat_type: ChatType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ChatType {
|
||||||
|
All,
|
||||||
|
Team,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ChatHandler(Vec<ChatMessage>);
|
||||||
|
|
||||||
|
impl EventHandler for ChatHandler {
|
||||||
|
type Output = Vec<ChatMessage>;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
|
matches!(ty, RawEventType::SayTeam | RawEventType::Say)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
event: &RawEvent,
|
||||||
|
) -> Result<(), Infallible> {
|
||||||
|
if !matches!(subject, SubjectId::Player(_)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
match event.ty {
|
||||||
|
RawEventType::SayTeam => self.0.push(ChatMessage {
|
||||||
|
time,
|
||||||
|
subject,
|
||||||
|
message: event.params.trim_matches('"').into(),
|
||||||
|
chat_type: ChatType::Team,
|
||||||
|
}),
|
||||||
|
RawEventType::Say => self.0.push(ChatMessage {
|
||||||
|
time,
|
||||||
|
subject,
|
||||||
|
message: event.params.trim_matches('"').into(),
|
||||||
|
chat_type: ChatType::All,
|
||||||
|
}),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> Self::Output {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
202
src/module/lobbysettings.rs
Normal file
202
src/module/lobbysettings.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
use crate::common::SubjectId;
|
||||||
|
use crate::module::EventHandler;
|
||||||
|
use crate::raw_event::{RawEvent, RawEventType};
|
||||||
|
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
|
||||||
|
use std::str::{FromStr, ParseBoolError};
|
||||||
|
use steamid_ng::SteamID;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum GameType {
|
||||||
|
Sixes,
|
||||||
|
Highlander,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for GameType {
|
||||||
|
type Err = LobbySettingsError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"6v6" => Ok(Self::Sixes),
|
||||||
|
"highlander" => Ok(Self::Highlander),
|
||||||
|
unknown => Err(LobbySettingsError::UnknownGameType(unknown.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Location {
|
||||||
|
Europe,
|
||||||
|
NorthAmerica,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Location {
|
||||||
|
type Err = LobbySettingsError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"Europe" => Ok(Self::Europe),
|
||||||
|
"North America" => Ok(Self::NorthAmerica),
|
||||||
|
unknown => Err(LobbySettingsError::UnknownLocation(unknown.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct LobbyLeader {
|
||||||
|
name: String,
|
||||||
|
steam_id: SteamID,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for LobbyLeader {
|
||||||
|
type Err = LobbySettingsError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if let Some((name, steam_id)) = s.rsplit_once(" (") {
|
||||||
|
if let Ok(steam_id) = steam_id.trim_end_matches(")").parse::<u64>() {
|
||||||
|
Ok(LobbyLeader {
|
||||||
|
name: name.into(),
|
||||||
|
steam_id: steam_id.into(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(LobbySettingsError::MalformedLeader(s.into()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(LobbySettingsError::MalformedLeader(s.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Settings {
|
||||||
|
id: u32,
|
||||||
|
leader: LobbyLeader,
|
||||||
|
map: String,
|
||||||
|
game_type: GameType,
|
||||||
|
location: Location,
|
||||||
|
advanced: bool,
|
||||||
|
region_lock: bool,
|
||||||
|
allow_offclassing: bool,
|
||||||
|
balancing: bool,
|
||||||
|
restriction: String,
|
||||||
|
mumble_required: bool,
|
||||||
|
date: DateTime<Utc>,
|
||||||
|
server: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings {
|
||||||
|
id: 0,
|
||||||
|
leader: LobbyLeader::default(),
|
||||||
|
map: "".to_string(),
|
||||||
|
game_type: GameType::Sixes,
|
||||||
|
location: Location::Europe,
|
||||||
|
advanced: false,
|
||||||
|
region_lock: false,
|
||||||
|
allow_offclassing: false,
|
||||||
|
balancing: false,
|
||||||
|
restriction: "".to_string(),
|
||||||
|
mumble_required: false,
|
||||||
|
date: Utc.ymd(1, 1, 1).and_hms(0, 0, 0),
|
||||||
|
server: "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LobbySettingsError {
|
||||||
|
#[error("Malformed lobby id: {0}")]
|
||||||
|
InvalidLobbyId(String),
|
||||||
|
#[error("Unknown game type: {0}")]
|
||||||
|
UnknownGameType(String),
|
||||||
|
#[error("Unknown location: {0}")]
|
||||||
|
UnknownLocation(String),
|
||||||
|
#[error("Unknown timezone in date: {0}")]
|
||||||
|
UnknownTimezone(String),
|
||||||
|
#[error("Malformed leader: {0}")]
|
||||||
|
MalformedLeader(String),
|
||||||
|
#[error("{0}")]
|
||||||
|
InvalidBool(#[from] ParseBoolError),
|
||||||
|
#[error("{0}")]
|
||||||
|
InvalidDate(#[from] chrono::ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LobbySettingsHandler(Settings);
|
||||||
|
|
||||||
|
impl EventHandler for LobbySettingsHandler {
|
||||||
|
type Output = Option<Settings>;
|
||||||
|
type Error = LobbySettingsError;
|
||||||
|
|
||||||
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
|
matches!(ty, RawEventType::Say)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
_time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
event: &RawEvent,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if !matches!(subject, SubjectId::Console) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if matches!(event.ty, RawEventType::Say) {
|
||||||
|
let msg = event.params.trim_matches('"');
|
||||||
|
if let Some((id, _)) = msg
|
||||||
|
.strip_prefix("TF2Center Lobby #")
|
||||||
|
.and_then(|s| str::split_once(s, " |"))
|
||||||
|
{
|
||||||
|
self.0.id = id
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| LobbySettingsError::InvalidLobbyId(id.into()))?;
|
||||||
|
}
|
||||||
|
if let Some((key, value)) = msg.split_once(": ") {
|
||||||
|
match key {
|
||||||
|
"Leader" => self.0.leader = value.parse()?,
|
||||||
|
"Map" => self.0.map = value.into(),
|
||||||
|
"GameType" => self.0.game_type = value.parse()?,
|
||||||
|
"Location" => self.0.location = value.parse()?,
|
||||||
|
"Advanced Lobby" => self.0.advanced = value.parse()?,
|
||||||
|
"Region lock" => self.0.region_lock = value.parse()?,
|
||||||
|
"Allow offclassing" => self.0.allow_offclassing = value.parse()?,
|
||||||
|
"Balancing" => self.0.balancing = value.parse()?,
|
||||||
|
"Restriction" => self.0.restriction = value.into(),
|
||||||
|
"Mumble required" => self.0.mumble_required = value.parse()?,
|
||||||
|
"Launch date" => {
|
||||||
|
self.0.date = get_timezone(value)?
|
||||||
|
.from_local_datetime(&NaiveDateTime::parse_from_str(
|
||||||
|
value,
|
||||||
|
"%a %b %d %H:%M:%S %Z %Y",
|
||||||
|
)?)
|
||||||
|
.earliest()
|
||||||
|
.unwrap()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
"Server" => self.0.server = value.into(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> Self::Output {
|
||||||
|
if self.0.id > 0 {
|
||||||
|
Some(self.0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_timezone(date: &str) -> Result<FixedOffset, LobbySettingsError> {
|
||||||
|
if date.contains("CEST") {
|
||||||
|
Ok(FixedOffset::east(2 * 60 * 60))
|
||||||
|
} else if date.contains("CET") {
|
||||||
|
Ok(FixedOffset::east(60 * 60))
|
||||||
|
} else {
|
||||||
|
Err(LobbySettingsError::UnknownTimezone(date.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/module/mod.rs
Normal file
121
src/module/mod.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
use crate::common::SubjectId;
|
||||||
|
use crate::raw_event::{RawEvent, RawEventType};
|
||||||
|
pub use chat::{ChatHandler, ChatMessage, ChatType};
|
||||||
|
pub use lobbysettings::{
|
||||||
|
LobbySettingsError, LobbySettingsHandler, Location, Settings as LobbySettings,
|
||||||
|
};
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
mod chat;
|
||||||
|
mod lobbysettings;
|
||||||
|
|
||||||
|
pub trait EventHandler: Default {
|
||||||
|
type Output;
|
||||||
|
type Error: Error + Debug;
|
||||||
|
|
||||||
|
fn does_handle(&self, ty: RawEventType) -> bool;
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
event: &RawEvent,
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
fn finish(self) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct HandlerStack<Head, Tail> {
|
||||||
|
head: Head,
|
||||||
|
tail: Tail,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Head: EventHandler, Tail: EventHandler> EventHandler for HandlerStack<Head, Tail> {
|
||||||
|
type Output = (Head::Output, Tail::Output);
|
||||||
|
type Error = EitherError<Head::Error, Tail::Error>;
|
||||||
|
|
||||||
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
|
self.head.does_handle(ty) || self.tail.does_handle(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
event: &RawEvent,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.head
|
||||||
|
.handle(time, subject, event)
|
||||||
|
.map_err(|e| EitherError::E1(e))?;
|
||||||
|
self.tail
|
||||||
|
.handle(time, subject, event)
|
||||||
|
.map_err(|e| EitherError::E2(e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> Self::Output {
|
||||||
|
(self.head.finish(), self.tail.finish())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum EitherError<E1: Error, E2: Error> {
|
||||||
|
#[error("{0}")]
|
||||||
|
E1(E1),
|
||||||
|
#[error("{0}")]
|
||||||
|
E2(E2),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handler that doesn't stop the parsing on failure
|
||||||
|
pub enum OptionalHandler<Handler: EventHandler> {
|
||||||
|
Active(Handler),
|
||||||
|
Failed(Handler::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: EventHandler> Default for OptionalHandler<Handler> {
|
||||||
|
fn default() -> Self {
|
||||||
|
OptionalHandler::Active(Handler::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Handler: EventHandler> EventHandler for OptionalHandler<Handler> {
|
||||||
|
type Output = Result<Handler::Output, Handler::Error>;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn does_handle(&self, ty: RawEventType) -> bool {
|
||||||
|
match self {
|
||||||
|
OptionalHandler::Active(handler) => handler.does_handle(ty),
|
||||||
|
OptionalHandler::Failed(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
time: u32,
|
||||||
|
subject: SubjectId,
|
||||||
|
event: &RawEvent,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let res = if let OptionalHandler::Active(handler) = self {
|
||||||
|
handler.handle(time, subject, event)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
dbg!(&e);
|
||||||
|
*self = OptionalHandler::Failed(e)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> Self::Output {
|
||||||
|
match self {
|
||||||
|
OptionalHandler::Active(handler) => Ok(handler.finish()),
|
||||||
|
OptionalHandler::Failed(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,10 +56,10 @@ pub struct RawDate<'a> {
|
||||||
pub seconds: &'a str,
|
pub seconds: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<RawDate<'a>> for DateTime<Utc> {
|
impl<'a> TryFrom<&RawDate<'a>> for DateTime<Utc> {
|
||||||
type Error = ParseIntError;
|
type Error = ParseIntError;
|
||||||
|
|
||||||
fn try_from(value: RawDate<'a>) -> Result<Self, Self::Error> {
|
fn try_from(value: &RawDate<'a>) -> Result<Self, Self::Error> {
|
||||||
Ok(Utc
|
Ok(Utc
|
||||||
.ymd(
|
.ymd(
|
||||||
value.year.parse()?,
|
value.year.parse()?,
|
||||||
|
|
@ -115,20 +115,32 @@ pub enum RawSubject<'a> {
|
||||||
World,
|
World,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> RawSubject<'a> {
|
||||||
|
pub fn name(&self) -> &'a str {
|
||||||
|
match self {
|
||||||
|
RawSubject::Player { name, .. } => name,
|
||||||
|
RawSubject::Team(team) => team,
|
||||||
|
RawSubject::System(system) => system,
|
||||||
|
RawSubject::Console => "Console",
|
||||||
|
RawSubject::World => "World",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn subject_parser_world(input: &str) -> IResult<&str, RawSubject> {
|
fn subject_parser_world(input: &str) -> IResult<&str, RawSubject> {
|
||||||
let (input, _) = tag("World")(input)?;
|
let (input, _) = tag("World")(input)?;
|
||||||
Ok((input, RawSubject::World))
|
Ok((input, RawSubject::World))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subject_parser_console(input: &str) -> IResult<&str, RawSubject> {
|
fn subject_parser_console(input: &str) -> IResult<&str, RawSubject> {
|
||||||
let (input, _) = tag("Console<0><Console><Console>")(input)?;
|
let (input, _) = tag(r#""Console<0><Console><Console>""#)(input)?;
|
||||||
Ok((input, RawSubject::World))
|
Ok((input, RawSubject::Console))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subject_parser_team(input: &str) -> IResult<&str, RawSubject> {
|
fn subject_parser_team(input: &str) -> IResult<&str, RawSubject> {
|
||||||
let (input, _) = tag(r#"Team ""#)(input)?;
|
let (input, _) = tag(r#"Team ""#)(input)?;
|
||||||
|
|
||||||
let (input, team) = take_while(|c| c != '"')(input)?;
|
let (input, team) = alt((tag("Red"), tag("Blue")))(input)?;
|
||||||
|
|
||||||
let (input, _) = one_of("\"")(input)?;
|
let (input, _) = one_of("\"")(input)?;
|
||||||
Ok((input, RawSubject::Team(team)))
|
Ok((input, RawSubject::Team(team)))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue