diff options
| author | Max Audron <audron@cocaine.farm> | 2021-10-17 17:07:09 +0200 |
|---|---|---|
| committer | Max Audron <audron@cocaine.farm> | 2021-10-19 14:37:14 +0200 |
| commit | 4754b420ced2503eb2641d6ddf678736e1aa7369 (patch) | |
| tree | 511e69d8753cdea469a57152a483d209d16a5fa8 /src/hooks/sed/mod.rs | |
| parent | add formatting trait for irc codes (diff) | |
replace sedregex crate8-rework-sed
This replaces the sedregex crate with our own implementation for
multiple reasons:
1. We required to access the parsed regex, this required a patch to the
sedregex crate which did not get merged due to an inactive dev, blocking
us from publishing on crates.Io
2. We wanted to highlight the changes done in bold
3. We want to add execution of multiple chained sed commands in the
future which would require more modification
Diffstat (limited to 'src/hooks/sed/mod.rs')
| -rw-r--r-- | src/hooks/sed/mod.rs | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/hooks/sed/mod.rs b/src/hooks/sed/mod.rs new file mode 100644 index 0000000..3128372 --- /dev/null +++ b/src/hooks/sed/mod.rs @@ -0,0 +1,252 @@ +use anyhow::{anyhow, bail, Context, Result}; +use irc::client::prelude::*; + +use std::collections::HashMap; + +#[allow(dead_code)] +mod parser; + +static LOG_MAX_SIZE: usize = 10000; + +thread_local!(static RE: regex::Regex = regex::Regex::new(r"^s/").unwrap()); + +pub struct Sed(HashMap<String, Vec<(String, String)>>); + +impl Sed { + pub fn new() -> Sed { + Sed(HashMap::new()) + } + + pub fn log(&mut self, _bot: &crate::Bot, msg: Message) -> Result<()> { + self.log_msg(msg).context("failed to log new message") + } + + fn log_msg(&mut self, msg: Message) -> Result<()> { + if let Command::PRIVMSG(target, mut text) = msg.command.clone() { + if text.starts_with("\x01ACTION") { + text = text.replace("\x01ACTION", "\x01\x01"); + } + + 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); + } + } + } + Ok(()) + } + + pub fn replace(&mut self, bot: &crate::Bot, msg: Message) -> Result<()> { + match self.find_and_replace(&msg) { + Ok(res) => match bot.send_privmsg(msg.response_target().unwrap(), res.as_str()) { + Ok(_) => Ok(()), + Err(_) => bail!( + "failed to send message: \"{:?}\" to channel: {:?}", + msg.response_target().unwrap(), + res + ), + }, + Err(_) => bail!("did not find match for: {:?}", msg), + } + } + + fn find_and_replace(&mut self, msg: &Message) -> Result<String> { + if let Command::PRIVMSG(target, text) = msg.command.clone() { + let cmd = + parser::Command::from_str(text.as_str()).context("failed to parse sed command")?; + + let log = self + .0 + .get(&target) + .context("did not find log for current channel")?; + + return log + .iter() + .rev() + .find(|(_, text)| cmd.regex().is_match(text) && !RE.with(|re| re.is_match(text))) + .and_then(|(nick, text)| { + if text.starts_with("\x01\x01") { + Some(format!( + "* {}{}", + nick, + cmd.execute(&text.replace("\x01", "")) + )) + } else { + Some(format!("<{}> {}", nick, cmd.execute(text))) + } + }) + .map_or(Err(anyhow!("replace failed")), |v| Ok(v)); + } + + Err(anyhow!("not a privmsg")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub 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() { + 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())); + } + + sed.log_msg( + Message::new(Some("user!user@user.com"), "PRIVMSG", vec!["user", "four"]).unwrap(), + ) + .unwrap(); + + { + 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() { + let mut sed = populate_log(); + + { + let log = sed.0.get("user").unwrap(); + assert_eq!(log.len(), LOG_MAX_SIZE); + } + + sed.log_msg( + Message::new( + Some("user!user@user.com"), + "PRIVMSG", + vec!["user", "this is the 10001th message"], + ) + .unwrap(), + ) + .unwrap(); + + { + let log = sed.0.get("user").unwrap(); + assert_eq!(log.len(), LOG_MAX_SIZE); + } + } + + #[test] + fn test_replace() { + let mut sed = populate_log(); + assert_eq!( + sed.find_and_replace(&Message { + tags: None, + prefix: None, + command: Command::PRIVMSG("user".to_string(), "s/will be/has been/".to_string(),), + }) + .unwrap(), + "<user> this is a long message which \x02has been\x02 replaced" + ) + } + + #[test] + fn test_replace_complex() { + let mut sed = populate_log(); + assert_eq!( + sed.find_and_replace(&Message { + tags: None, + prefix: None, + command: Command::PRIVMSG("user".to_string(), "s/(will).*(be)/$2 $1/".to_string(),), + }) + .unwrap(), + "<user> this is a long message which \x02be will\x02 replaced" + ) + } +} + +#[cfg(all(test, feature = "bench"))] +mod bench { + use super::*; + use test::Bencher; + + #[bench] + fn bench_replace(b: &mut Bencher) { + let mut sed = tests::populate_log(); + b.iter(|| { + sed.find_and_replace(&Message { + tags: None, + prefix: None, + command: Command::PRIVMSG("user".to_string(), "s/will be/has been/".to_string()), + }) + }); + } + + #[bench] + fn bench_replace_complex(b: &mut Bencher) { + let mut sed = tests::populate_log(); + b.iter(|| { + sed.find_and_replace(&Message { + tags: None, + prefix: None, + command: Command::PRIVMSG("user".to_string(), "s/(will).*(be)/$2 $1/".to_string()), + }) + }); + } +} |
