From 2902d8734f5c1f036eb340e35e8d1dc7a74c23b6 Mon Sep 17 00:00:00 2001 From: Till Hoeppner Date: Wed, 17 Jun 2015 17:03:56 +0200 Subject: This commit is a little too large. Sorry. :S --- Cargo.toml | 6 +++++ README.md | 48 +++++++++++-------------------------- examples/01.rs | 46 ++++++++++------------------------- src/client.rs | 75 ++++++++++++++++++++++++++++++++++++++++++---------------- src/event.rs | 9 +++++++ src/lib.rs | 16 ++++++++++++- src/message.rs | 2 ++ 7 files changed, 113 insertions(+), 89 deletions(-) create mode 100644 src/event.rs diff --git a/Cargo.toml b/Cargo.toml index 797fcc3..4cb462e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,19 @@ license = "MIT" log = "*" regex = "*" regex_macros = "*" +carboxyl = "*" [features] ssl = ["openssl"] +lints = ["clippy"] [dependencies.openssl] version = "*" optional = true +[dependencies.clippy] +version = "*" +optional = true + [dev-dependencies] env_logger = "*" diff --git a/README.md b/README.md index ff71b86..273fd24 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This library is supposed to be a thin layer over the IRC protocol, doing all the ## Example -Compiles and works with `rustc 1.1.0-nightly (ce1150b9f 2015-05-04)` and `fbb7251493ff5a9bc42f849b9b781e69aef9d184` of this library. +Compiles and tested with `rustc 1.2.0-nightly (8937ec100 2015-06-15)` and `63838165c31397fec199bf99c96497a1169c4d52` of this library. Run with @@ -38,69 +38,49 @@ and join [#botzoo on irc.mozilla.org](http://irc.lc/mozilla/botzoo). ```rust extern crate irsc; extern crate env_logger; -#[cfg(feature = "ssl")] extern crate openssl; -use std::borrow::ToOwned; -use std::borrow::Cow::*; - -use irsc::client::Client; use irsc::color::bold; use irsc::*; use irsc::Command::*; use irsc::Reply::*; -#[cfg(feature = "ssl")] use openssl::ssl::{ Ssl, SslContext, SslMethod }; static NAME: &'static str = "rusticbot"; static DESC: &'static str = "A bot, written in Rust."; -fn callback(server: &mut Client, msg: &Message) { - match Command::from_message(msg) { - Some(PRIVMSG(to, content)) => { - let from = msg.prefix().and_then(Ident::parse).unwrap(); +fn callback(server: &mut Client, msg: &Message, event: Option) { + match event { + Some(Event::Command(PRIVMSG(to, content))) => { + let from = msg.ident().unwrap(); let response = match msg.msg_type { MsgType::Irc => format!("{} wrote: {}", from.nickname, bold(&content)), - MsgType::Ctcp => format!("{} emoted: {}", from.nickname, bold(&content["ACTION ".len()..])) + MsgType::Ctcp => format!("{} emoted: {}", from.nickname, + bold(&content["ACTION ".len()..])) }; // only send to channels, to prevent recursion when we are pm'ed // technically, there are other prefixes than '#', but ignoring them is fine if to.starts_with("#") { - server.send(PRIVMSG(to, Owned(response))).unwrap(); + server.msg(&to, &response); } }, - _ => () - } - - match Reply::from_message(msg) { - Some(RPL_WELCOME(_)) => { - server.send(JOIN(vec![Borrowed("#botzoo")], vec![])).unwrap(); + Some(Event::Reply(RPL_WELCOME(_))) => { + server.join("#botzoo", None); }, _ => () } } -#[cfg(feature = "ssl")] -fn connect(s: &mut Client) { - let ssl = Ssl::new(&SslContext::new(SslMethod::Tlsv1).unwrap()).unwrap(); - s.connect_ssl("irc.mozilla.org".to_owned(), 6697, ssl).unwrap(); -} - -#[cfg(not(feature = "ssl"))] -fn connect(s: &mut Client) { - s.connect("irc.mozilla.org".to_owned(), 6667).unwrap(); -} - fn main() { env_logger::init().unwrap(); let mut s = Client::new(); - connect(&mut s); - s.send(NICK(Borrowed(NAME))).unwrap(); - s.send(USER(Borrowed(NAME), Borrowed("*"), Borrowed("*"), Borrowed(DESC))).unwrap(); + let ssl = Ssl::new(&SslContext::new(SslMethod::Tlsv1).unwrap()).unwrap(); + s.connect_ssl("irc.mozilla.org", 6697, ssl); + s.register(NAME, NAME, DESC); // Dedicate this thread to listening and event processing - s.listen(callback).unwrap(); + s.listen(Some(callback)); } ``` diff --git a/examples/01.rs b/examples/01.rs index 96848f6..2f82839 100644 --- a/examples/01.rs +++ b/examples/01.rs @@ -1,67 +1,47 @@ extern crate irsc; extern crate env_logger; -#[cfg(feature = "ssl")] extern crate openssl; -use std::borrow::ToOwned; -use std::borrow::Cow::*; - -use irsc::client::Client; use irsc::color::bold; use irsc::*; use irsc::Command::*; use irsc::Reply::*; -#[cfg(feature = "ssl")] use openssl::ssl::{ Ssl, SslContext, SslMethod }; static NAME: &'static str = "rusticbot"; static DESC: &'static str = "A bot, written in Rust."; -fn callback(server: &mut Client, msg: &Message) { - match Command::from_message(msg) { - Some(PRIVMSG(to, content)) => { - let from = msg.prefix().and_then(Ident::parse).unwrap(); +fn callback(server: &mut Client, msg: &Message, event: Option) { + match event { + Some(Event::Command(PRIVMSG(to, content))) => { + let from = msg.ident().unwrap(); let response = match msg.msg_type { MsgType::Irc => format!("{} wrote: {}", from.nickname, bold(&content)), - MsgType::Ctcp => format!("{} emoted: {}", from.nickname, bold(&content["ACTION ".len()..])) + MsgType::Ctcp => format!("{} emoted: {}", from.nickname, + bold(&content["ACTION ".len()..])) }; // only send to channels, to prevent recursion when we are pm'ed // technically, there are other prefixes than '#', but ignoring them is fine if to.starts_with("#") { - server.send(PRIVMSG(to, Owned(response))).unwrap(); + server.msg(&to, &response); } }, - _ => () - } - - match Reply::from_message(msg) { - Some(RPL_WELCOME(_)) => { - server.send(JOIN(vec![Borrowed("#botzoo")], vec![])).unwrap(); + Some(Event::Reply(RPL_WELCOME(_))) => { + server.join("#botzoo", None); }, _ => () } } -#[cfg(feature = "ssl")] -fn connect(s: &mut Client) { - let ssl = Ssl::new(&SslContext::new(SslMethod::Tlsv1).unwrap()).unwrap(); - s.connect_ssl("irc.mozilla.org".to_owned(), 6697, ssl).unwrap(); -} - -#[cfg(not(feature = "ssl"))] -fn connect(s: &mut Client) { - s.connect("irc.mozilla.org".to_owned(), 6667).unwrap(); -} - fn main() { env_logger::init().unwrap(); let mut s = Client::new(); - connect(&mut s); - s.send(NICK(Borrowed(NAME))).unwrap(); - s.send(USER(Borrowed(NAME), Borrowed("*"), Borrowed("*"), Borrowed(DESC))).unwrap(); + let ssl = Ssl::new(&SslContext::new(SslMethod::Tlsv1).unwrap()).unwrap(); + s.connect_ssl("irc.mozilla.org", 6697, ssl); + s.register(NAME, NAME, DESC); // Dedicate this thread to listening and event processing - s.listen(callback).unwrap(); + s.listen(Some(callback)); } diff --git a/src/client.rs b/src/client.rs index e573596..d830e1e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,6 +11,9 @@ use std::borrow::Cow; use message::Message; use command::Command; +use command::Command::*; +use reply::Reply; +use event::Event; use ::{ DEBUG, Result, IrscError }; #[cfg(feature = "ssl")] @@ -64,35 +67,38 @@ impl Client { fn handle_event(&mut self, msg: &Message) { let _ = match Command::from_message(msg) { - Some(Command::PING(s1, s2)) => self.send(Command::PONG(s1, s2)), - _ => Ok(()) + Some(PING(s1, s2)) => self.send(PONG(s1, s2)), + _ => Result(Ok(())) }; } - pub fn connect(&mut self, host: String, port: u16) -> Result<()> { + pub fn connect(&mut self, host: &str, port: u16) -> Result<()> { let s = &mut self.stream; - match *s { Some(_) => return Err(IrscError::AlreadyConnected), _ => () }; - *s = match TcpStream::connect((host.as_ref(), port)) { + if s.is_some() { return Result(Err(IrscError::AlreadyConnected)) } + *s = match TcpStream::connect((host, port)) { Ok(tcp) => Some(StreamKind::Plain(tcp)), - Err(e) => return Err(IrscError::Io(e)) + Err(e) => return Result(Err(IrscError::Io(e))) }; - Ok(()) + Result(Ok(())) } #[cfg(feature = "ssl")] - pub fn connect_ssl(&mut self, host: String, port: u16, ssl: Ssl) -> Result<()> { + pub fn connect_ssl(&mut self, host: &str, port: u16, ssl: Ssl) -> Result<()> { let s = &mut self.stream; - match *s { Some(_) => return Err(IrscError::AlreadyConnected), _ => () }; - let tcp_stream = match TcpStream::connect((host.as_ref(), port)) { + if s.is_some() { return Result(Err(IrscError::AlreadyConnected)) }; + let tcp_stream = match TcpStream::connect((host, port)) { Ok(tcp) => Some(tcp), - Err(e) => return Err(IrscError::Io(e)) + Err(e) => return Result(Err(IrscError::Io(e))) }; match tcp_stream.map(|tcp| SslStream::new_from(ssl, tcp)) { - Some(Ok(ssl_stream)) => { *s = Some(StreamKind::Ssl(ssl_stream)); Ok(()) }, - Some(Err(ssl_error)) => Err(IrscError::Ssl(ssl_error)), - None => Err(IrscError::NotConnected) + Some(Ok(ssl_stream)) => { + *s = Some(StreamKind::Ssl(ssl_stream)); + Result(Ok(())) + }, + Some(Err(ssl_error)) => Result(Err(IrscError::Ssl(ssl_error))), + None => Result(Err(IrscError::NotConnected)) } } @@ -103,11 +109,11 @@ impl Client { panic!("Message too long, kittens will die if this runs in release mode. Msg: {}", s) } - self.stream.as_mut() + Result(self.stream.as_mut() .ok_or(IrscError::NotConnected) .and_then(|mut stream| stream.write_all(s.as_bytes()) .and_then(|_| stream.flush()) - .map_err(IrscError::Io)) + .map_err(IrscError::Io))) } pub fn send_message(&mut self, msg: Message) -> Result<()> { @@ -118,13 +124,13 @@ impl Client { self.send_message(cmd.to_message()) } - pub fn listen(&mut self, events: F) -> Result<()> - where F: Fn(&mut Client, &Message) { + pub fn listen(&mut self, events: Option) -> Result<()> + where F: Fn(&mut Client, &Message, Option) { let reader = BufReader::new(match self.stream { Some(StreamKind::Plain(ref s)) => StreamKind::Plain((*s).try_clone().unwrap()), #[cfg(feature = "ssl")] Some(StreamKind::Ssl(ref s)) => StreamKind::Ssl((*s).try_clone().unwrap()), - None => return Err(IrscError::NotConnected) + None => return Result(Err(IrscError::NotConnected)) }); for line in reader.lines() { @@ -132,9 +138,36 @@ impl Client { if let Ok(msg) = line { self.handle_event(&msg); - events(self, &msg); + + // If a callback is desired, try to parse the message + // into a Command or a Reply, and call back. + if let Some(ref on_event) = events { + let event = match Command::from_message(&msg) { + Some(m) => Some(Event::Command(m)), + None => match Reply::from_message(&msg) { + Some(r) => Some(Event::Reply(r)), + None => None + } + }; + + on_event(self, &msg, event); + } } } - Ok(()) + Result(Ok(())) + } + + pub fn join(&mut self, channel: &str, password: Option<&str>) -> Result<()> { + self.send_message(JOIN(vec![channel.into()], password.iter().map(|&p| p.into()).collect()).to_message()) + } + + pub fn msg(&mut self, to: &str, message: &str) -> Result<()> { + self.send_message(PRIVMSG(to.into(), message.into()).to_message()) + } + + pub fn register(&mut self, nick: &str, user: &str, desc: &str) -> Result<()> { + Result(self.send_message(NICK(nick.into()).to_message()).inner() + .and_then(|_| self.send_message(USER(user.into(), Cow::Borrowed("0"), Cow::Borrowed("*"), desc.into()).to_message()).inner())) + } } diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..70ec816 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,9 @@ +use command; +use reply; + +pub enum Event<'a> { + Command(command::Command<'a>), + Reply(reply::Reply<'a>), + Connected, + Disconnected +} diff --git a/src/lib.rs b/src/lib.rs index abcbc50..168f137 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,9 +18,11 @@ pub mod callback; pub mod message; pub mod command; pub mod reply; +pub mod event; use std::io; use std::result; +use std::ops::{ Deref, DerefMut }; #[cfg(feature = "ssl")] use openssl::ssl::error::SslError; @@ -29,6 +31,7 @@ pub use ident::Ident; pub use message::{ Message, MsgType }; pub use command::Command; pub use reply::Reply; +pub use event::Event; pub use client::Client; #[derive(Debug)] @@ -46,6 +49,17 @@ impl From for IrscError { fn from(e: SslError) -> IrscError { IrscError::Ssl(e) } } -pub type Result = result::Result; +pub struct Result(result::Result); + +impl Deref for Result { + type Target = result::Result; + fn deref(&self) -> &result::Result { &self.0 } +} + +impl DerefMut for Result { + fn deref_mut(&mut self) -> &mut result::Result { &mut self.0 } +} + +impl Result { fn inner(self) -> result::Result { self.0 } } pub const DEBUG: bool = cfg!(debug_assertions); diff --git a/src/message.rs b/src/message.rs index dd88b1f..8b897be 100644 --- a/src/message.rs +++ b/src/message.rs @@ -6,6 +6,7 @@ use std::borrow::{ ToOwned }; use std::ops::{ Deref, Range }; use ::IrscError; +use ident::Ident; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum MsgType { @@ -92,6 +93,7 @@ impl Message { pub fn content(&self) -> Vec<&str> { self.content.iter().map(|r| self.range(&r)).collect() } pub fn suffix(&self) -> Option<&str> { self.suffix.as_ref().map(|r| self.range(r)) } pub fn elements(&self) -> Vec<&str> { let mut s = self.content(); self.suffix().map(|f| s.push(f)); s } + pub fn ident(&self) -> Option { self.prefix().and_then(Ident::parse) } } impl FromStr for Message { -- cgit v1.2.3