Merge pull request #1 from aaronweiss74/redesign

Iterator-based redesign.
This commit is contained in:
Aaron Weiss 2014-11-03 03:14:34 -05:00
commit b60236a0fd
14 changed files with 1105 additions and 287 deletions

View file

@ -1,7 +1,5 @@
language: rust language: rust
script: script:
- chmod +x mktestconfig.sh
- ./mktestconfig.sh
- cargo build --verbose - cargo build --verbose
- cargo test --verbose - cargo test --verbose
- cargo doc --verbose - cargo doc --verbose

View file

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

View file

@ -2,9 +2,9 @@
extern crate irc; extern crate irc;
use std::collections::HashMap; use std::collections::HashMap;
use irc::Server; use irc::data::config::Config;
use irc::bot::IrcServer; use irc::server::{IrcServer, Server};
use irc::data::{Config, Message}; use irc::server::utils::identify;
fn main() { fn main() {
let config = Config { let config = Config {
@ -18,10 +18,9 @@ fn main() {
channels: vec!("#vana".into_string()), channels: vec!("#vana".into_string()),
options: HashMap::new(), options: HashMap::new(),
}; };
let mut server = IrcServer::new_with_config(config).unwrap(); let server = IrcServer::from_config(config).unwrap();
server.send(Message::new(None, "NICK", vec!["pickles"], None)).unwrap(); identify(&server).unwrap();
server.send(Message::new(None, "USER", vec!["pickles", "0", "*", "pickles"], None)).unwrap(); for message in server.iter() {
for message in server { print!("{}", message.into_string());
println!("RCV: {}", message);
} }
} }

View file

@ -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

View file

@ -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<T, U>,
pub config: Config,
chanlists: RefCell<HashMap<String, Vec<User>>>,
}
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(IrcServer {
conn: conn,
config: config,
chanlists: RefCell::new(HashMap::new()),
})
}
pub fn new_with_config(config: Config) -> IoResult<IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
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<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))
}
}
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<Vec<User>> {
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<T, U>) -> IoResult<IrcServer<'a, T, U>> {
Ok(IrcServer {
conn: conn,
config: try!(Config::load_utf8("config.json")),
chanlists: RefCell::new(HashMap::new()),
})
}
}

View file

