Refactored for iterators.

This commit is contained in:
Aaron Weiss 2014-11-02 16:16:58 -05:00
parent 0ad276fdae
commit 91aa5bcc6f
6 changed files with 68 additions and 429 deletions

View file

@ -3,3 +3,6 @@
name = "irc"
version = "0.0.1"
authors = ["Aaron Weiss <aaronweiss74@gmail.com>"]
[[example]]
name = "simple"

27
examples/simple.rs Normal file
View file

@ -0,0 +1,27 @@
#![feature(slicing_syntax)]
extern crate irc;
use std::collections::HashMap;
use irc::Server;
use irc::bot::IrcServer;
use irc::data::{Config, Message};
fn main() {
let config = Config {
owners: vec!("awe".into_string()),
nickname: "pickles".into_string(),
username: "pickles".into_string(),
realname: "pickles".into_string(),
password: "".into_string(),
server: "irc.fyrechat.net".into_string(),
port: 6667,
channels: vec!("#vana".into_string()),
options: HashMap::new(),
};
let mut server = IrcServer::new_with_config(config).unwrap();
server.send(Message::new(None, "NICK", vec!["pickles"], None)).unwrap();
server.send(Message::new(None, "USER", vec!["pickles", "0", "*", "pickles"], None)).unwrap();
for message in server {
println!("RCV: {}", message);
}
}

View file

