Added parsing support for IRCv3.2 message tags.
This commit is contained in:
parent
e1abb935b1
commit
7d39a12142
1 changed files with 48 additions and 3 deletions
|
@ -5,6 +5,8 @@ use std::str::FromStr;
|
||||||
/// IRC Message data.
|
/// IRC Message data.
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
|
/// Message tags as defined by [IRCv3.2](http://ircv3.net/specs/core/message-tags-3.2.html).
|
||||||
|
pub tags: Option<Vec<Tag>>,
|
||||||
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
/// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
/// The IRC command as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
/// The IRC command as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812).
|
||||||
|
@ -20,7 +22,14 @@ impl Message {
|
||||||
/// Creates a new Message.
|
/// Creates a new Message.
|
||||||
pub fn new(prefix: Option<&str>, command: &str, args: Option<Vec<&str>>, suffix: Option<&str>)
|
pub fn new(prefix: Option<&str>, command: &str, args: Option<Vec<&str>>, suffix: Option<&str>)
|
||||||
-> Message {
|
-> Message {
|
||||||
|
Message::with_tags(None, prefix, command, args, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Message optionally including IRCv3.2 message tags.
|
||||||
|
pub fn with_tags(tags: Option<Vec<Tag>>, prefix: Option<&str>, command: &str,
|
||||||
|
args: Option<Vec<&str>>, suffix: Option<&str>) -> Message {
|
||||||
Message {
|
Message {
|
||||||
|
tags: tags,
|
||||||
prefix: prefix.map(|s| s.to_owned()),
|
prefix: prefix.map(|s| s.to_owned()),
|
||||||
command: command.to_owned(),
|
command: command.to_owned(),
|
||||||
args: args.map_or(Vec::new(), |v| v.iter().map(|&s| s.to_owned()).collect()),
|
args: args.map_or(Vec::new(), |v| v.iter().map(|&s| s.to_owned()).collect()),
|
||||||
|
@ -32,7 +41,7 @@ impl Message {
|
||||||
pub fn from_owned(prefix: Option<String>, command: String, args: Option<Vec<String>>,
|
pub fn from_owned(prefix: Option<String>, command: String, args: Option<Vec<String>>,
|
||||||
suffix: Option<String>) -> Message {
|
suffix: Option<String>) -> Message {
|
||||||
Message {
|
Message {
|
||||||
prefix: prefix, command: command, args: args.unwrap_or(Vec::new()), suffix: suffix
|
tags: None, prefix: prefix, command: command, args: args.unwrap_or(Vec::new()), suffix: suffix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +77,17 @@ impl FromStr for Message {
|
||||||
fn from_str(s: &str) -> Result<Message, &'static str> {
|
fn from_str(s: &str) -> Result<Message, &'static str> {
|
||||||
let mut state = s.clone();
|
let mut state = s.clone();
|
||||||
if s.len() == 0 { return Err("Cannot parse an empty string as a message.") }
|
if s.len() == 0 { return Err("Cannot parse an empty string as a message.") }
|
||||||
|
let tags = if state.starts_with("@") {
|
||||||
|
let tags = state.find(' ').map(|i| &state[1..i]);
|
||||||
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
||||||
|
tags.map(|ts| ts.split(";").filter(|s| s.len() != 0).map(|s: &str| {
|
||||||
|
let mut iter = s.splitn(2, "=");
|
||||||
|
let (fst, snd) = (iter.next(), iter.next());
|
||||||
|
Tag(fst.unwrap_or("").to_owned(), snd.map(|s| s.to_owned()))
|
||||||
|
}).collect::<Vec<_>>())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let prefix = if state.starts_with(":") {
|
let prefix = if state.starts_with(":") {
|
||||||
let prefix = state.find(' ').map(|i| &state[1..i]);
|
let prefix = state.find(' ').map(|i| &state[1..i]);
|
||||||
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
state = state.find(' ').map_or("", |i| &state[i+1..]);
|
||||||
|
@ -91,7 +111,9 @@ impl FromStr for Message {
|
||||||
};
|
};
|
||||||
if suffix.is_none() { state = &state[..state.len() - 2] }
|
if suffix.is_none() { state = &state[..state.len() - 2] }
|
||||||
let args: Vec<_> = state.splitn(14, ' ').filter(|s| s.len() != 0).collect();
|
let args: Vec<_> = state.splitn(14, ' ').filter(|s| s.len() != 0).collect();
|
||||||
Ok(Message::new(prefix, command, if args.len() > 0 { Some(args) } else { None }, suffix))
|
Ok(Message::with_tags(
|
||||||
|
tags, prefix, command, if args.len() > 0 { Some(args) } else { None }, suffix
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,13 +123,18 @@ impl<'a> From<&'a str> for Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A message tag as defined by [IRCv3.2](http://ircv3.net/specs/core/message-tags-3.2.html).
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct Tag(String, Option<String>);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::Message;
|
use super::{Message, Tag};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new() {
|
fn new() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
|
@ -130,6 +157,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn into_string() {
|
fn into_string() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
|
@ -137,6 +165,7 @@ mod test {
|
||||||
};
|
};
|
||||||
assert_eq!(&message.into_string()[..], "PRIVMSG test :Testing!\r\n");
|
assert_eq!(&message.into_string()[..], "PRIVMSG test :Testing!\r\n");
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some(format!("test!test@test")),
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
|
@ -148,6 +177,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn from_string() {
|
fn from_string() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
|
@ -155,17 +185,30 @@ mod test {
|
||||||
};
|
};
|
||||||
assert_eq!("PRIVMSG test :Testing!\r\n".parse(), Ok(message));
|
assert_eq!("PRIVMSG test :Testing!\r\n".parse(), Ok(message));
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some(format!("test!test@test")),
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
suffix: Some(format!("Still testing!")),
|
suffix: Some(format!("Still testing!")),
|
||||||
};
|
};
|
||||||
assert_eq!(":test!test@test PRIVMSG test :Still testing!\r\n".parse(), Ok(message));
|
assert_eq!(":test!test@test PRIVMSG test :Still testing!\r\n".parse(), Ok(message));
|
||||||
|
let message = Message {
|
||||||
|
tags: Some(vec![Tag(format!("aaa"), Some(format!("bbb"))),
|
||||||
|
Tag(format!("ccc"), None),
|
||||||
|
Tag(format!("example.com/ddd"), Some(format!("eee")))]),
|
||||||
|
prefix: Some(format!("test!test@test")),
|
||||||
|
command: format!("PRIVMSG"),
|
||||||
|
args: vec![format!("test")],
|
||||||
|
suffix: Some(format!("Testing with tags!")),
|
||||||
|
};
|
||||||
|
assert_eq!("@aaa=bbb;ccc;example.com/ddd=eee :test!test@test PRIVMSG test :Testing with \
|
||||||
|
tags!\r\n".parse(), Ok(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_message() {
|
fn to_message() {
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
|
@ -174,6 +217,7 @@ mod test {
|
||||||
let msg: Message = "PRIVMSG test :Testing!\r\n".into();
|
let msg: Message = "PRIVMSG test :Testing!\r\n".into();
|
||||||
assert_eq!(msg, message);
|
assert_eq!(msg, message);
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some(format!("test!test@test")),
|
||||||
command: format!("PRIVMSG"),
|
command: format!("PRIVMSG"),
|
||||||
args: vec![format!("test")],
|
args: vec![format!("test")],
|
||||||
|
@ -188,6 +232,7 @@ mod test {
|
||||||
// Apparently, UnrealIRCd (and perhaps some others) send some messages that include
|
// Apparently, UnrealIRCd (and perhaps some others) send some messages that include
|
||||||
// colons within individual parameters. So, let's make sure it parses correctly.
|
// colons within individual parameters. So, let's make sure it parses correctly.
|
||||||
let message = Message {
|
let message = Message {
|
||||||
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some(format!("test!test@test")),
|
||||||
command: format!("COMMAND"),
|
command: format!("COMMAND"),
|
||||||
args: vec![format!("ARG:test")],
|
args: vec![format!("ARG:test")],
|
||||||
|
|
Loading…
Add table
Reference in a new issue