Laid out basic structure of rewritten library.
This commit is contained in:
parent
91aa5bcc6f
commit
b2006d044d
9 changed files with 60 additions and 320 deletions
|
@ -3,6 +3,3 @@
|
|||
name = "irc"
|
||||
version = "0.0.1"
|
||||
authors = ["Aaron Weiss <aaronweiss74@gmail.com>"]
|
||||
|
||||
[[example]]
|
||||
name = "simple"
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
#![feature(slicing_syntax)]
|
||||
extern crate irc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use irc::Server;
|
||||
use irc::bot::IrcServer;
|
||||
use irc::data::{Config, Message};
|
||||
|
||||
fn main() {
|
||||
let config = Config {
|
||||
owners: vec!("awe".into_string()),
|
||||
nickname: "pickles".into_string(),
|
||||
username: "pickles".into_string(),
|
||||
realname: "pickles".into_string(),
|
||||
password: "".into_string(),
|
||||
server: "irc.fyrechat.net".into_string(),
|
||||
port: 6667,
|
||||
channels: vec!("#vana".into_string()),
|
||||
options: HashMap::new(),
|
||||
};
|
||||
let mut server = IrcServer::new_with_config(config).unwrap();
|
||||
server.send(Message::new(None, "NICK", vec!["pickles"], None)).unwrap();
|
||||
server.send(Message::new(None, "USER", vec!["pickles", "0", "*", "pickles"], None)).unwrap();
|
||||
for message in server {
|
||||
println!("RCV: {}", message);
|
||||
}
|
||||
}
|
|
@ -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()),
|
||||
})
|
||||
}
|
||||
}
|
51
src/conn.rs
51
src/conn.rs
|
@ -1,51 +0,0 @@
|
|||
use std::cell::{RefCell, RefMut};
|
||||
use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream, Writer};
|
||||
use data::{IrcReader, IrcWriter, Message};
|
||||
|
||||
pub struct Connection<T, U> where T: IrcWriter, U: IrcReader {
|
||||
writer: RefCell<T>,
|
||||
reader: RefCell<U>,
|
||||
}
|
||||
|
||||
impl 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));
|
||||
Connection::new(BufferedWriter::new(socket.clone()), BufferedReader::new(socket.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Connection<T, U> where T: IrcWriter, U: IrcReader {
|
||||
pub fn new(writer: T, reader: U) -> IoResult<Connection<T, U>> {
|
||||
Ok(Connection {
|
||||
writer: RefCell::new(writer),
|
||||
reader: RefCell::new(reader),
|
||||
})
|
||||
}
|
||||
|
||||
fn send_internal(&self, msg: &str) -> IoResult<()> {
|
||||
let mut send = self.writer.borrow_mut();
|
||||
try!(send.write_str(msg));
|
||||
send.flush()
|
||||
}
|
||||
|
||||
pub fn send(&self, msg: Message) -> IoResult<()> {
|
||||
let mut send = msg.command.to_string();
|
||||
if msg.args.init().len() > 0 {
|
||||
send.push_str(" ");
|
||||
send.push_str(msg.args.init().connect(" ")[]);
|
||||
}
|
||||
send.push_str(" ");
|
||||
if msg.colon_flag.is_some() { send.push_str(":") }
|
||||
send.push_str(msg.args.last().unwrap()[]);
|
||||
send.push_str("\r\n");
|
||||
self.send_internal(send[])
|
||||
}
|
||||
|
||||
pub fn writer<'a>(&'a self) -> RefMut<'a, T> {
|
||||
self.writer.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn reader<'a>(&'a self) -> RefMut<'a, U> {
|
||||
self.reader.borrow_mut()
|
||||
}
|
||||
}
|
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))
|
||||
}
|
||||
}
|
70
src/lib.rs
70
src/lib.rs
|
@ -1,3 +1,7 @@
|
|||
//! A simple, thread-safe IRC client library.
|
||||
#![crate_name = "irc"]
|
||||
#![crate_type = "lib"]
|
||||
|
||||
#![feature(if_let)]
|
||||
#![feature(phase)]
|
||||
#![feature(slicing_syntax)]
|
||||
|
@ -5,66 +9,6 @@ extern crate regex;
|
|||
#[phase(plugin)] extern crate regex_macros;
|
||||
extern crate serialize;
|
||||
|
||||
use std::io::{InvalidInput, IoError, IoResult};
|
||||
|
||||
pub mod bot;
|
||||
pub mod conn;
|
||||
pub mod data;
|
||||
|
||||
pub trait Server<'a>: Iterator<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"])
|
||||
}
|
||||
}
|
||||
mod conn;
|
||||
pub mod server;
|
||||
mod utils;
|
||||
|
|
0
src/server.rs
Normal file
0
src/server.rs
Normal file
53
src/utils.rs
Normal file
53
src/utils.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use std::io::{InvalidInput, IoError, IoResult};
|
||||
|
||||
fn process(msg: &str) -> IoResult<(&str, &str, Vec<&str>)> {
|
||||
let reg = regex!(r"^(?::([^ ]+) )?([^ ]+)(.*)");
|
||||
let cap = match reg.captures(msg) {
|
||||
Some(x) => x,
|
||||
None => return Err(IoError {
|
||||
kind: InvalidInput,
|
||||
desc: "Failed to parse line",
|
||||
detail: None,
|
||||
}),
|
||||
};
|
||||
let source = cap.at(1);
|
||||
let command = cap.at(2);
|
||||
let args = parse_args(cap.at(3));
|
||||
Ok((source, command, args))
|
||||
}
|
||||
|
||||
fn parse_args(line: &str) -> Vec<&str> {
|
||||
let reg = regex!(r" ([^: ]+)| :([^\r\n]*)[\r\n]*$");
|
||||
reg.captures_iter(line).map(|cap| {
|
||||
match cap.at(1) {
|
||||
"" => cap.at(2),
|
||||
x => x,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{process, parse_args};
|
||||
|
||||
#[test]
|
||||
fn process_line() {
|
||||
let res = process(":flare.to.ca.fyrechat.net 353 pickles = #pickles :pickles awe\r\n").unwrap();
|
||||
let (source, command, args) = res;
|
||||
assert_eq!(source, "flare.to.ca.fyrechat.net");
|
||||
assert_eq!(command, "353");
|
||||
assert_eq!(args, vec!["pickles", "=", "#pickles", "pickles awe"]);
|
||||
|
||||
let res = process("PING :flare.to.ca.fyrechat.net\r\n").unwrap();
|
||||
let (source, command, args) = res;
|
||||
assert_eq!(source, "");
|
||||
assert_eq!(command, "PING");
|
||||
assert_eq!(args, vec!["flare.to.ca.fyrechat.net"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_args() {
|
||||
let res = parse_args("PRIVMSG #vana :hi");
|
||||
assert_eq!(res, vec!["#vana", "hi"])
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue