Merge pull request #149 from brigand/feat/develop/message-prefix
Introduces Prefix enum
This commit is contained in:
commit
47d9c4c78b
4 changed files with 228 additions and 23 deletions
|
@ -23,6 +23,7 @@ pub mod irc;
|
||||||
pub mod line;
|
pub mod line;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
|
pub mod prefix;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
pub use self::caps::{Capability, NegotiationVersion};
|
pub use self::caps::{Capability, NegotiationVersion};
|
||||||
|
@ -33,4 +34,5 @@ pub use self::command::{BatchSubCommand, CapSubCommand, Command};
|
||||||
pub use self::irc::IrcCodec;
|
pub use self::irc::IrcCodec;
|
||||||
pub use self::message::Message;
|
pub use self::message::Message;
|
||||||
pub use self::mode::{ChannelMode, Mode, UserMode};
|
pub use self::mode::{ChannelMode, Mode, UserMode};
|
||||||
|
pub use self::prefix::Prefix;
|
||||||
pub use self::response::Response;
|
pub use self::response::Response;
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
//! A module providing a data structure for messages to and from IRC servers.
|
//! A module providing a data structure for messages to and from IRC servers.
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult, Write};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use error;
|
|
||||||
use error::{ProtocolError, MessageParseError};
|
|
||||||
use chan::ChannelExt;
|
use chan::ChannelExt;
|
||||||
use command::Command;
|
use command::Command;
|
||||||
|
use error;
|
||||||
|
use error::{MessageParseError, ProtocolError};
|
||||||
|
use prefix::Prefix;
|
||||||
|
|
||||||
|
|
||||||
/// A data structure representing an IRC message according to the protocol specification. It
|
/// A data structure representing an IRC message according to the protocol specification. It
|
||||||
/// consists of a collection of IRCv3 tags, a prefix (describing the source of the message), and
|
/// consists of a collection of IRCv3 tags, a prefix (describing the source of the message), and
|
||||||
|
@ -20,7 +22,7 @@ pub struct Message {
|
||||||
/// in IRCv3 extensions to the IRC protocol.
|
/// in IRCv3 extensions to the IRC protocol.
|
||||||
pub tags: Option<Vec<Tag>>,
|
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<Prefix>,
|
||||||
/// The IRC command, parsed according to the known specifications. The command itself and its
|
/// The IRC command, parsed according to the known specifications. The command itself and its
|
||||||
/// arguments (including the special suffix argument) are captured in this component.
|
/// arguments (including the special suffix argument) are captured in this component.
|
||||||
pub command: Command,
|
pub command: Command,
|
||||||
|
@ -60,7 +62,7 @@ impl Message {
|
||||||
) -> Result<Message, error::MessageParseError> {
|
) -> Result<Message, error::MessageParseError> {
|
||||||
Ok(Message {
|
Ok(Message {
|
||||||
tags: tags,
|
tags: tags,
|
||||||
prefix: prefix.map(|s| s.to_owned()),
|
prefix: prefix.map(|p| p.into()),
|
||||||
command: Command::new(command, args, suffix)?,
|
command: Command::new(command, args, suffix)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -81,15 +83,9 @@ impl Message {
|
||||||
pub fn source_nickname(&self) -> Option<&str> {
|
pub fn source_nickname(&self) -> Option<&str> {
|
||||||
// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
|
// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
|
||||||
// <servername> ::= <host>
|
// <servername> ::= <host>
|
||||||
self.prefix.as_ref().and_then(|s| match (
|
self.prefix.as_ref().and_then(|p| match p {
|
||||||
s.find('!'),
|
Prefix::Nickname(name, _, _) => Some(&name[..]),
|
||||||
s.find('@'),
|
_ => None
|
||||||
s.find('.'),
|
|
||||||
) {
|
|
||||||
(Some(i), _, _) | // <nick> '!' <user> [ '@' <host> ]
|
|
||||||
(None, Some(i), _) => Some(&s[..i]), // <nick> '@' <host>
|
|
||||||
(None, None, None) => Some(s), // <nick>
|
|
||||||
_ => None, // <servername>
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,9 +145,7 @@ impl Message {
|
||||||
ret.push(' ');
|
ret.push(' ');
|
||||||
}
|
}
|
||||||
if let Some(ref prefix) = self.prefix {
|
if let Some(ref prefix) = self.prefix {
|
||||||
ret.push(':');
|
write!(ret, ":{} ", prefix).unwrap();
|
||||||
ret.push_str(prefix);
|
|
||||||
ret.push(' ');
|
|
||||||
}
|
}
|
||||||
let cmd: String = From::from(&self.command);
|
let cmd: String = From::from(&self.command);
|
||||||
ret.push_str(&cmd);
|
ret.push_str(&cmd);
|
||||||
|
@ -362,7 +356,7 @@ mod test {
|
||||||
assert_eq!(&message.to_string()[..], "PRIVMSG test :Testing!\r\n");
|
assert_eq!(&message.to_string()[..], "PRIVMSG test :Testing!\r\n");
|
||||||
let message = Message {
|
let message = Message {
|
||||||
tags: None,
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some("test!test@test".into()),
|
||||||
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -384,7 +378,7 @@ mod test {
|
||||||
);
|
);
|
||||||
let message = Message {
|
let message = Message {
|
||||||
tags: None,
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some("test!test@test".into()),
|
||||||
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -399,7 +393,7 @@ mod test {
|
||||||
Tag(format!("ccc"), None),
|
Tag(format!("ccc"), None),
|
||||||
Tag(format!("example.com/ddd"), Some(format!("eee"))),
|
Tag(format!("example.com/ddd"), Some(format!("eee"))),
|
||||||
]),
|
]),
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some("test!test@test".into()),
|
||||||
command: PRIVMSG(format!("test"), format!("Testing with tags!")),
|
command: PRIVMSG(format!("test"), format!("Testing with tags!")),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -450,7 +444,7 @@ mod test {
|
||||||
assert_eq!(msg, message);
|
assert_eq!(msg, message);
|
||||||
let message = Message {
|
let message = Message {
|
||||||
tags: None,
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some("test!test@test".into()),
|
||||||
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
command: PRIVMSG(format!("test"), format!("Still testing!")),
|
||||||
};
|
};
|
||||||
let msg: Message = ":test!test@test PRIVMSG test :Still testing!\r\n".into();
|
let msg: Message = ":test!test@test PRIVMSG test :Still testing!\r\n".into();
|
||||||
|
@ -463,7 +457,7 @@ mod test {
|
||||||
// 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,
|
tags: None,
|
||||||
prefix: Some(format!("test!test@test")),
|
prefix: Some("test!test@test".into()),
|
||||||
command: Raw(
|
command: Raw(
|
||||||
format!("COMMAND"),
|
format!("COMMAND"),
|
||||||
vec![format!("ARG:test")],
|
vec![format!("ARG:test")],
|
||||||
|
|
209
irc-proto/src/prefix.rs
Normal file
209
irc-proto/src/prefix.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
//! A module providing an enum for a message prefix.
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// The Prefix indicates "the true origin of the message", according to the server.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
|
pub enum Prefix {
|
||||||
|
/// servername, e.g. collins.mozilla.org
|
||||||
|
ServerName(String),
|
||||||
|
/// nickname [ ["!" username] "@" hostname ]
|
||||||
|
/// i.e. Nickname(nickname, username, hostname)
|
||||||
|
/// Any of the strings may be ""
|
||||||
|
Nickname(String, String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prefix {
|
||||||
|
/// Creates a prefix by parsing a string.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// # extern crate irc_proto;
|
||||||
|
/// # use irc_proto::Prefix;
|
||||||
|
/// # fn main() {
|
||||||
|
/// Prefix::new_from_str("nickname!username@hostname");
|
||||||
|
/// Prefix::new_from_str("example.com");
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn new_from_str(s: &str) -> Prefix {
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum Active {
|
||||||
|
Name,
|
||||||
|
User,
|
||||||
|
Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut user = String::new();
|
||||||
|
let mut host = String::new();
|
||||||
|
let mut active = Active::Name;
|
||||||
|
let mut is_server = false;
|
||||||
|
|
||||||
|
for c in s.chars() {
|
||||||
|
if c == '.' && active == Active::Name {
|
||||||
|
// We won't return Nickname("nick", "", "") but if @ or ! are
|
||||||
|
// encountered, then we set this back to false
|
||||||
|
is_server = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
match c {
|
||||||
|
'!' if active == Active::Name => {
|
||||||
|
is_server = false;
|
||||||
|
active = Active::User;
|
||||||
|
},
|
||||||
|
|
||||||
|
'@' if active != Active::Host => {
|
||||||
|
is_server = false;
|
||||||
|
active = Active::Host;
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
// Push onto the active buffer
|
||||||
|
match active {
|
||||||
|
Active::Name => &mut name,
|
||||||
|
Active::User => &mut user,
|
||||||
|
Active::Host => &mut host,
|
||||||
|
}.push(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_server {
|
||||||
|
Prefix::ServerName(name)
|
||||||
|
} else {
|
||||||
|
Prefix::Nickname(name, user, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This implementation never returns an error and is isomorphic with `Display`.
|
||||||
|
impl FromStr for Prefix {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Prefix::new_from_str(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is isomorphic with `FromStr`
|
||||||
|
impl fmt::Display for Prefix {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Prefix::ServerName(name) => write!(f, "{}", name),
|
||||||
|
Prefix::Nickname(name, user, host) => match (&name[..], &user[..], &host[..]) {
|
||||||
|
("", "", "") => write!(f, ""),
|
||||||
|
(name, "", "") => write!(f, "{}", name),
|
||||||
|
(name, user, "") => write!(f, "{}!{}", name, user),
|
||||||
|
(name, "", host) => write!(f, "{}@{}", name, host),
|
||||||
|
(name, user, host) => write!(f, "{}!{}@{}", name, user, host),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Prefix {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Prefix::new_from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Prefix::{self, ServerName, Nickname};
|
||||||
|
|
||||||
|
// Checks that str -> parsed -> Display doesn't lose data
|
||||||
|
fn test_parse(s: &str) -> Prefix {
|
||||||
|
let prefix = Prefix::new_from_str(s);
|
||||||
|
let s2 = format!("{}", prefix);
|
||||||
|
assert_eq!(s, &s2);
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn print() {
|
||||||
|
let s = format!("{}", Nickname("nick".into(), "".into(), "".into()));
|
||||||
|
assert_eq!(&s, "nick");
|
||||||
|
let s = format!("{}", Nickname("nick".into(), "user".into(), "".into()));
|
||||||
|
assert_eq!(&s, "nick!user");
|
||||||
|
let s = format!("{}", Nickname("nick".into(), "user".into(), "host".into()));
|
||||||
|
assert_eq!(&s, "nick!user@host");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_word() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("only_nick"),
|
||||||
|
Nickname("only_nick".into(), String::new(), String::new())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_host() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("host.tld"),
|
||||||
|
ServerName("host.tld".into())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_nick_user() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("test!nick"),
|
||||||
|
Nickname("test".into(), "nick".into(), String::new())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_nick_user_host() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("test!nick@host"),
|
||||||
|
Nickname("test".into(), "nick".into(), "host".into())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_dot_and_symbols() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("test.net@something"),
|
||||||
|
Nickname("test.net".into(), "".into(), "something".into())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_danger_cases() {
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("name@name!user"),
|
||||||
|
Nickname("name".into(), "".into(), "name!user".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
// can't reverse the parse
|
||||||
|
"name!@".parse::<Prefix>().unwrap(),
|
||||||
|
Nickname("name".into(), "".into(), "".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
// can't reverse the parse
|
||||||
|
"name!@hostname".parse::<Prefix>().unwrap(),
|
||||||
|
Nickname("name".into(), "".into(), "hostname".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("name!.user"),
|
||||||
|
Nickname("name".into(), ".user".into(), "".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("name!user.user"),
|
||||||
|
Nickname("name".into(), "user.user".into(), "".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("name!user@host.host"),
|
||||||
|
Nickname("name".into(), "user".into(), "host.host".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
test_parse("!user"),
|
||||||
|
Nickname("".into(), "user".into(), "".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"!@host.host".parse::<Prefix>().unwrap(),
|
||||||
|
Nickname("".into(), "".into(), "host.host".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ pub use client::data::Config;
|
||||||
pub use client::reactor::IrcReactor;
|
pub use client::reactor::IrcReactor;
|
||||||
pub use client::{EachIncomingExt, IrcClient, Client};
|
pub use client::{EachIncomingExt, IrcClient, Client};
|
||||||
pub use client::ext::ClientExt;
|
pub use client::ext::ClientExt;
|
||||||
pub use proto::{Capability, ChannelExt, Command, Message, NegotiationVersion, Response};
|
pub use proto::{Capability, ChannelExt, Command, Message, Prefix, NegotiationVersion, Response};
|
||||||
pub use proto::{ChannelMode, Mode, UserMode};
|
pub use proto::{ChannelMode, Mode, UserMode};
|
||||||
|
|
||||||
pub use futures::{Future, Stream};
|
pub use futures::{Future, Stream};
|
||||||
|
|
Loading…
Add table
Reference in a new issue