Merge pull request #1 from aaronweiss74/redesign
Iterator-based redesign.
This commit is contained in:
commit
b60236a0fd
14 changed files with 1105 additions and 287 deletions
|
@ -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
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
69
src/bot.rs
69
src/bot.rs
|
@ -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()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
62
src/conn.rs
62
src/conn.rs
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
106
src/data.rs
106
src/data.rs
|
@ -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
768
src/data/command.rs
Normal 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
56
src/data/config.rs
Normal 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
84
src/data/message.rs
Normal 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
20
src/data/mod.rs
Normal 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;
|
72
src/lib.rs
72
src/lib.rs
|
@ -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
113
src/server/mod.rs
Normal 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
21
src/server/utils.rs
Normal 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))
|
||||||
|
}
|
Loading…
Reference in a new issue