roezio/src/logger.rs

231 lines
6.1 KiB
Rust
Raw Normal View History

2023-02-14 14:04:35 +00:00
use chrono::{DateTime, TimeZone, Utc};
use nom;
use nom::{
bytes::complete::{tag, take, take_until},
combinator::{map, map_res},
multi::many_m_n,
sequence::tuple,
IResult,
};
use std::str::FromStr;
pub trait LogItem {
fn get_time(&self) -> &DateTime<Utc>;
fn get_message(&self) -> String;
}
#[derive(Debug, PartialEq, Eq)]
pub struct LogInfo<'a> {
pub time: DateTime<Utc>,
pub message: Vec<&'a str>,
}
impl<'a> LogItem for LogInfo<'a> {
fn get_time(&self) -> &DateTime<Utc> {
&self.time
}
fn get_message(&self) -> String {
self.message.join("\n")
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct LogMessage<'a> {
pub time: DateTime<Utc>,
pub nick: &'a str,
pub message: Vec<&'a str>,
}
impl<'a> LogMessage<'a> {
pub fn get_nick(&self) -> &str {
self.nick
}
}
impl<'a> LogItem for LogMessage<'a> {
fn get_time(&self) -> &DateTime<Utc> {
&self.time
}
fn get_message(&self) -> String {
self.message.join("\n")
}
}
pub fn parse_datetime(i: &str) -> IResult<&str, DateTime<Utc>> {
let (i, (year, month, day, _, hour, _, minute, _, second, _)) = tuple((
map_res(take(4usize), i32::from_str),
map_res(take(2usize), u32::from_str),
map_res(take(2usize), u32::from_str),
tag("T"),
map_res(take(2usize), u32::from_str),
tag(":"),
map_res(take(2usize), u32::from_str),
tag(":"),
map_res(take(2usize), u32::from_str),
tag("Z"),
))(i)?;
Ok((
i,
Utc.with_ymd_and_hms(year, month, day, hour, minute, second)
.unwrap(),
))
}
pub fn parse_log_info(i: &str) -> IResult<&str, LogInfo> {
let (i, (_, time, _, nb_lines)) = tuple((
tag("MI "),
parse_datetime,
tag(" "),
map_res(take(3usize), usize::from_str),
))(i)?;
let (i, message) = many_m_n(
nb_lines + 1,
nb_lines + 1,
map(
tuple((tag(" "), take_until("\n"), tag("\n"))),
|(_, line, _)| line,
),
)(i)?;
Ok((i, LogInfo { time, message }))
}
pub fn parse_log_message(i: &str) -> IResult<&str, LogMessage> {
let (i, (_, time, _, nb_lines, _, nick, _, line0, _)) = tuple((
tag("MR "),
parse_datetime,
tag(" "),
map_res(take(3usize), usize::from_str),
tag(" <"),
take_until(">  "),
tag(">  "),
take_until("\n"),
tag("\n"),
))(i)?;
let (i, lines) = many_m_n(
nb_lines,
nb_lines,
map(
tuple((tag(" "), take_until("\n"), tag("\n"))),
|(_, line, _)| line,
),
)(i)?;
Ok((
i,
LogMessage {
time,
nick,
message: {
let mut message = lines;
message.insert(0, line0);
message
},
},
))
}
#[derive(Debug, PartialEq, Eq)]
pub enum Item<'a> {
Message(LogMessage<'a>),
Info(LogInfo<'a>),
}
pub fn parse_logs(mut logs: &str) -> IResult<&str, Vec<Item>> {
let mut items = vec![];
loop {
if logs.is_empty() {
break;
}
if logs.starts_with("MR ") {
let message = parse_log_message(logs)?;
logs = message.0;
items.push(Item::Message(message.1));
} else if logs.starts_with("MI ") {
let info = parse_log_info(logs)?;
logs = info.0;
items.push(Item::Info(info.1));
} else {
return Err(nom::Err::Error(nom::error::Error::new(
logs,
nom::error::ErrorKind::Fail,
)));
}
}
Ok((logs, items))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_message() {
let log = "MR 20181016T14:10:08Z 000 <Link Mauve>  Hello world!\n";
let message = LogMessage {
time: "2018-10-16T16:10:08+0200".parse().unwrap(),
nick: "Link Mauve",
message: vec!["Hello world!"],
};
let (_, message2) = parse_log_message(log).unwrap();
assert_eq!(message, message2);
}
#[test]
fn multiple_messages() {
let log = "MR 20181016T14:10:08Z 000 <Link Mauve>  Hello…\nMR 20181016T14:10:11Z 000 <Link Mauve>  world!\n";
let messages = [
LogMessage {
time: "2018-10-16T16:10:08+0200".parse().unwrap(),
nick: "Link Mauve",
message: vec!["Hello…"],
},
LogMessage {
time: "2018-10-16T16:10:11+0200".parse().unwrap(),
nick: "Link Mauve",
message: vec!["world!"],
},
];
let (i, message1) = parse_log_message(log).unwrap();
let (_, message2) = parse_log_message(i).unwrap();
assert_eq!(messages, [message1, message2]);
}
#[test]
fn parse_all_logs() {
let log = "MR 20181016T14:10:08Z 000 <Link Mauve>  Hello…\nMR 20181016T14:10:11Z 000 <Link Mauve>  world!\n";
let messages = vec![
Item::Message(LogMessage {
time: "2018-10-16T16:10:08+0200".parse().unwrap(),
nick: "Link Mauve",
message: vec!["Hello…"],
}),
Item::Message(LogMessage {
time: "2018-10-16T16:10:11+0200".parse().unwrap(),
nick: "Link Mauve",
message: vec!["world!"],
}),
];
let (_, messages1) = parse_logs(log).unwrap();
assert_eq!(messages, messages1);
}
#[test]
fn trailing_characters() {
let log = "MR 20181016T14:10:08Z 000 <Link Mauve>  Hello…\nMR 20181016T14:10:11Z 000 <Link Mauve>  world!\n\n";
parse_logs(log).unwrap_err();
}
#[test]
fn multiline_message() {
let log = "MR 20181016T14:10:08Z 001 <Link Mauve>  Hello…\n world!\n";
let message = LogMessage {
time: "2018-10-16T16:10:08+0200".parse().unwrap(),
nick: "Link Mauve",
message: vec!["Hello…", "world!"],
};
let (_, message2) = parse_log_message(log).unwrap();
assert_eq!(message, message2);
}
}