2014-11-06 21:23:02 +01:00
|
|
|
//! Messages to and from the server.
|
2015-01-13 09:37:38 +01:00
|
|
|
#![unstable = "New features were added recently."]
|
2014-12-23 18:53:30 +01:00
|
|
|
use std::borrow::ToOwned;
|
2014-11-19 01:00:18 +01:00
|
|
|
use std::str::FromStr;
|
2014-11-03 00:08:56 +01:00
|
|
|
|
2014-11-06 21:23:02 +01:00
|
|
|
/// IRC Message data.
|
2015-01-10 00:36:22 +01:00
|
|
|
#[stable]
|
2015-01-04 07:18:26 +01:00
|
|
|
#[derive(Clone, PartialEq, Show)]
|
2014-11-02 23:25:45 +01:00
|
|
|
pub struct Message {
|
2014-11-06 21:23:02 +01:00
|
|
|
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
2014-11-02 23:25:45 +01:00
|
|
|
pub prefix: Option<String>,
|
2014-11-06 21:23:02 +01:00
|
|
|
/// The IRC command as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
2014-11-02 23:25:45 +01:00
|
|
|
pub command: String,
|
2014-11-06 21:23:02 +01:00
|
|
|
/// The command arguments.
|
2014-11-02 23:25:45 +01:00
|
|
|
pub args: Vec<String>,
|
2014-11-06 21:23:02 +01:00
|
|
|
/// The message suffix as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
2014-11-03 06:52:15 +01:00
|
|
|
/// This is the only part of the message that is allowed to contain spaces.
|
2014-11-02 23:25:45 +01:00
|
|
|
pub suffix: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Message {
|
2014-11-06 21:23:02 +01:00
|
|
|
/// Creates a new Message.
|
2015-01-10 00:36:22 +01:00
|
|
|
#[stable]
|
2014-11-02 23:25:45 +01:00
|
|
|
pub fn new(prefix: Option<&str>, command: &str, args: Option<Vec<&str>>, suffix: Option<&str>)
|
|
|
|
-> Message {
|
|
|
|
Message {
|
2014-12-23 18:53:30 +01:00
|
|
|
prefix: prefix.map(|s| s.to_owned()),
|
|
|
|
command: command.to_owned(),
|
2014-12-23 19:31:10 +01:00
|
|
|
args: args.map_or(Vec::new(), |v| v.iter().map(|&s| s.to_owned()).collect()),
|
2014-12-23 18:53:30 +01:00
|
|
|
suffix: suffix.map(|s| s.to_owned()),
|
2014-11-02 23:25:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-02 21:20:26 +01:00
|
|
|
/// Gets the nickname of the message source, if it exists.
|
2015-01-10 00:36:22 +01:00
|
|
|
#[experimental = "Feature is new."]
|
2015-01-02 21:20:26 +01:00
|
|
|
pub fn get_source_nickname(&self) -> Option<&str> {
|
2015-01-09 23:38:46 +01:00
|
|
|
self.prefix.as_ref().and_then(|s| s.find('!').map(|i| &s[..i]))
|
2015-01-02 21:20:26 +01:00
|
|
|
}
|
|
|
|
|
2014-11-06 21:23:02 +01:00
|
|
|
/// Converts a Message into a String according to the IRC protocol.
|
2015-01-10 00:36:22 +01:00
|
|
|
#[stable]
|
2014-11-02 23:25:45 +01:00
|
|
|
pub fn into_string(&self) -> String {
|
|
|
|
let mut ret = String::new();
|
|
|
|
if let Some(ref prefix) = self.prefix {
|
|
|
|
ret.push(':');
|
2015-01-09 23:38:46 +01:00
|
|
|
ret.push_str(&prefix[]);
|
2014-11-02 23:25:45 +01:00
|
|
|
ret.push(' ');
|
|
|
|
}
|
2015-01-09 23:38:46 +01:00
|
|
|
ret.push_str(&self.command[]);
|
2014-11-02 23:25:45 +01:00
|
|
|
for arg in self.args.iter() {
|
|
|
|
ret.push(' ');
|
2015-01-09 23:38:46 +01:00
|
|
|
ret.push_str(&arg[]);
|
2014-11-02 23:25:45 +01:00
|
|
|
}
|
|
|
|
if let Some(ref suffix) = self.suffix {
|
|
|
|
ret.push_str(" :");
|
2015-01-09 23:38:46 +01:00
|
|
|
ret.push_str(&suffix[]);
|
2014-11-02 23:25:45 +01:00
|
|
|
}
|
2014-11-03 09:02:35 +01:00
|
|
|
ret.push_str("\r\n");
|
2014-11-02 23:25:45 +01:00
|
|
|
ret
|
|
|
|
}
|
|
|
|
}
|
2014-11-03 00:08:56 +01:00
|
|
|
|
2015-01-01 00:39:00 +01:00
|
|
|
impl ToMessage for Message {
|
|
|
|
fn to_message(&self) -> Message {
|
|
|
|
self.clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-03 00:08:56 +01:00
|
|
|
impl FromStr for Message {
|
|
|
|
fn from_str(s: &str) -> Option<Message> {
|
|
|
|
let mut state = s.clone();
|
|
|
|
if s.len() == 0 { return None }
|
|
|
|
let prefix = if state.starts_with(":") {
|
2015-01-09 23:38:46 +01:00
|
|
|
let prefix = state.find(' ').map(|i| &state[1..i]);
|
|
|
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
2014-11-03 00:08:56 +01:00
|
|
|
prefix
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let suffix = if state.contains(":") {
|
2015-01-09 23:38:46 +01:00
|
|
|
let suffix = state.find(':').map(|i| &state[i+1..state.len()-2]);
|
|
|
|
state = state.find(':').map_or("", |i| &state[..i]);
|
2014-11-03 00:08:56 +01:00
|
|
|
suffix
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2015-01-09 23:38:46 +01:00
|
|
|
let command = match state.find(' ').map(|i| &state[..i]) {
|
2014-11-03 08:02:29 +01:00
|
|
|
Some(cmd) => {
|
2015-01-09 23:38:46 +01:00
|
|
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
2014-11-03 08:02:29 +01:00
|
|
|
cmd
|
|
|
|
}
|
|
|
|
_ => return None
|
2014-11-03 00:08:56 +01:00
|
|
|
};
|
2015-01-09 23:38:46 +01:00
|
|
|
if suffix.is_none() { state = &state[..state.len() - 2] }
|
2014-11-03 08:30:58 +01:00
|
|
|
let args: Vec<_> = state.splitn(14, ' ').filter(|s| s.len() != 0).collect();
|
2014-11-03 00:08:56 +01:00
|
|
|
Some(Message::new(prefix, command, if args.len() > 0 { Some(args) } else { None }, suffix))
|
|
|
|
}
|
|
|
|
}
|
2014-11-05 07:45:17 +01:00
|
|
|
|
2015-01-01 02:05:10 +01:00
|
|
|
/// A trait representing the ability to be converted into a Message.
|
2015-01-10 00:36:22 +01:00
|
|
|
#[experimental = "Design is new."]
|
2015-01-01 02:05:10 +01:00
|
|
|
pub trait ToMessage {
|
|
|
|
/// Converts this to a Message.
|
|
|
|
fn to_message(&self) -> Message;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> ToMessage for &'a str {
|
|
|
|
fn to_message(&self) -> Message {
|
|
|
|
self.parse().unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-05 07:45:17 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2015-01-01 02:05:10 +01:00
|
|
|
use super::{Message, ToMessage};
|
2014-11-05 07:45:17 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn new() {
|
|
|
|
let message = Message {
|
|
|
|
prefix: None,
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Testing!")),
|
|
|
|
};
|
|
|
|
assert_eq!(Message::new(None, "PRIVMSG", Some(vec!["test"]), Some("Testing!")), message);
|
|
|
|
}
|
|
|
|
|
2015-01-02 21:20:26 +01:00
|
|
|
#[test]
|
|
|
|
fn get_source_nickname() {
|
|
|
|
assert_eq!(Message::new(None, "PING", None, None).get_source_nickname(), None);
|
|
|
|
assert_eq!(Message::new(
|
|
|
|
Some("irc.test.net"), "PING", None, None
|
|
|
|
).get_source_nickname(), None);
|
|
|
|
assert_eq!(Message::new(
|
|
|
|
Some("test!test@test"), "PING", None, None
|
|
|
|
).get_source_nickname(), Some("test"));
|
|
|
|
}
|
|
|
|
|
2014-11-05 07:45:17 +01:00
|
|
|
#[test]
|
|
|
|
fn into_string() {
|
|
|
|
let message = Message {
|
|
|
|
prefix: None,
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Testing!")),
|
|
|
|
};
|
2015-01-09 23:38:46 +01:00
|
|
|
assert_eq!(&message.into_string()[], "PRIVMSG test :Testing!\r\n");
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
|
|
|
prefix: Some(format!("test!test@test")),
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Still testing!")),
|
|
|
|
};
|
2015-01-09 23:38:46 +01:00
|
|
|
assert_eq!(&message.into_string()[], ":test!test@test PRIVMSG test :Still testing!\r\n");
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn from_string() {
|
|
|
|
let message = Message {
|
|
|
|
prefix: None,
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Testing!")),
|
|
|
|
};
|
2014-12-23 18:53:30 +01:00
|
|
|
assert_eq!("PRIVMSG test :Testing!\r\n".parse(), Some(message));
|
2014-11-05 07:45:17 +01:00
|
|
|
let message = Message {
|
|
|
|
prefix: Some(format!("test!test@test")),
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Still testing!")),
|
|
|
|
};
|
2014-12-23 18:53:30 +01:00
|
|
|
assert_eq!(":test!test@test PRIVMSG test :Still testing!\r\n".parse(), Some(message));
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|
2015-01-01 02:05:10 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn to_message() {
|
|
|
|
let message = Message {
|
|
|
|
prefix: None,
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Testing!")),
|
|
|
|
};
|
|
|
|
assert_eq!("PRIVMSG test :Testing!\r\n".to_message(), message);
|
|
|
|
let message = Message {
|
|
|
|
prefix: Some(format!("test!test@test")),
|
|
|
|
command: format!("PRIVMSG"),
|
|
|
|
args: vec![format!("test")],
|
|
|
|
suffix: Some(format!("Still testing!")),
|
|
|
|
};
|
|
|
|
assert_eq!(":test!test@test PRIVMSG test :Still testing!\r\n".to_message(), message);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[should_fail]
|
|
|
|
fn to_message_invalid_format() {
|
|
|
|
":invalid :message".to_message();
|
|
|
|
}
|
2014-11-05 07:45:17 +01:00
|
|
|
}
|