@ -1,51 +1,47 @@
use std::cell::{RefCell, RefMut}; //! Thread-safe connections on any IrcWriters and IrcReaders
use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream, Writer}; #![experimental]
use data::{IrcReader, IrcWriter, Message}; use std::sync::Mutex;
use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream};
use data::kinds::{IrcWriter, IrcReader};
use data::message::Message;
/// A thread-safe connection
#[experimental]
pub struct Connection<T, U> where T: IrcWriter, U: IrcReader { pub struct Connection<T, U> where T: IrcWriter, U: IrcReader {
writer: RefCell<T>, writer: Mutex<T>,
reader: RefCell<U>, reader: Mutex<U>,
} }
impl Connection<BufferedWriter<TcpStream>, BufferedReader<TcpStream>> { impl Connection<BufferedWriter<TcpStream>, BufferedReader<TcpStream>> {
/// Creates a thread-safe TCP connection to the specified server
#[experimental]
pub fn connect(host: &str, port: u16) -> IoResult<Connection<BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> { pub fn connect(host: &str, port: u16) -> IoResult<Connection<BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
let socket = try!(TcpStream::connect(host, port)); let socket = try!(TcpStream::connect(host, port));
Connection::new(BufferedWriter::new(socket.clone()), BufferedReader::new(socket.clone())) Ok(Connection::new(BufferedWriter::new(socket.clone()), BufferedReader::new(socket)))
} }
} }
impl<T, U> Connection<T, U> where T: IrcWriter, U: IrcReader { impl<T, U> Connection<T, U> where T: IrcWriter, U: IrcReader {
pub fn new(writer: T, reader: U) -> IoResult<Connection<T, U>> { /// Creates a new connection from any arbitrary IrcWriter and IrcReader
Ok(Connection { #[experimental]
writer: RefCell::new(writer), pub fn new(writer: T, reader: U) -> Connection<T, U> {
reader: RefCell::new(reader), Connection {
}) writer: Mutex::new(writer),
reader: Mutex::new(reader),
}
} }
fn send_internal(&self, msg: &str) -> IoResult<()> { /// Sends a Message over this connection
let mut send = self.writer.borrow_mut(); #[experimental]
try!(send.write_str(msg)); pub fn send(&self, message: Message) -> IoResult<()> {
let mut send = self.writer.lock();
try!(send.write_str(message.into_string()[]));
send.flush() send.flush()
} }
pub fn send(&self, msg: Message) -> IoResult<()> { /// Receives a single line from this connection
let mut send = msg.command.to_string(); #[experimental]
if msg.args.init().len() > 0 { pub fn recv(&self) -> IoResult<String> {
send.push_str(" "); self.reader.lock().read_line()
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()
} }
} }

View file

@ -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<T> IrcWriter for T where T: Writer + Sized + 'static {}
pub trait IrcReader: Buffer + Sized + 'static {}
impl<T> 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<String>,
pub command: String,
pub args: Vec<String>,
pub colon_flag: Option<bool>,
}
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.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<String>,
pub nickname: String,
pub username: String,
pub realname: String,
pub password: String,
pub server: String,
pub port: u16,
pub channels: Vec<String>,
pub options: HashMap<String, String>,
}
impl Config {
pub fn load(path: Path) -> IoResult<Config> {
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> {
Config::load(Path::new(path))
}
pub fn is_owner(&self, nickname: &str) -> bool {
self.owners[].contains(&String::from_str(nickname))
}
}

768
src/data/command.rs Normal file
View file

@ -0,0 +1,768 @@
//! Enumeration of all available client commands
#![stable]
use std::io::{InvalidInput, IoError, IoResult};
use data::message::Message;
/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812)
#[stable]
#[deriving(Show, PartialEq)]
pub enum Command<'a> {
// 3.1 Connection Registration
/// PASS password
PASS(&'a str),
/// NICK nickname
NICK(&'a str),
/// USER user mode * realname
USER(&'a str, &'a str, &'a str),
/// OPER name password
OPER(&'a str, &'a str),
/// MODE nickname modes
/// MODE channel modes [modeparams]
MODE(&'a str, &'a str, Option<&'a str>),
/// SERVICE nickname reserved distribution type reserved info
SERVICE(&'a str, &'a str, &'a str, &'a str, &'a str, &'a str),
/// QUIT Quit Message
QUIT(Option<&'a str>),
/// SQUIT server comment
SQUIT(&'a str, &'a str),
// 3.2 Channel operations
/// JOIN chanlist [chankeys]
JOIN(&'a str, Option<&'a str>),
/// PART chanlist [Part Message]
PART(&'a str, Option<&'a str>),
// MODE is already defined.
// MODE(&'a str, &'a str, Option<&'a str>),
/// TOPIC channel [topic]
TOPIC(&'a str, Option<&'a str>),
/// NAMES [chanlist [target]]
NAMES(Option<&'a str>, Option<&'a str>),
/// LIST [chanlist [target]]
LIST(Option<&'a str>, Option<&'a str>),
/// INVITE nickname channel
INVITE(&'a str, &'a str),
/// KICK chanlist userlist [comment]
KICK(&'a str, &'a str, Option<&'a str>),
// 3.3 Sending messages
/// PRIVMSG msgtarget text to be sent
PRIVMSG(&'a str, &'a str),
/// NOTICE msgtarget text
NOTICE(&'a str, &'a str),
// 3.4 Server queries and commands
/// MOTD [target]
MOTD(Option<&'a str>),
/// LUSERS [mask [target]]
LUSERS(Option<&'a str>, Option<&'a str>),
/// VERSION [target]
VERSION(Option<&'a str>),
/// STATS [query [target]]
STATS(Option<&'a str>, Option<&'a str>),
/// LINKS [[remote server] server mask]
LINKS(Option<&'a str>, Option<&'a str>),
/// TIME [target]
TIME(Option<&'a str>),
/// CONNECT target server port [remote server]
CONNECT(&'a str, &'a str, Option<&'a str>),
/// TRACE [target]
TRACE(Option<&'a str>),
/// ADMIN [target]
ADMIN(Option<&'a str>),
/// INFO [target]
INFO(Option<&'a str>),
// 3.5 Service Query and Commands
/// SERVLIST [mask [type]]
SERVLIST(Option<&'a str>, Option<&'a str>),
/// SQUERY servicename text
SQUERY(&'a str, &'a str),
// 3.6 User based queries
/// WHO [mask ["o"]]
WHO(Option<&'a str>, Option<bool>),
/// WHOIS [target] masklist
WHOIS(Option<&'a str>, &'a str),
/// WHOWAS nicklist [count [target]]
WHOWAS(&'a str, Option<&'a str>, Option<&'a str>),
// 3.7 Miscellaneous messages
/// KILL nickname comment
KILL(&'a str, &'a str),
/// PING server1 [server2]
PING(&'a str, Option<&'a str>),
/// PONG server [server2]
PONG(&'a str, Option<&'a str>),
/// ERROR error message
ERROR(&'a str),
// 4 Optional Features
/// AWAY [text]
AWAY(Option<&'a str>),
/// REHASH
REHASH,
/// DIE
DIE,
/// RESTART
RESTART,
/// SUMMON user [target [channel]]
SUMMON(&'a str, Option<&'a str>, Option<&'a str>),
/// USERS [target]
USERS(Option<&'a str>),
/// WALLOPS Text to be sent
WALLOPS(&'a str),
/// USERHOST space-separated nicklist
USERHOST(Vec<&'a str>),
/// ISON space-separated nicklist
ISON(Vec<&'a str>),
// Non-RFC commands from InspIRCd
/// SAJOIN nickname channel
SAJOIN(&'a str, &'a str),
/// SAMODE target modes [modeparams]
SAMODE(&'a str, &'a str, Option<&'a str>),
/// SANICK old nickname new nickname
SANICK(&'a str, &'a str),
/// SAPART nickname reason
SAPART(&'a str, &'a str),
/// SAQUIT nickname reason
SAQUIT(&'a str, &'a str),
}
impl<'a> Command<'a> {
/// Converts a Command into a Message
#[stable]
pub fn to_message(self) -> Message {
match self {
PASS(p) => Message::new(None, "PASS", None, Some(p)),
NICK(n) => Message::new(None, "NICK", None, Some(n)),
USER(u, m, r) => Message::new(None, "USER", Some(vec![u, m, "*"]), Some(r)),
OPER(u, p) => Message::new(None, "OPER", Some(vec![u]), Some(p)),
MODE(t, m, Some(p)) => Message::new(None, "MODE", Some(vec![t, m, p]), None),
MODE(t, m, None) => Message::new(None, "MODE", Some(vec![t, m]), None),
SERVICE(n, r, d, t, re, i) => Message::new(None, "SERVICE",
Some(vec![n, r, d, t, re]), Some(i)),
QUIT(Some(m)) => Message::new(None, "QUIT", None, Some(m)),
QUIT(None) => Message::new(None, "QUIT", None, None),
SQUIT(s, c) => Message::new(None, "SQUIT", Some(vec![s]), Some(c)),
JOIN(c, Some(k)) => Message::new(None, "JOIN", Some(vec![c, k]), None),
JOIN(c, None) => Message::new(None, "JOIN", Some(vec![c]), None),
PART(c, Some(m)) => Message::new(None, "PART", Some(vec![c]), Some(m)),
PART(c, None) => Message::new(None, "PART", Some(vec![c]), None),
TOPIC(c, Some(t)) => Message::new(None, "TOPIC", Some(vec![c]), Some(t)),
TOPIC(c, None) => Message::new(None, "TOPIC", Some(vec![c]), None),
NAMES(Some(c), Some(t)) => Message::new(None, "NAMES", Some(vec![c]), Some(t)),
NAMES(Some(c), None) => Message::new(None, "NAMES", Some(vec![c]), None),
NAMES(None, _) => Message::new(None, "NAMES", None, None),
LIST(Some(c), Some(t)) => Message::new(None, "LIST", Some(vec![c]), Some(t)),
LIST(Some(c), None) => Message::new(None, "LIST", Some(vec![c]), None),
LIST(None, _) => Message::new(None, "LIST", None, None),
INVITE(n, c) => Message::new(None, "INVITE", Some(vec![n, c]), None),
KICK(c, n, Some(r)) => Message::new(None, "KICK", Some(vec![c, n]), Some(r)),
KICK(c, n, None) => Message::new(None, "KICK", Some(vec![c, n]), None),
PRIVMSG(t, m) => Message::new(None, "PRIVMSG", Some(vec![t]), Some(m)),
NOTICE(t, m) => Message::new(None, "NOTICE", Some(vec![t]), Some(m)),
MOTD(Some(t)) => Message::new(None, "MOTD", None, Some(t)),
MOTD(None) => Message::new(None, "MOTD", None, None),
LUSERS(Some(m), Some(t)) => Message::new(None, "LUSERS", Some(vec![m]), Some(t)),
LUSERS(Some(m), None) => Message::new(None, "LUSERS", Some(vec![m]), None),
LUSERS(None, _) => Message::new(None, "LUSERS", None, None),
VERSION(Some(t)) => Message::new(None, "VERSION", None, Some(t)),
VERSION(None) => Message::new(None, "VERSION", None, None),
STATS(Some(q), Some(t)) => Message::new(None, "STATS", Some(vec![q]), Some(t)),
STATS(Some(q), None) => Message::new(None, "STATS", Some(vec![q]), None),
STATS(None, _) => Message::new(None, "STATS", None, None),
LINKS(Some(r), Some(s)) => Message::new(None, "LINKS", Some(vec![r]), Some(s)),
LINKS(None, Some(s)) => Message::new(None, "LINKS", None, Some(s)),
LINKS(_, None) => Message::new(None, "LINKS", None, None),
TIME(Some(t)) => Message::new(None, "TIME", None, Some(t)),
TIME(None) => Message::new(None, "TIME", None, None),
CONNECT(t, p, Some(r)) => Message::new(None, "CONNECT", Some(vec![t, p]), Some(r)),
CONNECT(t, p, None) => Message::new(None, "CONNECT", Some(vec![t, p]), None),
TRACE(Some(t)) => Message::new(None, "TRACE", None, Some(t)),
TRACE(None) => Message::new(None, "TRACE", None, None),
ADMIN(Some(t)) => Message::new(None, "ADMIN", None, Some(t)),
ADMIN(None) => Message::new(None, "ADMIN", None, None),
INFO(Some(t)) => Message::new(None, "INFO", None, Some(t)),
INFO(None) => Message::new(None, "INFO", None, None),
SERVLIST(Some(m), Some(t)) => Message::new(None, "SERVLIST", Some(vec![m]), Some(t)),
SERVLIST(Some(m), None) => Message::new(None, "SERVLIST", Some(vec![m]), None),
SERVLIST(None, _) => Message::new(None, "SERVLIST", None, None),
SQUERY(s, t) => Message::new(None, "SQUERY", Some(vec![s, t]), None),
WHO(Some(s), Some(true)) => Message::new(None, "WHO", Some(vec![s, "o"]), None),
WHO(Some(s), _) => Message::new(None, "WHO", Some(vec![s]), None),
WHO(None, _) => Message::new(None, "WHO", None, None),
WHOIS(Some(t), m) => Message::new(None, "WHOIS", Some(vec![t, m]), None),
WHOIS(None, m) => Message::new(None, "WHOIS", Some(vec![m]), None),
WHOWAS(n, Some(c), Some(t)) => Message::new(None, "WHOWAS", Some(vec![n, c]), Some(t)),
WHOWAS(n, Some(c), None) => Message::new(None, "WHOWAS", Some(vec![n, c]), None),
WHOWAS(n, None, _) => Message::new(None, "WHOWAS", Some(vec![n]), None),
KILL(n, c) => Message::new(None, "KILL", Some(vec![n]), Some(c)),
PING(s, Some(t)) => Message::new(None, "PING", Some(vec![s]), Some(t)),
PING(s, None) => Message::new(None, "PING", None, Some(s)),
PONG(s, Some(t)) => Message::new(None, "PONG", Some(vec![s]), Some(t)),
PONG(s, None) => Message::new(None, "PONG", None, Some(s)),
ERROR(m) => Message::new(None, "ERROR", None, Some(m)),
AWAY(Some(m)) => Message::new(None, "AWAY", None, Some(m)),
AWAY(None) => Message::new(None, "AWAY", None, None),
REHASH => Message::new(None, "REHASH", None, None),
DIE => Message::new(None, "DIE", None, None),
RESTART => Message::new(None, "RESTART", None, None),
SUMMON(u, Some(t), Some(c)) => Message::new(None, "SUMMON", Some(vec![u, t]), Some(c)),
SUMMON(u, Some(t), None) => Message::new(None, "SUMMON", Some(vec![u, t]), None),
SUMMON(u, None, _) => Message::new(None, "SUMMON", Some(vec![u]), None),
USERS(Some(t)) => Message::new(None, "USERS", None, Some(t)),
USERS(None) => Message::new(None, "USERS", None, None),
WALLOPS(t) => Message::new(None, "WALLOPS", None, Some(t)),
USERHOST(u) => Message::new(None, "USERHOST", Some(u), None),
ISON(u) => Message::new(None, "ISON", Some(u), None),
SAJOIN(n, c) => Message::new(None, "SAJOIN", Some(vec![n, c]), None),
SAMODE(t, m, Some(p)) => Message::new(None, "SAMODE", Some(vec![t, m, p]), None),
SAMODE(t, m, None) => Message::new(None, "SAMODE", Some(vec![t, m]), None),
SANICK(o, n) => Message::new(None, "SANICK", Some(vec![o, n]), None),
SAPART(c, r) => Message::new(None, "SAPART", Some(vec![c]), Some(r)),
SAQUIT(c, r) => Message::new(None, "SAQUIT", Some(vec![c]), Some(r)),
}
}
/// Converts a Message into a Command
#[stable]
pub fn from_message(m: &'a Message) -> IoResult<Command<'a>> {
/* FIXME: Re-write this using match as so:
if let "PASS" = m.command[] {
match m.suffix {
Some(ref suffix) => {
if m.args.len() != 0 { return Err(invalid_input()) }
PASS(suffix[])
},
None => {
if m.args.len() != 1 { return Err(invalid_input()) }
PASS(m.args[0][])
}
}
}
*/
Ok(if let "PASS" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 0 { return Err(invalid_input()) }
PASS(m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 1 { return Err(invalid_input()) }
PASS(m.args[0][])
}
} else if let "NICK" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 0 { return Err(invalid_input()) }
NICK(m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 1 { return Err(invalid_input()) }
NICK(m.args[0][])
}
} else if let "USER" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 2 { return Err(invalid_input()) }
USER(m.args[0][], m.args[1][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 3 { return Err(invalid_input()) }
USER(m.args[0][], m.args[1][], m.args[2][])
}
} else if let "OPER" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
OPER(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
OPER(m.args[0][], m.args[1][])
}
} else if let "MODE" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 2 { return Err(invalid_input()) }
MODE(m.args[0][], m.args[1][], Some(m.suffix.as_ref().unwrap()[]))
} else if m.args.len() == 3 {
MODE(m.args[0][], m.args[1][], Some(m.args[2][]))
} else if m.args.len() == 2 {
MODE(m.args[0][], m.args[1][], None)
} else {
return Err(invalid_input())
}
} else if let "SERVICE" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 5 { return Err(invalid_input()) }
SERVICE(m.args[0][], m.args[1][], m.args[2][], m.args[3][], m.args[4][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 6 { return Err(invalid_input()) }
SERVICE(m.args[0][], m.args[1][], m.args[2][], m.args[3][], m.args[4][], m.args[5][])
}
} else if let "QUIT" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
QUIT(Some(m.suffix.as_ref().unwrap()[]))
} else {
QUIT(None)
}
} else if let "SQUIT" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
SQUIT(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
SQUIT(m.args[0][], m.args[1][])
}
} else if let "JOIN" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
JOIN(m.suffix.as_ref().unwrap()[], None)
} else if m.args.len() == 1 {
JOIN(m.args[0][], Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
JOIN(m.args[0][], None)
} else if m.args.len() == 2 {
JOIN(m.args[0][], Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "PART" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
PART(m.suffix.as_ref().unwrap()[], None)
} else if m.args.len() == 1 {
PART(m.args[0][], Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
PART(m.args[0][], None)
} else if m.args.len() == 2 {
PART(m.args[0][], Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "TOPIC" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
TOPIC(m.suffix.as_ref().unwrap()[], None)
} else if m.args.len() == 1 {
TOPIC(m.args[0][], Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
TOPIC(m.args[0][], None)
} else if m.args.len() == 2 {
TOPIC(m.args[0][], Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "NAMES" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
NAMES(Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 1 {
NAMES(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
NAMES(None, None)
} else if m.args.len() == 1 {
NAMES(Some(m.args[0][]), None)
} else if m.args.len() == 2 {
NAMES(Some(m.args[0][]), Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "LIST" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
LIST(Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 1 {
LIST(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
LIST(None, None)
} else if m.args.len() == 1 {
LIST(Some(m.args[0][]), None)
} else if m.args.len() == 2 {
LIST(Some(m.args[0][]), Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "INVITE" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
INVITE(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
INVITE(m.args[0][], m.args[1][])
}
} else if let "KICK" = m.command[] {
if m.args.len() != 2 { return Err(invalid_input()) }
KICK(m.args[0][], m.args[1][], m.suffix.as_ref().map(|s| s[]))
} else if let "PRIVMSG" = m.command[] {
if !m.suffix.is_some() || m.args.len() != 1 { return Err(invalid_input()) }
PRIVMSG(m.args[0][], m.suffix.as_ref().unwrap()[])
} else if let "NOTICE" = m.command[] {
if !m.suffix.is_some() || m.args.len() != 1 { return Err(invalid_input()) }
NOTICE(m.args[0][], m.suffix.as_ref().unwrap()[])
} else if let "MOTD" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
MOTD(Some(m.suffix.as_ref().unwrap()[]))
} else {
MOTD(None)
}
} else if let "LUSERS" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
LUSERS(Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 1 {
LUSERS(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
LUSERS(None, None)
} else if m.args.len() == 1 {
LUSERS(Some(m.args[0][]), None)
} else if m.args.len() == 2 {
LUSERS(Some(m.args[0][]), Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "VERSION" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
VERSION(Some(m.suffix.as_ref().unwrap()[]))
} else {
VERSION(None)
}
} else if let "STATS" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
STATS(Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 1 {
STATS(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
STATS(None, None)
} else if m.args.len() == 1 {
STATS(Some(m.args[0][]), None)
} else if m.args.len() == 2 {
STATS(Some(m.args[0][]), Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "LINKS" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
LINKS(None, Some(m.suffix.as_ref().unwrap()[]))
} else if m.args.len() == 1 {
LINKS(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
LINKS(None, None)
} else {
return Err(invalid_input())
}
} else if let "TIME" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
TIME(Some(m.suffix.as_ref().unwrap()[]))
} else {
TIME(None)
}
} else if let "CONNECT" = m.command[] {
if m.args.len() != 2 { return Err(invalid_input()) }
CONNECT(m.args[0][], m.args[1][], m.suffix.as_ref().map(|s| s[]))
} else if let "TRACE" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
TRACE(Some(m.suffix.as_ref().unwrap()[]))
} else {
TRACE(None)
}
} else if let "ADMIN" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
TIME(Some(m.suffix.as_ref().unwrap()[]))
} else {
TIME(None)
}
} else if let "INFO" = m.command[] {
if m.args.len() != 0 { return Err(invalid_input()) }
if m.suffix.is_some() {
TIME(Some(m.suffix.as_ref().unwrap()[]))
} else {
TIME(None)
}
} else if let "SERVLIST" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
SERVLIST(Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 1 {
SERVLIST(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
SERVLIST(None, None)
} else if m.args.len() == 1 {
SERVLIST(Some(m.args[0][]), None)
} else if m.args.len() == 2 {
SERVLIST(Some(m.args[0][]), Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "SQUERY" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
SQUERY(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
SQUERY(m.args[0][], m.args[1][])
}
} else if let "WHO" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
WHO(Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 1 {
WHO(Some(m.args[0][]), Some(m.suffix.as_ref().unwrap()[] == "o"))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 0 {
WHO(None, None)
} else if m.args.len() == 1 {
WHO(Some(m.args[0][]), None)
} else if m.args.len() == 2 {
WHO(Some(m.args[0][]), Some(m.args[1][] == "o"))
} else {
return Err(invalid_input())
}
} else if let "WHOIS" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
WHOIS(None, m.suffix.as_ref().unwrap()[])
} else if m.args.len() == 1 {
WHOIS(Some(m.args[0][]), m.suffix.as_ref().unwrap()[])
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
WHOIS(None, m.args[0][])
} else if m.args.len() == 2 {
WHOIS(Some(m.args[0][]), m.args[1][])
} else {
return Err(invalid_input())
}
} else if let "WHOWAS" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
WHOWAS(m.suffix.as_ref().unwrap()[], None, None)
} else if m.args.len() == 1 {
WHOWAS(m.args[0][], None, Some(m.suffix.as_ref().unwrap()[]))
} else if m.args.len() == 2 {
WHOWAS(m.args[0][], Some(m.args[1][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
WHOWAS(m.args[0][], None, None)
} else if m.args.len() == 2 {
WHOWAS(m.args[0][], None, Some(m.args[1][]))
} else if m.args.len() == 3 {
WHOWAS(m.args[0][], Some(m.args[1][]), Some(m.args[2][]))
} else {
return Err(invalid_input())
}
} else if let "KILL" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
KILL(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
KILL(m.args[0][], m.args[1][])
}
} else if let "PING" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
PING(m.suffix.as_ref().unwrap()[], None)
} else if m.args.len() == 1 {
PING(m.args[0][], Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
PING(m.args[0][], None)
} else if m.args.len() == 2 {
PING(m.args[0][], Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "PONG" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
PONG(m.suffix.as_ref().unwrap()[], None)
} else if m.args.len() == 1 {
PONG(m.args[0][], Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
PONG(m.args[0][], None)
} else if m.args.len() == 2 {
PONG(m.args[0][], Some(m.args[1][]))
} else {
return Err(invalid_input())
}
} else if let "ERROR" = m.command[] {
if m.suffix.is_some() && m.args.len() == 0 {
ERROR(m.suffix.as_ref().unwrap()[])
} else {
return Err(invalid_input())
}
} else if let "AWAY" = m.command[] {
if m.args.len() == 0 {
AWAY(m.suffix.as_ref().map(|s| s[]))
} else {
return Err(invalid_input())
}
} else if let "REHASH" = m.command[] {
if m.args.len() == 0 {
REHASH
} else {
return Err(invalid_input())
}
} else if let "DIE" = m.command[] {
if m.args.len() == 0 {
DIE
} else {
return Err(invalid_input())
}
} else if let "RESTART" = m.command[] {
if m.args.len() == 0 {
RESTART
} else {
return Err(invalid_input())
}
} else if let "SUMMON" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 0 {
SUMMON(m.suffix.as_ref().unwrap()[], None, None)
} else if m.args.len() == 1 {
SUMMON(m.args[0][], Some(m.suffix.as_ref().unwrap()[]), None)
} else if m.args.len() == 2 {
SUMMON(m.args[0][], Some(m.args[1][]), Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 1 {
SUMMON(m.args[0][], None, None)
} else if m.args.len() == 2 {
SUMMON(m.args[0][], Some(m.args[1][]), None)
} else if m.args.len() == 3 {
SUMMON(m.args[0][], Some(m.args[1][]), Some(m.args[2][]))
} else {
return Err(invalid_input())
}
} else if let "USERS" = m.command[] {
if m.args.len() == 0 {
USERS(m.suffix.as_ref().map(|s| s[]))
} else if m.args.len() == 1 {
USERS(Some(m.args[0][]))
} else {
return Err(invalid_input())
}
} else if let "WALLOPS" = m.command[] {
if m.suffix.is_some() && m.args.len() == 0 {
WALLOPS(m.suffix.as_ref().unwrap()[])
} else if m.args.len() == 1 {
WALLOPS(m.args[0][])
} else {
return Err(invalid_input())
}
} else if let "USERHOST" = m.command[] {
if m.suffix.is_none() {
USERHOST(m.args.iter().map(|s| s[]).collect())
} else {
return Err(invalid_input())
}
} else if let "ISON" = m.command[] {
if m.suffix.is_none() {
USERHOST(m.args.iter().map(|s| s[]).collect())
} else {
return Err(invalid_input())
}
} else if let "SAJOIN" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
SAJOIN(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
SAJOIN(m.args[0][], m.args[1][])
}
} else if let "SAMODE" = m.command[] {
if m.suffix.is_some() {
if m.args.len() == 1 {
SAMODE(m.args[0][], m.suffix.as_ref().unwrap()[], None)
} else if m.args.len() == 2 {
SAMODE(m.args[0][], m.args[1][], Some(m.suffix.as_ref().unwrap()[]))
} else {
return Err(invalid_input())
}
} else if m.args.len() == 2 {
SAMODE(m.args[0][], m.args[1][], None)
} else if m.args.len() == 3 {
SAMODE(m.args[0][], m.args[1][], Some(m.args[2][]))
} else {
return Err(invalid_input())
}
} else if let "SANICK" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
SANICK(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
SANICK(m.args[0][], m.args[1][])
}
} else if let "SAPART" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
SAPART(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
SAPART(m.args[0][], m.args[1][])
}
} else if let "SAQUIT" = m.command[] {
if m.suffix.is_some() {
if m.args.len() != 1 { return Err(invalid_input()) }
SAQUIT(m.args[0][], m.suffix.as_ref().unwrap()[])
} else {
if m.args.len() != 2 { return Err(invalid_input()) }
SAQUIT(m.args[0][], m.args[1][])
}
} else {
return Err(invalid_input())
})
}
}
/// Produces an invalid_input IoError
#[stable]
fn invalid_input() -> IoError {
IoError {
kind: InvalidInput,
desc: "Failed to parse malformed message as command.",
detail: None
}
}

56
src/data/config.rs Normal file
View file

@ -0,0 +1,56 @@
//! JSON configuration files using libserialize
#![stable]
use std::collections::HashMap;
use std::io::fs::File;
use std::io::{InvalidInput, IoError, IoResult};
use serialize::json::decode;
/// Configuration data
#[deriving(Clone, Decodable)]
#[unstable]
pub struct Config {
/// A list of the owners of the bot by nickname
pub owners: Vec<String>,
/// The bot's nickname
pub nickname: String,
/// The bot's username
pub username: String,
/// The bot's real name
pub realname: String,
/// The bot's password
pub password: String,
/// The server to connect to
pub server: String,
/// The port to connect on
pub port: u16,
/// A list of channels to join on connection
pub channels: Vec<String>,
/// A map of additional options to be stored in config
pub options: HashMap<String, String>,
}
impl Config {
/// Loads a JSON configuration from the desired path.
#[stable]
pub fn load(path: Path) -> IoResult<Config> {
let mut file = try!(File::open(&path));
let data = try!(file.read_to_string());
decode(data[]).map_err(|e| IoError {
kind: InvalidInput,
desc: "Failed to decode configuration file.",
detail: Some(e.to_string()),
})
}
/// Loads a JSON configuration using the string as a UTF-8 path.
#[stable]
pub fn load_utf8(path: &str) -> IoResult<Config> {
Config::load(Path::new(path))
}
/// Determines whether or not the nickname provided is the owner of the bot.
#[stable]
pub fn is_owner(&self, nickname: &str) -> bool {
self.owners[].contains(&String::from_str(nickname))
}
}

84
src/data/message.rs Normal file
View file

@ -0,0 +1,84 @@
//! Messages to and from the server
#![experimental]
use std::from_str::FromStr;
/// IRC Message data
#[experimental]
#[deriving(Clone, PartialEq, Show)]
pub struct Message {
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812)
pub prefix: Option<String>,
/// The IRC command as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812)
pub command: String,
/// The command arguments
pub args: Vec<String>,
/// The message suffix as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812)
/// This is the only part of the message that is allowed to contain spaces.
pub suffix: Option<String>,
}
impl Message {
/// Creates a new Message
#[experimental]
pub fn new(prefix: Option<&str>, command: &str, args: Option<Vec<&str>>, suffix: Option<&str>)
-> Message {
Message {
prefix: prefix.map(|s| s.into_string()),
command: command.into_string(),
args: args.map_or(Vec::new(), |v| v.iter().map(|s| s.into_string()).collect()),
suffix: suffix.map(|s| s.into_string()),
}
}
/// Converts a Message into a String according to the IRC protocol
#[experimental]
pub fn into_string(&self) -> String {
let mut ret = String::new();
if let Some(ref prefix) = self.prefix {
ret.push(':');
ret.push_str(prefix[]);
ret.push(' ');
}
ret.push_str(self.command[]);
for arg in self.args.iter() {
ret.push(' ');
ret.push_str(arg[]);
}
if let Some(ref suffix) = self.suffix {
ret.push_str(" :");
ret.push_str(suffix[]);
}
ret.push_str("\r\n");
ret
}
}
impl FromStr for Message {
fn from_str(s: &str) -> Option<Message> {
let mut state = s.clone();
if s.len() == 0 { return None }
let prefix = if state.starts_with(":") {
let prefix = state.find(' ').map(|i| state[1..i]);
state = state.find(' ').map_or("", |i| state[i+1..]);
prefix
} else {
None
};
let suffix = if state.contains(":") {
let suffix = state.find(':').map(|i| state[i+1..state.len()-2]);
state = state.find(':').map_or("", |i| state[..i]);
suffix
} else {
None
};
let command = match state.find(' ').map(|i| state[..i]) {
Some(cmd) => {
state = state.find(' ').map_or("", |i| state[i+1..]);
cmd
}
_ => return None
};
let args: Vec<_> = state.splitn(14, ' ').filter(|s| s.len() != 0).collect();
Some(Message::new(prefix, command, if args.len() > 0 { Some(args) } else { None }, suffix))
}
}

20
src/data/mod.rs Normal file
View file

@ -0,0 +1,20 @@
//! Data related to IRC functionality
#![experimental]
pub mod kinds {
//! Trait definitions of appropriate Writers and Buffers for use with this library
#![unstable]
/// Trait describing all possible Writers for this library
#[unstable]
pub trait IrcWriter: Writer + Sized + Send + 'static {}
impl<T> IrcWriter for T where T: Writer + Sized + Send + 'static {}
/// Trait describing all possible Readers for this library
#[unstable]
pub trait IrcReader: Buffer + Sized + Send + 'static {}
impl<T> IrcReader for T where T: Buffer + Sized + Send + 'static {}
}
pub mod command;
pub mod config;
pub mod message;

View file

@ -1,70 +1,12 @@
//! A simple, thread-safe IRC client library.
#![crate_name = "irc"]
#![crate_type = "lib"]
#![unstable]
#![feature(if_let)] #![feature(if_let)]
#![feature(phase)]
#![feature(slicing_syntax)] #![feature(slicing_syntax)]
extern crate regex;
#[phase(plugin)] extern crate regex_macros;
extern crate serialize; extern crate serialize;
use std::io::{InvalidInput, IoError, IoResult}; mod conn;
pub mod bot;
pub mod conn;
pub mod data; pub mod data;
pub mod server;
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>>;
}
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"])
}
}

113
src/server/mod.rs Normal file
View file

@ -0,0 +1,113 @@
//! Interface for working with IRC Servers
#![experimental]
use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream};
use conn::Connection;
use data::command::Command;
use data::config::Config;
use data::kinds::{IrcReader, IrcWriter};
use data::message::Message;
pub mod utils;
/// Trait describing core Server functionality
#[experimental]
pub trait Server<'a, T, U> {
/// Gets the configuration being used with this Server
fn config(&self) -> &Config;
/// Sends a Command to this Server
fn send(&self, _: Command) -> IoResult<()>;
/// Gets an Iterator over Messages received by this Server
fn iter(&'a self) -> ServerIterator<'a, T, U>;
}
/// A thread-safe implementation of an IRC Server connection
#[experimental]
pub struct IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
/// The thread-safe IRC connection
conn: Connection<T, U>,
/// The configuration used with this connection
config: Config
}
impl<'a> IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>> {
/// Creates a new IRC Server connection from the configuration at the specified path, connecting immediately.
#[experimental]
pub fn new(config: &str) -> IoResult<IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
let config = try!(Config::load_utf8(config));
let conn = try!(Connection::connect(config.server[], config.port));
IrcServer::from_connection(config, conn)
}
/// Creates a new IRC server connection from the specified configuration, connecting immediately.
#[experimental]
pub fn from_config(config: Config) -> IoResult<IrcServer<'a, BufferedWriter<TcpStream>, BufferedReader<TcpStream>>> {
let conn = try!(Connection::connect(config.server[], config.port));
IrcServer::from_connection(config, conn)
}
}
impl<'a, T, U> Server<'a, T, U> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
fn config(&self) -> &Config {
&self.config
}
fn send(&self, command: Command) -> IoResult<()> {
self.conn.send(command.to_message())
}
fn iter(&'a self) -> ServerIterator<'a, T, U> {
ServerIterator::new(self)
}
}
impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader {
/// Creates an IRC server from the specified configuration, and any arbitrary Connection
#[experimental]
pub fn from_connection(config: Config, conn: Connection<T, U>) -> IoResult<IrcServer<'a, T, U>> {
Ok(IrcServer {
conn: conn,
config: config
})
}
fn handle_message(&self, message: &Message) {
if message.command[] == "PING" {
utils::send_pong(self, message.suffix.as_ref().unwrap()[]).unwrap();
} else if message.command[] == "376" || message.command[] == "422" {
for chan in self.config.channels.iter() {
utils::send_join(self, chan[]).unwrap();
}
}
/* TODO: implement more message handling */
}
}
/// An Iterator over an IrcServer's incoming Messages
#[experimental]
pub struct ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader {
pub server: &'a IrcServer<'a, T, U>
}
impl<'a, T, U> ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader {
/// Creates a new ServerIterator for the desired IrcServer
#[experimental]
pub fn new(server: &'a IrcServer<'a, T, U>) -> ServerIterator<'a, T, U> {
ServerIterator {
server: server
}
}
}
impl<'a, T, U> Iterator<Message> for ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader {
fn next(&mut self) -> Option<Message> {
let line = self.server.conn.recv();
match line {
Err(_) => None,
Ok(msg) => {
let message = from_str(msg[]);
self.server.handle_message(message.as_ref().unwrap());
message
}
}
}
}

21
src/server/utils.rs Normal file
View file

@ -0,0 +1,21 @@
//! Utilities and shortcuts for working with IRC servers
#![experimental]
use std::io::IoResult;
use data::command::{JOIN, NICK, PONG, USER};
use data::kinds::{IrcReader, IrcWriter};
use server::Server;
/// Sends a NICK and USER to identify
pub fn identify<'a, T, U>(server: &Server<'a, T, U>) -> IoResult<()> where T: IrcWriter, U: IrcReader {
try!(server.send(NICK(server.config().nickname[])));
server.send(USER(server.config().username[], "0", server.config().realname[]))
}
pub fn send_pong<'a, T, U>(server: &Server<'a, T, U>, msg: &str) -> IoResult<()> where T: IrcWriter, U: IrcReader {
server.send(PONG(msg, None))
}
pub fn send_join<'a, T, U>(server: &Server<'a, T, U>, chanlist: &str) -> IoResult<()> where T: IrcWriter, U: IrcReader {
server.send(JOIN(chanlist, None))
}