diff --git a/Cargo.toml b/Cargo.toml index 83f1052..c538663 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,3 @@ name = "irc" version = "0.0.1" authors = ["Aaron Weiss "] - -[[example]] -name = "simple" diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index 566a78c..0000000 --- a/examples/simple.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![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); - } -} diff --git a/mktestconfig.sh b/mktestconfig.sh deleted file mode 100644 index c650ad8..0000000 --- a/mktestconfig.sh +++ /dev/null @@ -1 +0,0 @@ -echo "{\"owners\": [\"test\"],\"nickname\": \"test\",\"username\": \"test\",\"realname\": \"test\",\"password\": \"\",\"server\": \"irc.fyrechat.net\",\"port\": 6667,\"channels\": [\"#test\", \"#test2\"],\"options\": {}}" > config.json diff --git a/src/bot.rs b/src/bot.rs deleted file mode 100644 index 4e1e015..0000000 --- a/src/bot.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream}; -use {Server, process}; -use conn::Connection; -use data::{Config, IrcReader, IrcWriter, Message, User}; - -pub struct IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { - pub conn: Connection, - pub config: Config, - chanlists: RefCell>>, -} - -impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { - pub fn new() -> IoResult, BufferedReader>> { - let config = try!(Config::load_utf8("config.json")); - let conn = try!(Connection::connect(config.server[], config.port)); - Ok(IrcServer { - conn: conn, - config: config, - chanlists: RefCell::new(HashMap::new()), - }) - } - - pub fn new_with_config(config: Config) -> IoResult, BufferedReader>> { - let conn = try!(Connection::connect(config.server[], config.port)); - Ok(IrcServer { - conn: conn, - config: config, - chanlists: RefCell::new(HashMap::new()), - }) - } -} - -impl<'a, T, U> Iterator for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { - fn next(&mut self) -> Option { - 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)) - } -} - -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 { - &self.config - } - - fn get_users(&self, chan: &str) -> Option> { - self.chanlists.borrow_mut().find_copy(&chan.into_string()) - } -} - -impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { - pub fn from_connection(conn: Connection) -> IoResult> { - Ok(IrcServer { - conn: conn, - config: try!(Config::load_utf8("config.json")), - chanlists: RefCell::new(HashMap::new()), - }) - } -} diff --git a/src/conn.rs b/src/conn.rs index 011b4d5..e69de29 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -1,51 +0,0 @@ -use std::cell::{RefCell, RefMut}; -use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream, Writer}; -use data::{IrcReader, IrcWriter, Message}; - -pub struct Connection where T: IrcWriter, U: IrcReader { - writer: RefCell, - reader: RefCell, -} - -impl Connection, BufferedReader> { - pub fn connect(host: &str, port: u16) -> IoResult, BufferedReader>> { - let socket = try!(TcpStream::connect(host, port)); - Connection::new(BufferedWriter::new(socket.clone()), BufferedReader::new(socket.clone())) - } -} - -impl Connection where T: IrcWriter, U: IrcReader { - pub fn new(writer: T, reader: U) -> IoResult> { - Ok(Connection { - writer: RefCell::new(writer), - reader: RefCell::new(reader), - }) - } - - fn send_internal(&self, msg: &str) -> IoResult<()> { - let mut send = self.writer.borrow_mut(); - try!(send.write_str(msg)); - send.flush() - } - - pub fn send(&self, msg: Message) -> IoResult<()> { - let mut send = msg.command.to_string(); - if msg.args.init().len() > 0 { - send.push_str(" "); - send.push_str(msg.args.init().connect(" ")[]); - } - send.push_str(" "); - 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[]) - } - - pub fn writer<'a>(&'a self) -> RefMut<'a, T> { - self.writer.borrow_mut() - } - - pub fn reader<'a>(&'a self) -> RefMut<'a, U> { - self.reader.borrow_mut() - } -} diff --git a/src/data.rs b/src/data.rs deleted file mode 100644 index 872f1d5..0000000 --- a/src/data.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::collections::HashMap; -use std::io::fs::File; -use std::io::{InvalidInput, IoError, IoResult}; -use serialize::json::{decode}; - -pub trait IrcWriter: Writer + Sized + 'static {} -impl IrcWriter for T where T: Writer + Sized + 'static {} -pub trait IrcReader: Buffer + Sized + 'static {} -impl IrcReader for T where T: Buffer + Sized + 'static {} - -#[deriving(PartialEq, Clone, Show)] -pub struct User { - name: String, - access_level: AccessLevel, -} - -impl User { - pub fn new(name: &str) -> User { - let rank = AccessLevel::from_str(name); - User { - name: if let Member = rank { - name.into_string() - } else { - name[1..].into_string() - }, - access_level: rank, - } - } -} - -#[deriving(PartialEq, Clone, Show)] -pub enum AccessLevel { - Owner, - Admin, - Oper, - HalfOp, - Voice, - Member, -} - -impl AccessLevel { - pub fn from_str(s: &str) -> AccessLevel { - if s.len() == 0 { Member } else { - match s.char_at(0) { - '~' => Owner, - '&' => Admin, - '@' => Oper, - '%' => HalfOp, - '+' => Voice, - _ => Member, - } - } - } -} - -#[deriving(Show, PartialEq)] -pub struct Message { - pub source: Option, - pub command: String, - pub args: Vec, - pub colon_flag: Option, -} - -impl<'a> Message { - pub fn new(source: Option<&'a str>, command: &'a str, args: Vec<&'a str>, colon_flag: Option) -> Message { - Message { - 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, - } - } -} - -#[deriving(Clone, Decodable)] -pub struct Config { - pub owners: Vec, - pub nickname: String, - pub username: String, - pub realname: String, - pub password: String, - pub server: String, - pub port: u16, - pub channels: Vec, - pub options: HashMap, -} - -impl Config { - pub fn load(path: Path) -> IoResult { - let mut file = try!(File::open(&path)); - let data = try!(file.read_to_string()); - decode(data[]).map_err(|e| IoError { - kind: InvalidInput, - desc: "Decoder error", - detail: Some(e.to_string()), - }) - } - - pub fn load_utf8(path: &str) -> IoResult { - Config::load(Path::new(path)) - } - - pub fn is_owner(&self, nickname: &str) -> bool { - self.owners[].contains(&String::from_str(nickname)) - } -} diff --git a/src/lib.rs b/src/lib.rs index e2a53ce..7e3d85e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ +//! A simple, thread-safe IRC client library. +#![crate_name = "irc"] +#![crate_type = "lib"] + #![feature(if_let)] #![feature(phase)] #![feature(slicing_syntax)] @@ -5,66 +9,6 @@ extern crate regex; #[phase(plugin)] extern crate regex_macros; extern crate serialize; -use std::io::{InvalidInput, IoError, IoResult}; - -pub mod bot; -pub mod conn; -pub mod data; - -pub trait Server<'a>: Iterator { - fn send(&self, message: data::Message) -> IoResult<()>; - fn config(&self) -> &data::Config; - fn get_users(&self, chan: &str) -> Option>; -} - -fn process(msg: &str) -> IoResult<(&str, &str, Vec<&str>)> { - let reg = regex!(r"^(?::([^ ]+) )?([^ ]+)(.*)"); - let cap = match reg.captures(msg) { - Some(x) => x, - None => return Err(IoError { - kind: InvalidInput, - desc: "Failed to parse line", - detail: None, - }), - }; - let source = cap.at(1); - let command = cap.at(2); - let args = parse_args(cap.at(3)); - Ok((source, command, args)) -} - -fn parse_args(line: &str) -> Vec<&str> { - let reg = regex!(r" ([^: ]+)| :([^\r\n]*)[\r\n]*$"); - reg.captures_iter(line).map(|cap| { - match cap.at(1) { - "" => cap.at(2), - x => x, - } - }).collect() -} - -#[cfg(test)] -mod test { - use super::{process, parse_args}; - - #[test] - fn process_line() { - let res = process(":flare.to.ca.fyrechat.net 353 pickles = #pickles :pickles awe\r\n").unwrap(); - let (source, command, args) = res; - assert_eq!(source, "flare.to.ca.fyrechat.net"); - assert_eq!(command, "353"); - assert_eq!(args, vec!["pickles", "=", "#pickles", "pickles awe"]); - - let res = process("PING :flare.to.ca.fyrechat.net\r\n").unwrap(); - let (source, command, args) = res; - assert_eq!(source, ""); - assert_eq!(command, "PING"); - assert_eq!(args, vec!["flare.to.ca.fyrechat.net"]); - } - - #[test] - fn process_args() { - let res = parse_args("PRIVMSG #vana :hi"); - assert_eq!(res, vec!["#vana", "hi"]) - } -} +mod conn; +pub mod server; +mod utils; diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..324e70c --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,53 @@ +use std::io::{InvalidInput, IoError, IoResult}; + +fn process(msg: &str) -> IoResult<(&str, &str, Vec<&str>)> { + let reg = regex!(r"^(?::([^ ]+) )?([^ ]+)(.*)"); + let cap = match reg.captures(msg) { + Some(x) => x, + None => return Err(IoError { + kind: InvalidInput, + desc: "Failed to parse line", + detail: None, + }), + }; + let source = cap.at(1); + let command = cap.at(2); + let args = parse_args(cap.at(3)); + Ok((source, command, args)) +} + +fn parse_args(line: &str) -> Vec<&str> { + let reg = regex!(r" ([^: ]+)| :([^\r\n]*)[\r\n]*$"); + reg.captures_iter(line).map(|cap| { + match cap.at(1) { + "" => cap.at(2), + x => x, + } + }).collect() +} + +#[cfg(test)] +mod test { + use super::{process, parse_args}; + + #[test] + fn process_line() { + let res = process(":flare.to.ca.fyrechat.net 353 pickles = #pickles :pickles awe\r\n").unwrap(); + let (source, command, args) = res; + assert_eq!(source, "flare.to.ca.fyrechat.net"); + assert_eq!(command, "353"); + assert_eq!(args, vec!["pickles", "=", "#pickles", "pickles awe"]); + + let res = process("PING :flare.to.ca.fyrechat.net\r\n").unwrap(); + let (source, command, args) = res; + assert_eq!(source, ""); + assert_eq!(command, "PING"); + assert_eq!(args, vec!["flare.to.ca.fyrechat.net"]); + } + + #[test] + fn process_args() { + let res = parse_args("PRIVMSG #vana :hi"); + assert_eq!(res, vec!["#vana", "hi"]) + } +}