From f83d7ddf9b3514c7e1a5f7d39f1788fef1d0542d Mon Sep 17 00:00:00 2001 From: Max Audron Date: Wed, 23 Jun 2021 19:17:16 +0200 Subject: rewrite sed regex to work cross channel --- Cargo.lock | 4 +- Cargo.toml | 4 +- src/hooks/sed.rs | 242 ++++++++++++++++++++++++++++++------------------------- src/main.rs | 6 +- 4 files changed, 141 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3ef1ea..47390c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "catinator" -version = "1.2.1" +version = "1.3.0" dependencies = [ "anyhow", "base64", @@ -95,7 +95,7 @@ dependencies = [ [[package]] name = "catinator_macros" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "catinator", diff --git a/Cargo.toml b/Cargo.toml index 55bb9d4..b96a380 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "catinator" -version = "1.2.1" +version = "1.3.0" authors = ["Max Audron "] edition = "2018" [dependencies] -macros = { package = "catinator_macros", version = "0.1", path = "./macros" } +macros = { package = "catinator_macros", version = "0", path = "./macros" } irc = { version = "0.15", features = ["json", "tls-rust", "ctcp"], default_features = false } irc-proto = "0.15" diff --git a/src/hooks/sed.rs b/src/hooks/sed.rs index 7416111..cb17ed2 100644 --- a/src/hooks/sed.rs +++ b/src/hooks/sed.rs @@ -1,62 +1,76 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use irc::client::prelude::*; use sedregex::ReplaceCommand; -use std::cell::RefCell; +use std::collections::HashMap; static LOG_MAX_SIZE: usize = 10000; -thread_local!(static LOG: RefCell> = RefCell::new(Vec::with_capacity(LOG_MAX_SIZE))); thread_local!(static RE: regex::Regex = regex::Regex::new(r"^s/").unwrap()); -pub fn log(_bot: &crate::Bot, msg: Message) -> Result<()> { - log_msg(msg) -} +pub struct Sed(HashMap>); + +impl Sed { + pub fn new() -> Sed { + Sed(HashMap::new()) + } -fn log_msg(msg: Message) -> Result<()> { - if let Command::PRIVMSG(_, text) = msg.command.clone() { - LOG.with(|log_cell| { - let mut log = log_cell.borrow_mut(); - if log.len() >= LOG_MAX_SIZE { - let _ = log.remove(0); + pub fn log(&mut self, _bot: &crate::Bot, msg: Message) -> Result<()> { + self.log_msg(msg) + } + + fn log_msg(&mut self, msg: Message) -> Result<()> { + if let Command::PRIVMSG(target, text) = msg.command.clone() { + match self.0.get_mut(&target) { + Some(log) => { + if log.len() >= LOG_MAX_SIZE { + let _ = log.remove(0); + } + log.push((msg.source_nickname().unwrap().to_string(), text)) + } + None => { + let mut log = Vec::with_capacity(LOG_MAX_SIZE); + log.push((msg.source_nickname().unwrap().to_string(), text)); + self.0.insert(target, log); + } } - log.push((msg.source_nickname().unwrap().to_string(), text)) - }); + } + Ok(()) } - Ok(()) -} -pub fn replace(bot: &crate::Bot, msg: Message) -> Result<()> { - match find_and_replace(&msg) { - Ok(res) => { - bot.send_privmsg(msg.response_target().unwrap(), res.as_str()) - .unwrap(); - Ok(()) + pub fn replace(&mut self, bot: &crate::Bot, msg: Message) -> Result<()> { + match self.find_and_replace(&msg) { + Ok(res) => { + bot.send_privmsg(msg.response_target().unwrap(), res.as_str())?; + Ok(()) + } + Err(_) => Ok(()), } - Err(_) => Ok(()), } -} -fn find_and_replace(msg: &Message) -> Result { - if let Command::PRIVMSG(_, text) = msg.command.clone() { - let cmd = match ReplaceCommand::new(text.as_str()) { - Ok(cmd) => cmd, - Err(_) => return Err(anyhow!("building replace command failed")), - }; + fn find_and_replace(&mut self, msg: &Message) -> Result { + if let Command::PRIVMSG(target, text) = msg.command.clone() { + let cmd = match ReplaceCommand::new(text.as_str()) { + Ok(cmd) => cmd, + Err(_) => return Err(anyhow!("building replace command failed")), + }; + + let log = self + .0 + .get(&target) + .context("did not find log for current channel")?; - return LOG.with(|log_cell| { - log_cell - .borrow() + return log .iter() .rev() .find(|(_, text)| cmd.expr.is_match(text) && !RE.with(|re| re.is_match(text))) .and_then(|(nick, text)| Some(format!("<{}> {}", nick, cmd.execute(text)))) - .map_or(Err(anyhow!("replace failed")), |v| Ok(v)) - }); - } + .map_or(Err(anyhow!("replace failed")), |v| Ok(v)); + } - Err(anyhow!("not a privmsg")) + Err(anyhow!("not a privmsg")) + } } #[cfg(test)] @@ -64,93 +78,112 @@ mod tests { use super::*; use test::Bencher; - fn populate_log() { - LOG.with(|log_cell| { - let mut log = log_cell.borrow_mut(); - log.push(( - "user".to_string(), - "this is a long message which will be replaced".to_string(), - )); - for _ in 0..LOG_MAX_SIZE - 1 { - log.push(( - "user".to_string(), - "this is a long message which doesn't matter".to_string(), - )) - } - }); + fn populate_log() -> Sed { + let mut sed = Sed::new(); + + sed.log_msg( + Message::new( + Some("user!user@user.com"), + "PRIVMSG", + vec!["user", "this is a long message which will be replaced"], + ) + .unwrap(), + ) + .unwrap(); + + for _ in 0..LOG_MAX_SIZE - 1 { + sed.log_msg( + Message::new( + Some("user!user@user.com"), + "PRIVMSG", + vec!["user", "this is a long message which doesn't matter"], + ) + .unwrap(), + ) + .unwrap(); + } + + return sed; } #[test] fn test_log_push_max() { - LOG.with(|log_cell| { - let mut log = log_cell.borrow_mut(); - log.push(("user".to_string(), "one".to_string())); - for _ in 0..LOG_MAX_SIZE - 2 { - log.push(("user".to_string(), "two".to_string())) - } - log.push(("user".to_string(), "three".to_string())); + let mut sed = Sed::new(); + + sed.log_msg( + Message::new(Some("user!user@user.com"), "PRIVMSG", vec!["user", "one"]).unwrap(), + ) + .unwrap(); + + for _ in 0..LOG_MAX_SIZE - 2 { + sed.log_msg( + Message::new(Some("user!user@user.com"), "PRIVMSG", vec!["user", "two"]).unwrap(), + ) + .unwrap(); + } + sed.log_msg( + Message::new(Some("user!user@user.com"), "PRIVMSG", vec!["user", "three"]).unwrap(), + ) + .unwrap(); + { + let log = sed.0.get("user").unwrap(); assert_eq!( log[LOG_MAX_SIZE - 1], ("user".to_string(), "three".to_string()) ); assert_eq!(log[0], ("user".to_string(), "one".to_string())); - }); + } - log_msg(Message::new(Some("user!user@user.com"), "PRIVMSG", vec!["user", "four"]).unwrap()) - .unwrap(); + sed.log_msg( + Message::new(Some("user!user@user.com"), "PRIVMSG", vec!["user", "four"]).unwrap(), + ) + .unwrap(); - LOG.with(|log_cell| { - let log = log_cell.borrow(); + { + let log = sed.0.get("user").unwrap(); assert_eq!( log[LOG_MAX_SIZE - 1], ("user".to_string(), "four".to_string()) ); assert_eq!(log[0], ("user".to_string(), "two".to_string())); - }); + } } #[test] fn test_log_limit() { - populate_log(); + let mut sed = populate_log(); - LOG.with(|log_cell| { - let log = log_cell.borrow(); - assert_eq!(log.len(), LOG_MAX_SIZE) - }); + { + let log = sed.0.get("user").unwrap(); + assert_eq!(log.len(), LOG_MAX_SIZE); + } - log_msg(Message { - tags: None, - prefix: Some(Prefix::Nickname( - "user".to_string(), - "username".to_string(), - "userhost".to_string(), - )), - command: Command::PRIVMSG( - "#channel".to_string(), - "this is the 10001th message".to_string(), - ), - }) + sed.log_msg( + Message::new( + Some("user!user@user.com"), + "PRIVMSG", + vec!["user", "this is the 10001th message"], + ) + .unwrap(), + ) .unwrap(); - LOG.with(|log_cell| { - let log = log_cell.borrow(); - assert_eq!(log.len(), LOG_MAX_SIZE) - }); + { + let log = sed.0.get("user").unwrap(); + assert_eq!(log.len(), LOG_MAX_SIZE); + } } #[test] fn test_replace() { - populate_log(); + let mut sed = populate_log(); assert_eq!( - find_and_replace(&Message { + sed.find_and_replace(&Message { tags: None, prefix: None, - command: Command::PRIVMSG( - "#channel".to_string(), - "s/will be/has been/".to_string(), - ), + command: Command::PRIVMSG("user".to_string(), "s/will be/has been/".to_string(),), }) .unwrap(), " this is a long message which has been replaced" @@ -159,15 +192,12 @@ mod tests { #[test] fn test_replace_complex() { - populate_log(); + let mut sed = populate_log(); assert_eq!( - find_and_replace(&Message { + sed.find_and_replace(&Message { tags: None, prefix: None, - command: Command::PRIVMSG( - "#channel".to_string(), - "s/(will).*(be)/$2 $1/".to_string(), - ), + command: Command::PRIVMSG("user".to_string(), "s/(will).*(be)/$2 $1/".to_string(),), }) .unwrap(), " this is a long message which be will replaced" @@ -176,30 +206,24 @@ mod tests { #[bench] fn bench_replace(b: &mut Bencher) { - populate_log(); + let mut sed = populate_log(); b.iter(|| { - find_and_replace(&Message { + sed.find_and_replace(&Message { tags: None, prefix: None, - command: Command::PRIVMSG( - "#channel".to_string(), - "s/will be/has been/".to_string(), - ), + command: Command::PRIVMSG("user".to_string(), "s/will be/has been/".to_string()), }) }); } #[bench] fn bench_replace_complex(b: &mut Bencher) { - populate_log(); + let mut sed = populate_log(); b.iter(|| { - find_and_replace(&Message { + sed.find_and_replace(&Message { tags: None, prefix: None, - command: Command::PRIVMSG( - "#channel".to_string(), - "s/will be/has been/".to_string(), - ), + command: Command::PRIVMSG("user".to_string(), "s/(will).*(be)/$2 $1/".to_string()), }) }); } diff --git a/src/main.rs b/src/main.rs index 21a1ba1..dc128cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ async fn main() { .with_thread_ids(true) .init(); + let mut sed = catinator::hooks::sed::Sed::new(); + catinator![ hook( "sasl", @@ -20,7 +22,7 @@ async fn main() { "sed_log", "Log messages for use with sed replace, max 10k lines.", PRIVMSG, - catinator::hooks::sed::log + sed.log ), matcher( "shifty_eyes", @@ -32,7 +34,7 @@ async fn main() { "replace", "sed style replace with regex support. i/g/U/x sed flags available", r"^s/", - catinator::hooks::sed::replace + sed.replace ), matcher( "intensify", -- cgit v1.2.3