Laid out basic structure of rewritten library.

This commit is contained in:
Aaron Weiss 2014-11-02 16:25:57 -05:00
parent 91aa5bcc6f
commit b2006d044d
9 changed files with 60 additions and 320 deletions

View file

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

View file

@ -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);
}
}

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 +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()
}
}

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))
}
}

View file

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

53
src/utils.rs Normal file
View 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"])
}
}