use std::fmt; use std::from_str::FromStr; use std::ascii::OwnedStrAsciiExt; pub mod cmd { #[deriving(Clone, PartialEq, Eq, Show, Hash)] pub enum Command { Nick(String), User(String, u8, String), Quit(Option), Join(String), Part(String, Option), PrivMsg(String, String), Notice(String, String), Motd(Option), Ping(String), Pong(String), Error(String), Away(Option), Numeric(u16, String, Vec), UnknownCmd(String, Vec) } } #[deriving(Clone, PartialEq, Eq, Hash)] pub struct Prefix { pub name: String, pub user: Option, pub host: Option, } #[deriving(Clone, PartialEq, Eq, Hash)] pub struct Message { pub prefix: Option, pub command: cmd::Command, } impl Message { pub fn new(command: cmd::Command) -> Message { Message { prefix: None, command: command } } pub fn with_prefix(prefix: Prefix, command: cmd::Command) -> Message { Message { prefix: Some(prefix), command: command } } } impl 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::Motd(ref target) => if target.is_some() { write!(formatter, "MOTD :{}", target.get_ref()) } else { write!(formatter, "MOTD") }, cmd::Numeric(i, ref target, ref args) => { try!(write!(formatter, "{:03u} {}", i, target)); 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(()) } 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 fmt::Show for Prefix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "{}", self.name)); if self.user.is_some() { try!(write!(f, "!{}", self.user.get_ref())); } if self.host.is_some() { try!(write!(f, "@{}", self.host.get_ref())); } Ok(()) } } impl FromStr for Prefix { fn from_str(s: &str) -> Option { let name; let user; let host; match s.find('!') { Some(user_start) => { name = s.slice_to(user_start).to_string(); let rest = s.slice_from(user_start + 1); match rest.find('@') { Some(host_start) => { user = Some(rest.slice_to(host_start).to_string()); host = Some(rest.slice_from(host_start + 1).to_string()); } None => { user = Some(rest.to_string()); host = None; } } } None => { name = s.to_string(); user = None; host = None; } } Some(Prefix{ name: name, user: user, host: host }) } } impl FromStr for Message { fn from_str(s: &str) -> Option { let mut prefix = None; let mut cmd = None; let mut args = Vec::new(); let mut current_str: Option = None; let mut is_prefix = false; let mut is_final = false; for c in s.chars() { match c { c if is_final => { current_str.as_mut().unwrap().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.as_mut().unwrap().push_char(c) } } } if cmd.is_none() { cmd = current_str.take(); } else { args.push(current_str.take().unwrap()); } let prefix: Option = prefix.and_then(|s| from_str(s.as_slice())); let cmd = match cmd.map(|s| s.into_ascii_upper()).as_ref().map(|s| s.as_slice()) { Some(s) => { match s { "NICK" if args.len() == 1 => cmd::Nick(args.pop().unwrap()), "USER" if args.len() == 4 => { let mut iter = args.move_iter(); let uname = iter.next().unwrap(); let opt_mode: Option = from_str(iter.next().unwrap().as_slice()); iter.next(); let fullname = iter.next().unwrap(); cmd::User(uname, opt_mode.unwrap_or(0), fullname) } "NOTICE" if args.len() == 2 => { let mut iter = args.move_iter(); cmd::Notice(iter.next().unwrap(), iter.next().unwrap()) } "PRIVMSG" if args.len() == 2 => { let mut iter = args.move_iter(); cmd::PrivMsg(iter.next().unwrap(), iter.next().unwrap()) } "PING" if args.len() == 1 => cmd::Ping(args.pop().unwrap()), "PONG" if args.len() == 1 => cmd::Pong(args.pop().unwrap()), "AWAY" if args.len() == 0 || args.len() == 1 => cmd::Away(args.pop()), "QUIT" if args.len() == 0 || args.len() == 1 => cmd::Quit(args.pop()), "JOIN" if args.len() == 1 => cmd::Join(args.pop().unwrap()), "MOTD" if args.len() == 0 || args.len() == 1 => cmd::Motd(args.pop()), other => { match from_str::(other) { Some(n) if args.len() > 0 => { let target = args.remove(0).unwrap(); cmd::Numeric(n, target, args) } _ => cmd::UnknownCmd(s.to_string(), args) } } } } None => cmd::UnknownCmd(String::new(), args) }; Some(Message { prefix: prefix, command: cmd }) } }