@ -1,113 +1,52 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream};
use {Bot, process};
use {Server, process};
use conn::Connection;
use data::{Config, IrcReader, IrcWriter, Message, User};
pub struct IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader {
pub struct IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
pub conn: Connection<T, U>,
pub config: Config,
process: RefCell<|&IrcBot<T, U>, &str, &str, &[&str]|:'a -> IoResult<()>>,
chanlists: RefCell<HashMap<String, Vec<User>>>,
}
impl<'a> IrcBot<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>> {
pub fn new(process: |&IrcBot<BufferedWriter<TcpStream>, BufferedReader<TcpStream>>, &str, &str, &[&str]|:'a -> IoResult<()>) -> IoResult<IrcBot<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
impl<'a> IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>> {
pub fn new() -> IoResult<IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
let config = try!(Config::load_utf8("config.json"));
let conn = try!(Connection::connect(config.server[], config.port));
Ok(IrcBot {
Ok(IrcServer {
conn: conn,
config: config,
process: RefCell::new(process),
chanlists: RefCell::new(HashMap::new()),
})
}
pub fn new_with_config(config: Config, process: |&IrcBot<BufferedWriter<TcpStream>, BufferedReader<TcpStream>>, &str, &str, &[&str]|:'a -> IoResult<()>) -> IoResult<IrcBot<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
pub fn new_with_config(config: Config) -> IoResult<IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
let conn = try!(Connection::connect(config.server[], config.port));
Ok(IrcBot {
Ok(IrcServer {
conn: conn,
config: config,
process: RefCell::new(process),
chanlists: RefCell::new(HashMap::new()),
})
}
}
impl<'a, T, U> Bot for IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader {
fn send_sanick(&self, old_nick: &str, new_nick: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "SANICK", [old_nick, new_nick], false))
impl<'a, T, U> Iterator<Message> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
fn next(&mut self) -> Option<Message> {
let line_res = self.conn.reader().read_line();
if let Err(e) = line_res { println!("{}", e); return None; }
let line = line_res.unwrap();
let processed = process(line[]);
if let Err(e) = processed { println!("{}", e); return None; }
let (source, command, args) = processed.unwrap();
Some(Message::new(Some(source), command, args, None))
}
}
fn send_nick(&self, nick: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "NICK", [nick], true))
}
fn send_user(&self, username: &str, real_name: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "USER", [username, "0", "*", real_name], true))
}
fn send_join(&self, chan: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "JOIN", [chan], true))
}
fn send_samode(&self, target: &str, mode: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "SAMODE", [target, mode], false))
}
fn send_mode(&self, target: &str, mode: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "MODE", [target, mode], false))
}
fn send_oper(&self, name: &str, password: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "OPER", [name, password], false))
}
fn send_topic(&self, chan: &str, topic: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "TOPIC", [chan, topic], true))
}
fn send_invite(&self, person: &str, chan: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "INVITE", [person, chan], true))
}
fn send_kick(&self, chan: &str, user: &str, msg: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "KICK", [chan, user, msg], true))
}
fn send_kill(&self, nick: &str, msg: &str) -> IoResult<()> {
self.conn.send(Message::new(None, "KILL", [nick, msg], true))
}
fn send_privmsg(&self, chan: &str, msg: &str) -> IoResult<()> {
for line in msg.split_str("\r\n") {
try!(self.conn.send(Message::new(None, "PRIVMSG", [chan, line], true)));
}
Ok(())
}
fn identify(&self) -> IoResult<()> {
try!(self.send_nick(self.config.nickname[]));
self.send_user(self.config.username[], self.config.realname[])
}
fn output(&mut self) -> IoResult<()> {
let mut reader = self.conn.reader();
for line in reader.lines() {
match line {
Ok(ln) => {
let (source, command, args) = try!(process(ln[]));
try!(self.handle_command(source, command, args[]));
println!("{}", ln)
},
Err(e) => {
println!("{}", e);
return Err(e)
},
}
}
Ok(())
impl<'a, T, U> Server<'a> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
fn send(&self, message: Message) -> IoResult<()> {
self.conn.send(message)
}
fn config(&self) -> &Config {
@ -119,267 +58,12 @@ impl<'a, T, U> Bot for IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader {
}
}
impl<'a, T, U> IrcBot<'a, T, U> where T: IrcWriter, U: IrcReader {
pub fn from_connection(conn: Connection<T, U>, process: |&IrcBot<T, U>, &str, &str, &[&str]|:'a -> IoResult<()>) -> IoResult<IrcBot<'a, T, U>> {
Ok(IrcBot {
impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
pub fn from_connection(conn: Connection<T, U>) -> IoResult<IrcServer<'a, T, U>> {
Ok(IrcServer {
conn: conn,
config: try!(Config::load_utf8("config.json")),
process: RefCell::new(process),
chanlists: RefCell::new(HashMap::new()),
})
}
fn handle_command(&self, source: &str, command: &str, args: &[&str]) -> IoResult<()> {
match (command, args) {
("PING", [msg]) => {
try!(self.conn.send(Message::new(None, "PONG", [msg], true)));
},
("376", _) => { // End of MOTD
for chan in self.config.channels.iter() {
try!(self.send_join(chan[]));
}
},
("422", _) => { // Missing MOTD
for chan in self.config.channels.iter() {
try!(self.send_join(chan[]));
}
},
("353", [_, _, chan, users]) => { // /NAMES
for user in users.split_str(" ") {
if !match self.chanlists.borrow_mut().find_mut(&String::from_str(chan)) {
Some(vec) => { vec.push(User::new(user)); true },
None => false,
} {
self.chanlists.borrow_mut().insert(chan.into_string(), vec!(User::new(user)));
}
}
},
("JOIN", [chan]) => {
if let Some(vec) = self.chanlists.borrow_mut().find_mut(&String::from_str(chan)) {
if let Some(i) = source.find('!') {
vec.push(User::new(source[..i]));
}
}
},
("PART", [chan, _]) => {
if let Some(vec) = self.chanlists.borrow_mut().find_mut(&String::from_str(chan)) {
if let Some(i) = source.find('!') {
if let Some(n) = vec.as_slice().position_elem(&User::new(source[..i])) {
vec.swap_remove(n);
}
}
}
},
_ => (),
};
(*self.process.borrow_mut().deref_mut())(self, source, command, args)
}
}
#[cfg(test)]
mod test {
use Bot;
use super::IrcBot;
use std::io::{BufReader, MemWriter};
use std::io::util::{NullReader, NullWriter};
use conn::Connection;
use data::{IrcReader, User};
fn data<U>(conn: Connection<MemWriter, U>) -> String where U: IrcReader {
String::from_utf8(conn.writer().deref_mut().get_ref().to_vec()).unwrap()
}
#[test]
fn from_connection() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
assert!(IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).is_ok());
}
#[test]
fn send_sanick() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_sanick("test", "test2").unwrap();
assert_eq!(data(b.conn), format!("SANICK test test2\r\n"));
}
#[test]
fn send_nick() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_nick("test").unwrap();
assert_eq!(data(b.conn), format!("NICK :test\r\n"));
}
#[test]
fn send_user() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_user("test", "Test").unwrap();
assert_eq!(data(b.conn), format!("USER test 0 * :Test\r\n"));
}
#[test]
fn send_join() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_join("#test").unwrap();
assert_eq!(data(b.conn), format!("JOIN :#test\r\n"));
}
#[test]
fn send_samode() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_samode("#test", "+i").unwrap();
assert_eq!(data(b.conn), format!("SAMODE #test +i\r\n"));
}
#[test]
fn send_mode() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_mode("#test", "+i").unwrap();
assert_eq!(data(b.conn), format!("MODE #test +i\r\n"));
}
#[test]
fn send_oper() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_oper("test", "test").unwrap();
assert_eq!(data(b.conn), format!("OPER test test\r\n"));
}
#[test]
fn send_topic() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_topic("#test", "This is a test topic.").unwrap();
assert_eq!(data(b.conn), format!("TOPIC #test :This is a test topic.\r\n"));
}
#[test]
fn send_invite() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_invite("test2", "#test").unwrap();
assert_eq!(data(b.conn), format!("INVITE test2 :#test\r\n"));
}
#[test]
fn send_kick() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_kick("#test", "test2", "Goodbye.").unwrap();
assert_eq!(data(b.conn), format!("KICK #test test2 :Goodbye.\r\n"));
}
#[test]
fn send_kill() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_kill("test", "Goodbye.").unwrap();
assert_eq!(data(b.conn), format!("KILL test :Goodbye.\r\n"));
}
#[test]
fn send_privmsg() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_privmsg("#test", "This is a test message.").unwrap();
assert_eq!(data(b.conn), format!("PRIVMSG #test :This is a test message.\r\n"));
}
#[test]
fn send_privmsg_multiline() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.send_privmsg("#test", "This is a test message.\r\nIt has two lines.").unwrap();
let mut exp = format!("PRIVMSG #test :This is a test message.\r\n");
exp.push_str("PRIVMSG #test :It has two lines.\r\n");
assert_eq!(data(b.conn), format!("{}", exp));
}
#[test]
fn identify() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.identify().unwrap();
assert_eq!(data(b.conn), format!("NICK :test\r\nUSER test 0 * :test\r\n"));
}
#[test]
fn ping_response() {
let r = BufReader::new(":embyr.tx.us.fyrechat.net PING :01R6\r\n".as_bytes());
let c = Connection::new(MemWriter::new(), r).unwrap();
let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.output().unwrap();
assert_eq!(data(b.conn), format!("PONG :01R6\r\n"));
}
#[test]
fn end_of_motd_response() {
let r = BufReader::new(":embyr.tx.us.fyrechat.net 376 test :End of /MOTD command.\r\n".as_bytes());
let c = Connection::new(MemWriter::new(), r).unwrap();
let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.output().unwrap();
assert_eq!(data(b.conn), format!("JOIN :#test\r\nJOIN :#test2\r\n"));
}
#[test]
fn missing_motd_response() {
let r = BufReader::new(":flare.to.ca.fyrechat.net 422 pickles :MOTD File is missing\r\n".as_bytes());
let c = Connection::new(MemWriter::new(), r).unwrap();
let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.output().unwrap();
assert_eq!(data(b.conn), format!("JOIN :#test\r\nJOIN :#test2\r\n"));
}
#[test]
fn generate_user_list() {
let r = BufReader::new(":flare.to.ca.fyrechat.net 353 test @ #test :test test2 test3\r\n".as_bytes());
let c = Connection::new(NullWriter, r).unwrap();
let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.output().unwrap();
let vec_res = match b.chanlists.borrow_mut().find_mut(&String::from_str("#test")) {
Some(v) => Ok(v.clone()),
None => Err("Could not find vec for channel."),
};
assert!(vec_res.is_ok());
let vec = vec_res.unwrap();
assert_eq!(vec, vec![User::new("test"), User::new("test2"), User::new("test3")]);
}
#[test]
fn add_to_user_list() {
let r = BufReader::new(":flare.to.ca.fyrechat.net 353 test @ #test :test test2\r\n:test3!test@test JOIN :#test\r\n".as_bytes());
let c = Connection::new(NullWriter, r).unwrap();
let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.output().unwrap();
let vec_res = match b.chanlists.borrow_mut().find_mut(&String::from_str("#test")) {
Some(v) => Ok(v.clone()),
None => Err("Could not find vec for channel."),
};
assert!(vec_res.is_ok());
let vec = vec_res.unwrap();
assert_eq!(vec, vec![User::new("test"), User::new("test2"), User::new("test3")]);
}
#[test]
fn remove_from_user_list() {
let r = BufReader::new(":flare.to.ca.fyrechat.net 353 test @ #test :test test2 test3\r\n:test3!test@test PART #test :\r\n".as_bytes());
let c = Connection::new(NullWriter, r).unwrap();
let mut b = IrcBot::from_connection(c, |_, _, _, _| { Ok(()) }).unwrap();
b.output().unwrap();
let vec_res = match b.chanlists.borrow_mut().find_mut(&String::from_str("#test")) {
Some(v) => Ok(v.clone()),
None => Err("Could not find vec for channel."),
};
assert!(vec_res.is_ok());
let vec = vec_res.unwrap();
// n.b. ordering is not guaranteed, this only ought to hold because we're removing the last user
assert_eq!(vec, vec![User::new("test"), User::new("test2")]);
}
}

View file

@ -35,8 +35,8 @@ impl<T, U> Connection<T, U> where T: IrcWriter, U: IrcReader {
send.push_str(msg.args.init().connect(" ")[]);
}
send.push_str(" ");
if msg.colon_flag { send.push_str(":") }
send.push_str(*msg.args.last().unwrap());
if msg.colon_flag.is_some() { send.push_str(":") }
send.push_str(msg.args.last().unwrap()[]);
send.push_str("\r\n");
self.send_internal(send[])
}
@ -49,35 +49,3 @@ impl<T, U> Connection<T, U> where T: IrcWriter, U: IrcReader {
self.reader.borrow_mut()
}
}
#[cfg(test)]
mod test {
use super::Connection;
use std::io::MemWriter;
use std::io::util::NullReader;
use data::{IrcReader, Message};
fn data<U>(conn: Connection<MemWriter, U>) -> String where U: IrcReader {
String::from_utf8(conn.writer().deref_mut().get_ref().to_vec()).unwrap()
}
#[test]
fn new_connection() {
assert!(Connection::new(MemWriter::new(), NullReader).is_ok());
}
#[test]
fn send_internal() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
c.send_internal("string of text").unwrap();
assert_eq!(data(c), format!("string of text"));
}
#[test]
fn send() {
let c = Connection::new(MemWriter::new(), NullReader).unwrap();
let args = ["flare.to.ca.fyrechat.net"];
c.send(Message::new(None, "PING", args, true)).unwrap();
assert_eq!(data(c), format!("PING :flare.to.ca.fyrechat.net\r\n"));
}
}

