From 5346e888401282f57efe0a95210959a0d53ed252 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 23 Mar 2022 13:25:11 +0100 Subject: roezio: Add a logger implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maxime “pep” Buquet --- src/lib.rs | 1 + src/logger.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/logger.rs diff --git a/src/lib.rs b/src/lib.rs index 5eed1395..40464c30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod logger; mod theming; use crate::theming::{curses_attr, parse_attrs}; diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 00000000..d90efebd --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,226 @@ +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; + fn get_message(&self) -> String; +} + +#[derive(Debug, PartialEq)] +pub struct LogInfo<'a> { + time: DateTime, + message: Vec<&'a str>, +} + +impl<'a> LogItem for LogInfo<'a> { + fn get_time(&self) -> &DateTime { + &self.time + } + + fn get_message(&self) -> String { + self.message.join("\n") + } +} + +#[derive(Debug, PartialEq)] +pub struct LogMessage<'a> { + time: DateTime, + nick: &'a str, + 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 { + &self.time + } + + fn get_message(&self) -> String { + self.message.join("\n") + } +} + +pub fn parse_datetime(i: &str) -> IResult<&str, DateTime> { + 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.ymd(year, month, day).and_hms(hour, minute, second))) +} + +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)] +pub enum Item<'a> { + Message(LogMessage<'a>), + Info(LogInfo<'a>), +} + +pub fn parse_logs(mut logs: &str) -> IResult<&str, Vec> { + 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  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  Hello…\nMR 20181016T14:10:11Z 000  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  Hello…\nMR 20181016T14:10:11Z 000  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  Hello…\nMR 20181016T14:10:11Z 000  world!\n\n"; + parse_logs(log).unwrap_err(); + } + + #[test] + fn multiline_message() { + let log = "MR 20181016T14:10:08Z 001  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); + } +} -- cgit v1.2.3