use std::ops::Range;
use std::slice::Iter;
#[derive(Clone)]
pub struct ArgumentIter<'a> {
source: &'a str,
iter: Iter<'a, Range<usize>>,
}
impl<'a> ArgumentIter<'a> {
#[doc(hidden)]
pub fn new(source: &'a str, iter: Iter<'a, Range<usize>>) -> ArgumentIter<'a> {
ArgumentIter {
source: source,
iter: iter,
}
}
}
impl<'a> Iterator for ArgumentIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|range| &self.source[range.clone()])
}
}
impl<'a> DoubleEndedIterator for ArgumentIter<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter
.next_back()
.map(|range| &self.source[range.clone()])
}
}
pub trait Command<'a> {
fn name() -> &'static str;
fn parse(arguments: ArgumentIter<'a>) -> Option<Self>
where
Self: Sized;
fn try_match(command: &str, arguments: ArgumentIter<'a>) -> Option<Self>
where
Self: Sized,
{
if command == Self::name() {
Self::parse(arguments)
} else {
None
}
}
}
#[macro_export]
macro_rules! command_match {
(@message=$message:expr => $command:pat => $body:expr) => {{
let $command = $message;
$body
}};
(@message=$message:expr => $command:pat => $body:expr, $($rest:tt)*) => {
match $message.command() {
Some($command) => $body,
_ => command_match!(@message=$message => $($rest)*)
}
};
($message:expr => { $($rest:tt)* }) => {{
let message = $message;
command_match!(@message=message => $($rest)*)
}};
}
#[macro_export]
macro_rules! command {
($(#[$meta:meta])* ($command:expr => $command_name:ident())) => {
$(#[$meta])*
pub struct $command_name;
impl<'a> $crate::command::Command<'a> for $command_name {
fn name() -> &'static str {
$command
}
fn parse(_: $crate::command::ArgumentIter<'a>) -> Option<$command_name> {
Some($command_name)
}
}
};
($(#[$meta:meta])* ($command:expr => $command_name:ident($($name:ident),+))) => {
$(#[$meta])*
pub struct $command_name<'a>($(pub expand_param!($name)),+);
impl<'a> $crate::command::Command<'a> for $command_name<'a> {
fn name() -> &'static str {
$command
}
fn parse(mut arguments: $crate::command::ArgumentIter<'a>) -> Option<$command_name> {
$(
let $name = match arguments.next() {
Some(value) => value,
None => return None
};
)+
Some($command_name($($name),*))
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! expand_param {
($i:ident) => { &'a str };
}
command! {
("PING" => Ping(host))
}
command! {
("PONG" => Pong(host))
}
command! {
("PRIVMSG" => PrivMsg(target, message))
}
command! {
("001" => Welcome(user, message))
}
command! {
("002" => YourHost(user, message))
}
command!{
("003" => Created(user, message))
}
command!{
("004" => ServerInfo(user, message))
}
#[derive(PartialEq, Debug)]
pub enum NamesReplyChannelType {
Secret,
Private,
Other,
}
pub struct NamesReply<'a>(pub NamesReplyChannelType, pub &'a str, pub Vec<&'a str>);
impl<'a> Command<'a> for NamesReply<'a> {
fn name() -> &'static str {
"353"
}
fn parse(arguments: ArgumentIter<'a>) -> Option<NamesReply<'a>> {
let mut arguments = arguments.rev();
let names = match arguments.next() {
Some(names) => names.split_whitespace(),
None => return None,
};
let channel = match arguments.next() {
Some(channel) => channel,
None => return None,
};
let channel_type = match arguments.next() {
Some(channel_type) => {
match channel_type {
"@" => NamesReplyChannelType::Secret,
"*" => NamesReplyChannelType::Private,
_ => NamesReplyChannelType::Other,
}
}
None => NamesReplyChannelType::Other,
};
Some(NamesReply(channel_type, channel, names.collect()))
}
}
pub struct EndNamesReply<'a>(pub &'a str, pub &'a str);
impl<'a> Command<'a> for EndNamesReply<'a> {
fn name() -> &'static str {
"366"
}
fn parse(arguments: ArgumentIter<'a>) -> Option<EndNamesReply<'a>> {
let mut arguments = arguments.rev();
let message = match arguments.next() {
Some(message) => message,
None => return None,
};
let channel = match arguments.next() {
Some(channel) => channel,
None => return None,
};
Some(EndNamesReply(channel, message))
}
}
#[cfg(test)]
mod tests {
use super::*;
use message::*;
#[test]
fn test_ping_command() {
let message = server::ping("test.host.com").unwrap();
let Ping(host) = message.command::<Ping>().unwrap();
assert_eq!("test.host.com", host);
}
#[test]
fn test_pong_command() {
let message = client::pong("test.host.com").unwrap();
let Pong(host) = message.command::<Pong>().unwrap();
assert_eq!("test.host.com", host);
}
#[test]
fn test_privmsg_command() {
let message = client::priv_msg("#channel", "This is a message!").unwrap();
let PrivMsg(target, message) = message.command::<PrivMsg>().unwrap();
assert_eq!("#channel", target);
assert_eq!("This is a message!", message);
}
#[test]
fn test_welcome_command() {
let msg = server::welcome("robots", "our overlords").unwrap();
let Welcome(username, message) = msg.command::<Welcome>().unwrap();
assert_eq!("robots", username);
assert_eq!("our overlords", message);
}
#[test]
fn test_your_host_command() {
let msg = server::your_host("robots", "our overlords").unwrap();
let YourHost(username, message) = msg.command::<YourHost>().unwrap();
assert_eq!("robots", username);
assert_eq!("our overlords", message);
}
#[test]
fn test_created_command() {
let msg = server::created("robots", "our overlords").unwrap();
let Created(username, message) = msg.command::<Created>().unwrap();
assert_eq!("robots", username);
assert_eq!("our overlords", message);
}
#[test]
fn test_server_info_command() {
let msg = server::server_info("robots", "our overlords").unwrap();
let ServerInfo(username, message) = msg.command::<ServerInfo>().unwrap();
assert_eq!("robots", username);
assert_eq!("our overlords", message);
}
#[test]
fn test_names_reply_command() {
let msg: Message = "353 = #test :robot1 robot2 robot3".parse().unwrap();
let NamesReply(channel_type, channel, users) = msg.command::<NamesReply>().unwrap();
let expected_users = vec!["robot1", "robot2", "robot3"];
assert_eq!(NamesReplyChannelType::Other, channel_type);
assert_eq!("#test", channel);
assert_eq!(expected_users, users);
}
#[test]
fn test_command_match_with_single_branchj() {
let message = client::priv_msg("#channel", "This is a message!").unwrap();
command_match! {
message => {
PrivMsg(target, message) => {
assert_eq!(target, "#channel");
assert_eq!(message, "This is a message!");
},
_ => {
panic!("Command was not matched.")
}
}
}
}
#[test]
fn test_command_match_with_multiple_branches() {
let message = client::priv_msg("#channel", "This is a message!").unwrap();
command_match! {
message => {
Ping(_) => panic!("Command was inadvertently matched."),
Pong(_) => panic!("Command was inadvertently matched."),
Welcome(_, _) => panic!("Command was inadvertently matched."),
PrivMsg(target, message) => {
assert_eq!(target, "#channel");
assert_eq!(message, "This is a message!");
},
_ => {
panic!("Command was not matched.")
}
}
}
}
}