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"
|
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"
|
|
||||||
|
|
|
@ -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(if_let)]
|
||||||
#![feature(phase)]
|
#![feature(phase)]
|
||||||
#![feature(slicing_syntax)]
|
#![feature(slicing_syntax)]
|
||||||
|
@ -5,66 +9,6 @@ extern crate regex;
|
||||||
#[phase(plugin)] extern crate regex_macros;
|
#[phase(plugin)] extern crate regex_macros;
|
||||||
extern crate serialize;
|
extern crate serialize;
|
||||||
|
|
||||||
use std::io::{InvalidInput, IoError, IoResult};
|
mod conn;
|
||||||
|
pub mod server;
|
||||||
pub mod bot;
|
mod utils;
|
||||||
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"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
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