aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorZachary Dremann <dremann@gmail.com>2014-06-09 08:51:54 -0400
committerZachary Dremann <dremann@gmail.com>2014-06-09 08:51:54 -0400
commitb4643e1d1ec8f447d36f3794f3a051a64a090d16 (patch)
tree4eeb01e94dd417eb0372d1480f370a6a38497282 /src
parent4098f06294a2a9c40f4183e9e2ce291550cb69fd (diff)
downloadrusty-irc-b4643e1d1ec8f447d36f3794f3a051a64a090d16.tar.gz
rusty-irc-b4643e1d1ec8f447d36f3794f3a051a64a090d16.zip
Using rust-empty for structure
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs86
-rw-r--r--src/msg.rs185
2 files changed, 271 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..8b385ec
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,86 @@
+#![crate_id = "irc#0.1"]
+#![crate_type = "lib"]
+
+use std::io::net::tcp::TcpStream;
+use std::io::IoResult;
+use std::io::BufferedReader;
+use std::io::IoError;
+
+use msg::Message;
+use msg::cmd;
+
+pub mod msg;
+
+pub struct IrcConnection<'a> {
+ stream: TcpStream,
+ output_sender: Sender<Message>,
+ msg_callback: |&Message, &Sender<Message>|: 'a -> ()
+}
+
+impl<'a> IrcConnection<'a> {
+ pub fn connect<'b>(
+ host: &str, port: u16, nick: String, username: String,
+ real_name: String, msg_callback: |&Message, &Sender<Message>|: 'b -> ()) -> IoResult<IrcConnection<'b>> {
+
+ let (send_writer, rec_writer) = channel();
+
+ let mut connection = IrcConnection {
+ stream: try!(TcpStream::connect(host, port)),
+ output_sender: send_writer.clone(),
+ msg_callback: msg_callback,
+ };
+
+ let writer = connection.stream.clone();
+
+ // spawn writer thread
+ spawn(proc() {
+ let mut writer = writer;
+ for msg in rec_writer.iter() {
+ (write!(writer, "{}", msg)).ok().expect("Unable to write to stream");
+ }
+ });
+
+ connection.send(Message::new(cmd::Nick(nick)));
+ connection.send(Message::new(cmd::User(username, 0, real_name)));
+ Ok(connection)
+ }
+
+ pub fn send(&mut self, message: Message) {
+ self.output_sender.send(message);
+ }
+
+ fn on_msg_rec(msg: &Message, sender: &Sender<Message>) {
+ let prefix = &msg.prefix;
+ let cmd = &msg.command;
+ match *cmd {
+ cmd::Ping(ref s) => sender.send(Message::new(cmd::Pong(s.clone()))),
+ _ => { }
+ };
+ }
+
+ pub fn run_loop(&mut self) {
+ let reader = &mut self.stream;
+ loop {
+ fn reader_by_ref<'a, R: Reader>(reader: &'a mut R) -> std::io::RefReader<'a, R> { reader.by_ref() }
+
+ reader.set_read_timeout(Some(500));
+ let mut buf_reader = BufferedReader::new(reader_by_ref(reader));
+
+ let line = buf_reader.read_line();
+ match line {
+ Ok(line) => match from_str::<Message>(line.as_slice().trim_right()) {
+ Some(msg) => {
+ IrcConnection::on_msg_rec(&msg, &self.output_sender);
+ (self.msg_callback)(&msg, &self.output_sender);
+ },
+ None => println!("Invalid Message recieved"),
+ },
+ Err(IoError{kind: std::io::TimedOut, ..}) => continue,
+ Err(e) => {
+ println!("Unable to read line: {}", e);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/msg.rs b/src/msg.rs
new file mode 100644
index 0000000..3471d75
--- /dev/null
+++ b/src/msg.rs
@@ -0,0 +1,185 @@
+use std::fmt;
+use std::from_str::FromStr;
+use std::ascii::OwnedStrAsciiExt;
+
+pub mod cmd {
+ #[deriving(Clone, Show)]
+ pub enum Command {
+ Nick(String),
+ User(String, u8, String),
+ Quit(Option<String>),
+ Join(String),
+ Part(String, Option<String>),
+ PrivMsg(String, String),
+ Notice(String, String),
+ //Motd(Option<String>),
+ Ping(String),
+ Pong(String),
+ Error(String),
+ Away(Option<String>),
+ Numeric(u16, Option<String>),
+ UnknownCmd(String, Vec<String>)
+ }
+}
+
+pub struct PrefixHost {
+ hostname: String,
+ user: Option<String>,
+}
+
+pub enum Prefix {
+ PrefixServer(String),
+ PrefixUser(String, Option<PrefixHost>),
+}
+
+#[deriving(Clone)]
+pub struct Message {
+ pub prefix: Option<String>,
+ pub command: cmd::Command,
+}
+
+impl Message {
+ pub fn new(command: cmd::Command) -> Message {
+ Message { prefix: None, command: command }
+ }
+
+ pub fn with_prefix(prefix: String, command: cmd::Command) -> Message {
+ Message { prefix: Some(prefix), command: command }
+ }
+}
+
+impl<'a> fmt::Show for Message {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ if self.prefix.is_some() {
+ try!(write!(formatter, ":{} ", self.prefix.get_ref()));
+ }
+
+ match self.command {
+ cmd::Nick(ref nickname) => write!(formatter, "NICK {}", nickname),
+ cmd::User(ref username, mode, ref real_name) => write!(formatter, "USER {} {} * :{}", username, mode, real_name),
+ cmd::Quit(ref msg) => if msg.is_some() { write!(formatter, "QUIT :{}", *msg.get_ref()) } else { write!(formatter, "QUIT") },
+ cmd::Join(ref channel) => write!(formatter, "JOIN :{}", channel),
+ cmd::Part(ref channel, ref msg) => if msg.is_some() { write!(formatter, "PART {} :{}", channel, *msg.get_ref()) } else { write!(formatter, "PART {}", channel) },
+ cmd::PrivMsg(ref target, ref msg) => write!(formatter, "PRIVMSG {} :{}", target, msg),
+ cmd::Notice(ref target, ref msg) => write!(formatter, "NOTICE {} :{}", target, msg),
+ cmd::Ping(ref msg) => write!(formatter, "PING :{}", msg),
+ cmd::Pong(ref msg) => write!(formatter, "PONG :{}", msg),
+ cmd::Error(ref msg) => write!(formatter, "ERROR :{}", msg),
+ cmd::Away(ref msg) => if msg.is_some() { write!(formatter, "AWAY :{}", msg.get_ref()) } else { write!(formatter, "AWAY") },
+ cmd::Numeric(i, ref msg) => if msg.is_some() { write!(formatter, "{:03u} :{}", i, msg.get_ref()) } else { write!(formatter, "{:03u}", i) },
+ cmd::UnknownCmd(ref cmd, ref args) => {
+ try!(write!(formatter, "{}", cmd));
+ let mut iter = args.iter().peekable();
+ loop {
+ match iter.next() {
+ Some(arg) => {
+ try!(
+ if iter.peek().is_some() {
+ write!(formatter, " {}", arg)
+ }
+ else {
+ write!(formatter, " :{}", arg)
+ }
+ )
+ }
+ None => break
+ }
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+impl FromStr for Message {
+ fn from_str(s: &str) -> Option<Message> {
+ let mut prefix = None;
+ let mut cmd = None;
+ let mut args = Vec::new();
+ let mut current_str: Option<String> = None;
+ let mut is_prefix = false;
+ let mut is_final = false;
+
+ for c in s.chars() {
+ match c {
+ c if is_final => {
+ current_str.get_mut_ref().push_char(c);
+ }
+ ' ' => {
+ if is_prefix {
+ prefix = current_str.take();
+ }
+ else if cmd.is_none() {
+ cmd = current_str.take();
+ }
+ else {
+ args.push(current_str.take_unwrap());
+ }
+ is_prefix = false;
+ }
+ ':' if current_str.is_none() => {
+ current_str = Some(String::new());
+ if cmd.is_none() {
+ is_prefix = true;
+ }
+ else {
+ is_final = true;
+ }
+ }
+ c => {
+ if current_str.is_none() {
+ current_str = Some(String::new());
+ }
+ current_str.get_mut_ref().push_char(c)
+ }
+ }
+ }
+
+ args.push(current_str.take_unwrap());
+
+ let cmd = match cmd.map(|s| s.into_ascii_upper()).as_ref().map(|s| s.as_slice()) {
+ Some("NICK") => {
+ if args.len() == 1 { Some(cmd::Nick(args.pop().unwrap())) }
+ else { None }
+ }
+ Some("USER") => {
+ if args.len() == 4 {
+ let mut iter = args.move_iter();
+ let uname = iter.next().unwrap();
+ let opt_mode: Option<u8> = from_str(iter.next().unwrap().as_slice());
+ iter.next();
+ let fullname = iter.next().unwrap();
+ if opt_mode.is_some() {
+ Some(cmd::User(uname, opt_mode.unwrap(), fullname))
+ }
+ else {
+ None
+ }
+ }
+ else { None }
+ }
+ Some("NOTICE") => {
+ if args.len() == 2 {
+ let mut iter = args.move_iter();
+ Some(cmd::Notice(iter.next().unwrap(), iter.next().unwrap()))
+ }
+ else { None }
+ }
+ Some("PRIVMSG") => {
+ if args.len() == 2 {
+ let mut iter = args.move_iter();
+ Some(cmd::PrivMsg(iter.next().unwrap(), iter.next().unwrap()))
+ }
+ else { None }
+ }
+ Some(other) => {
+ Some(cmd::UnknownCmd(other.to_string(), args))
+ }
+ None => {
+ None
+ }
+ };
+
+ cmd.map(|c| Message { prefix: prefix.take(), command: c })
+ }
+}