View file

@ -54,19 +54,19 @@ impl AccessLevel {
}
#[deriving(Show, PartialEq)]
pub struct Message<'a> {
pub source: Option<&'a str>,
pub command: &'a str,
pub args: &'a [&'a str],
pub colon_flag: bool,
pub struct Message {
pub source: Option<String>,
pub command: String,
pub args: Vec<String>,
pub colon_flag: Option<bool>,
}
impl<'a> Message<'a> {
pub fn new(source: Option<&'a str>, command: &'a str, args: &'a [&'a str], colon_flag: bool) -> Message<'a> {
impl<'a> Message {
pub fn new(source: Option<&'a str>, command: &'a str, args: Vec<&'a str>, colon_flag: Option<bool>) -> Message {
Message {
source: source,
command: command,
args: args,
source: source.map(|s: &str| s.into_string()),
command: command.into_string(),
args: args.into_iter().map(|s: &str| s.into_string()).collect(),
colon_flag: colon_flag,
}
}
@ -104,32 +104,3 @@ impl Config {
self.owners[].contains(&String::from_str(nickname))
}
}
#[cfg(test)]
mod test {
use super::{Config, Message};
#[test]
fn new_message() {
let args = ["flare.to.ca.fyrechat.net"];
let m = Message::new(None, "PING", args, true);
assert_eq!(m, Message {
source: None,
command: "PING",
args: args,
colon_flag: true,
});
}
#[test]
fn load_config() {
assert!(Config::load_utf8("config.json").is_ok());
}
#[test]
fn is_owner() {
let cfg = Config::load_utf8("config.json").unwrap();
assert!(cfg.is_owner("test"));
assert!(!cfg.is_owner("test2"));
}
}

View file

@ -6,28 +6,14 @@ extern crate regex;
extern crate serialize;
use std::io::{InvalidInput, IoError, IoResult};
use data::Config;
pub mod bot;
pub mod conn;
pub mod data;
pub trait Bot {
fn send_sanick(&self, old_nick: &str, new_nick: &str) -> IoResult<()>;
fn send_nick(&self, nick: &str) -> IoResult<()>;
fn send_user(&self, username: &str, real_name: &str) -> IoResult<()>;
fn send_join(&self, chan: &str) -> IoResult<()>;
fn send_samode(&self, target: &str, mode: &str) -> IoResult<()>;
fn send_mode(&self, target: &str, mode: &str) -> IoResult<()>;
fn send_oper(&self, name: &str, password: &str) -> IoResult<()>;
fn send_topic(&self, chan: &str, topic: &str) -> IoResult<()>;
fn send_invite(&self, person: &str, chan: &str) -> IoResult<()>;
fn send_privmsg(&self, chan: &str, msg: &str) -> IoResult<()>;
fn send_kick(&self, chan: &str, user: &str, msg: &str) -> IoResult<()>;
fn send_kill(&self, nick: &str, msg: &str) -> IoResult<()>;
fn identify(&self) -> IoResult<()>;
fn output(&mut self) -> IoResult<()>;
fn config(&self) -> &Config;
pub trait Server<'a>: Iterator<data::Message> {
fn send(&self, message: data::Message) -> IoResult<()>;
fn config(&self) -> &data::Config;
fn get_users(&self, chan: &str) -> Option<Vec<data::User>>;
}