summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs1
-rw-r--r--src/logger.rs226
2 files changed, 227 insertions, 0 deletions
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<Utc>;
+ fn get_message(&self) -> String;
+}
+
+#[derive(Debug, PartialEq)]
+pub struct LogInfo<'a> {
+ time: DateTime<Utc>,
+ 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)]
+pub struct LogMessage<'a> {
+ time: DateTime<Utc>,
+ 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<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.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<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);
+ }
+}