Ran rustfmt on all the code.

This commit is contained in:
Aaron Weiss 2017-06-19 13:59:26 -04:00
parent 388628d62a
commit f0f0b95038
No known key found for this signature in database
GPG key ID: 0237035D9BF03AE2
15 changed files with 1953 additions and 1178 deletions

View file

@ -9,14 +9,12 @@ fn main() {
nickname: Some("pickles".to_owned()), nickname: Some("pickles".to_owned()),
server: Some("irc.fyrechat.net".to_owned()), server: Some("irc.fyrechat.net".to_owned()),
channels: Some(vec!["#vana".to_owned()]), channels: Some(vec!["#vana".to_owned()]),
.. Default::default() ..Default::default()
}; };
let server = IrcServer::from_config(config).unwrap(); let server = IrcServer::from_config(config).unwrap();
server.identify().unwrap(); server.identify().unwrap();
let server = server.clone(); let server = server.clone();
let _ = spawn(move || { let _ = spawn(move || for msg in server.iter() {
for msg in server.iter() { print!("{}", msg.unwrap());
print!("{}", msg.unwrap());
}
}).join(); // You might not want to join here for actual multi-threading. }).join(); // You might not want to join here for actual multi-threading.
} }

View file

@ -9,7 +9,7 @@ fn main() {
alt_nicks: Some(vec!["bananas".to_owned(), "apples".to_owned()]), alt_nicks: Some(vec!["bananas".to_owned(), "apples".to_owned()]),
server: Some("irc.fyrechat.net".to_owned()), server: Some("irc.fyrechat.net".to_owned()),
channels: Some(vec!["#vana".to_owned()]), channels: Some(vec!["#vana".to_owned()]),
.. Default::default() ..Default::default()
}; };
let server = IrcServer::from_config(config).unwrap(); let server = IrcServer::from_config(config).unwrap();
server.identify().unwrap(); server.identify().unwrap();
@ -17,9 +17,11 @@ fn main() {
let message = message.unwrap(); // We'll just panic if there's an error. let message = message.unwrap(); // We'll just panic if there's an error.
print!("{}", message); print!("{}", message);
match message.command { match message.command {
Command::PRIVMSG(ref target, ref msg) => if msg.contains("pickles") { Command::PRIVMSG(ref target, ref msg) => {
server.send_privmsg(target, "Hi!").unwrap(); if msg.contains("pickles") {
}, server.send_privmsg(target, "Hi!").unwrap();
}
}
_ => (), _ => (),
} }
} }

View file

@ -10,7 +10,7 @@ fn main() {
channels: Some(vec!["#vana".to_owned()]), channels: Some(vec!["#vana".to_owned()]),
port: Some(6697), port: Some(6697),
use_ssl: Some(true), use_ssl: Some(true),
.. Default::default() ..Default::default()
}; };
let server = IrcServer::from_config(config).unwrap(); let server = IrcServer::from_config(config).unwrap();
server.identify().unwrap(); server.identify().unwrap();
@ -18,9 +18,11 @@ fn main() {
let message = message.unwrap(); // We'll just panic if there's an error. let message = message.unwrap(); // We'll just panic if there's an error.
print!("{}", message); print!("{}", message);
match message.command { match message.command {
Command::PRIVMSG(ref target, ref msg) => if msg.contains("pickles") { Command::PRIVMSG(ref target, ref msg) => {
server.send_privmsg(target, "Hi!").unwrap(); if msg.contains("pickles") {
}, server.send_privmsg(target, "Hi!").unwrap();
}
}
_ => (), _ => (),
} }
} }

View file

@ -10,7 +10,7 @@ fn main() {
nickname: Some("pickles".to_owned()), nickname: Some("pickles".to_owned()),
server: Some("irc.fyrechat.net".to_owned()), server: Some("irc.fyrechat.net".to_owned()),
channels: Some(vec!["#vana".to_owned()]), channels: Some(vec!["#vana".to_owned()]),
.. Default::default() ..Default::default()
}; };
let server = IrcServer::from_config(config).unwrap(); let server = IrcServer::from_config(config).unwrap();
server.identify().unwrap(); server.identify().unwrap();

View file

@ -1,16 +1,24 @@
//! Thread-safe connections on `IrcStreams`. //! Thread-safe connections on `IrcStreams`.
#[cfg(feature = "ssl")] use std::error::Error as StdError; #[cfg(feature = "ssl")]
use std::error::Error as StdError;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::{BufReader, BufWriter, Cursor, Result}; use std::io::{BufReader, BufWriter, Cursor, Result};
#[cfg(feature = "ssl")] use std::io::Error; #[cfg(feature = "ssl")]
#[cfg(feature = "ssl")] use std::io::ErrorKind; use std::io::Error;
#[cfg(feature = "ssl")]
use std::io::ErrorKind;
use std::net::TcpStream; use std::net::TcpStream;
#[cfg(feature = "ssl")] use std::result::Result as StdResult; #[cfg(feature = "ssl")]
use std::result::Result as StdResult;
use std::sync::Mutex; use std::sync::Mutex;
#[cfg(feature = "encode")] use encoding::DecoderTrap; #[cfg(feature = "encode")]
#[cfg(feature = "encode")] use encoding::label::encoding_from_whatwg_label; use encoding::DecoderTrap;
#[cfg(feature = "ssl")] use openssl::ssl::{SslContext, SslMethod, SslStream}; #[cfg(feature = "encode")]
#[cfg(feature = "ssl")] use openssl::ssl::error::SslError; use encoding::label::encoding_from_whatwg_label;
#[cfg(feature = "ssl")]
use openssl::ssl::{SslContext, SslMethod, SslStream};
#[cfg(feature = "ssl")]
use openssl::ssl::error::SslError;
/// A connection. /// A connection.
pub trait Connection { pub trait Connection {
@ -77,8 +85,12 @@ impl NetConnection {
/// connects to the specified server and returns a reader-writer pair. /// connects to the specified server and returns a reader-writer pair.
fn connect_internal(host: &str, port: u16) -> Result<NetReadWritePair> { fn connect_internal(host: &str, port: u16) -> Result<NetReadWritePair> {
let socket = try!(TcpStream::connect(&format!("{}:{}", host, port)[..])); let socket = try!(TcpStream::connect(&format!("{}:{}", host, port)[..]));
Ok((BufReader::new(NetStream::Unsecured(try!(socket.try_clone()))), Ok((
BufWriter::new(NetStream::Unsecured(socket)))) BufReader::new(
NetStream::Unsecured(try!(socket.try_clone())),
),
BufWriter::new(NetStream::Unsecured(socket)),
))
} }
/// Creates a thread-safe TCP connection to the specified server over SSL. /// Creates a thread-safe TCP connection to the specified server over SSL.
@ -94,14 +106,20 @@ impl NetConnection {
let socket = try!(TcpStream::connect(&format!("{}:{}", host, port)[..])); let socket = try!(TcpStream::connect(&format!("{}:{}", host, port)[..]));
let ssl = try!(ssl_to_io(SslContext::new(SslMethod::Sslv23))); let ssl = try!(ssl_to_io(SslContext::new(SslMethod::Sslv23)));
let ssl_socket = try!(ssl_to_io(SslStream::connect_generic(&ssl, socket))); let ssl_socket = try!(ssl_to_io(SslStream::connect_generic(&ssl, socket)));
Ok((BufReader::new(NetStream::Ssl(try!(ssl_socket.try_clone()))), Ok((
BufWriter::new(NetStream::Ssl(ssl_socket)))) BufReader::new(NetStream::Ssl(try!(ssl_socket.try_clone()))),
BufWriter::new(NetStream::Ssl(ssl_socket)),
))
} }
/// Panics because SSL support is not compiled in. /// Panics because SSL support is not compiled in.
#[cfg(not(feature = "ssl"))] #[cfg(not(feature = "ssl"))]
fn connect_ssl_internal(host: &str, port: u16) -> Result<NetReadWritePair> { fn connect_ssl_internal(host: &str, port: u16) -> Result<NetReadWritePair> {
panic!("Cannot connect to {}:{} over SSL without compiling with SSL support.", host, port) panic!(
"Cannot connect to {}:{} over SSL without compiling with SSL support.",
host,
port
)
} }
} }
@ -110,8 +128,9 @@ impl NetConnection {
fn ssl_to_io<T>(res: StdResult<T, SslError>) -> Result<T> { fn ssl_to_io<T>(res: StdResult<T, SslError>) -> Result<T> {
match res { match res {
Ok(x) => Ok(x), Ok(x) => Ok(x),
Err(e) => Err(Error::new(ErrorKind::Other, Err(e) => Err(Error::new(
&format!("An SSL error occurred. ({})", e.description())[..] ErrorKind::Other,
&format!("An SSL error occurred. ({})", e.description())[..],
)), )),
} }
} }
@ -149,7 +168,7 @@ impl Connection for NetConnection {
fn reconnect(&self) -> Result<()> { fn reconnect(&self) -> Result<()> {
let use_ssl = match *self.reader.lock().unwrap().get_ref() { let use_ssl = match *self.reader.lock().unwrap().get_ref() {
NetStream::Unsecured(_) => false, NetStream::Unsecured(_) => false,
#[cfg(feature = "ssl")] #[cfg(feature = "ssl")]
NetStream::Ssl(_) => true, NetStream::Ssl(_) => true,
}; };
@ -215,9 +234,10 @@ impl Connection for MockConnection {
#[cfg(feature = "encoding")] #[cfg(feature = "encoding")]
fn written(&self, encoding: &str) -> Option<String> { fn written(&self, encoding: &str) -> Option<String> {
encoding_from_whatwg_label(encoding).and_then(|enc| encoding_from_whatwg_label(encoding).and_then(|enc| {
enc.decode(&self.writer.lock().unwrap(), DecoderTrap::Replace).ok() enc.decode(&self.writer.lock().unwrap(), DecoderTrap::Replace)
) .ok()
})
} }
#[cfg(not(feature = "encoding"))] #[cfg(not(feature = "encoding"))]
@ -235,23 +255,36 @@ mod imp {
use std::io::Error; use std::io::Error;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::sync::Mutex; use std::sync::Mutex;
#[cfg(feature = "encode")] use encoding::{DecoderTrap, EncoderTrap}; #[cfg(feature = "encode")]
#[cfg(feature = "encode")] use encoding::label::encoding_from_whatwg_label; use encoding::{DecoderTrap, EncoderTrap};
#[cfg(feature = "encode")]
use encoding::label::encoding_from_whatwg_label;
use client::data::kinds::{IrcRead, IrcWrite}; use client::data::kinds::{IrcRead, IrcWrite};
#[cfg(feature = "encode")] #[cfg(feature = "encode")]
pub fn send<T: IrcWrite>(writer: &Mutex<T>, msg: &str, encoding: &str) -> Result<()> { pub fn send<T: IrcWrite>(writer: &Mutex<T>, msg: &str, encoding: &str) -> Result<()> {
let encoding = match encoding_from_whatwg_label(encoding) { let encoding = match encoding_from_whatwg_label(encoding) {
Some(enc) => enc, Some(enc) => enc,
None => return Err(Error::new( None => {
ErrorKind::InvalidInput, &format!("Failed to find encoder. ({})", encoding)[..] return Err(Error::new(
)) ErrorKind::InvalidInput,
&format!("Failed to find encoder. ({})", encoding)[..],
))
}
}; };
let data = match encoding.encode(msg, EncoderTrap::Replace) { let data = match encoding.encode(msg, EncoderTrap::Replace) {
Ok(data) => data, Ok(data) => data,
Err(data) => return Err(Error::new(ErrorKind::InvalidInput, Err(data) => {
&format!("Failed to encode {} as {}.", data, encoding.name())[..] return Err(Error::new(
)) ErrorKind::InvalidInput,
&format!(
"Failed to encode {} as {}.",
data,
encoding.name()
)
[..],
))
}
}; };
let mut writer = writer.lock().unwrap(); let mut writer = writer.lock().unwrap();
try!(writer.write_all(&data)); try!(writer.write_all(&data));
@ -269,20 +302,31 @@ mod imp {
pub fn recv<T: IrcRead>(reader: &Mutex<T>, encoding: &str) -> Result<String> { pub fn recv<T: IrcRead>(reader: &Mutex<T>, encoding: &str) -> Result<String> {
let encoding = match encoding_from_whatwg_label(encoding) { let encoding = match encoding_from_whatwg_label(encoding) {
Some(enc) => enc, Some(enc) => enc,
None => return Err(Error::new( None => {
ErrorKind::InvalidInput, &format!("Failed to find decoder. ({})", encoding)[..] return Err(Error::new(
)) ErrorKind::InvalidInput,
}; &format!("Failed to find decoder. ({})", encoding)[..],
let mut buf = Vec::new();
reader.lock().unwrap().read_until(b'\n', &mut buf).and_then(|_|
match encoding.decode(&buf, DecoderTrap::Replace) {
_ if buf.is_empty() => Err(Error::new(ErrorKind::Other, "EOF")),
Ok(data) => Ok(data),
Err(data) => Err(Error::new(ErrorKind::InvalidInput,
&format!("Failed to decode {} as {}.", data, encoding.name())[..]
)) ))
} }
) };
let mut buf = Vec::new();
reader
.lock()
.unwrap()
.read_until(b'\n', &mut buf)
.and_then(|_| match encoding.decode(&buf, DecoderTrap::Replace) {
_ if buf.is_empty() => Err(Error::new(ErrorKind::Other, "EOF")),
Ok(data) => Ok(data),
Err(data) => Err(Error::new(
ErrorKind::InvalidInput,
&format!(
"Failed to decode {} as {}.",
data,
encoding.name()
)
[..],
)),
})
} }
#[cfg(not(feature = "encoding"))] #[cfg(not(feature = "encoding"))]
@ -376,7 +420,13 @@ mod test {
#[cfg(feature = "encode")] #[cfg(feature = "encode")]
fn send_utf8() { fn send_utf8() {
let conn = MockConnection::empty(); let conn = MockConnection::empty();
assert!(send_to(&conn, PRIVMSG("test".to_owned(), "€ŠšŽžŒœŸ".to_owned()), "UTF-8").is_ok()); assert!(
send_to(
&conn,
PRIVMSG("test".to_owned(), "€ŠšŽžŒœŸ".to_owned()),
"UTF-8",
).is_ok()
);
let data = conn.written("UTF-8").unwrap(); let data = conn.written("UTF-8").unwrap();
assert_eq!(&data[..], "PRIVMSG test :€ŠšŽžŒœŸ\r\n"); assert_eq!(&data[..], "PRIVMSG test :€ŠšŽžŒœŸ\r\n");
} }
@ -395,7 +445,13 @@ mod test {
#[cfg(feature = "encode")] #[cfg(feature = "encode")]
fn send_iso885915() { fn send_iso885915() {
let conn = MockConnection::empty(); let conn = MockConnection::empty();
assert!(send_to(&conn, PRIVMSG("test".to_owned(), "€ŠšŽžŒœŸ".to_owned()), "l9").is_ok()); assert!(
send_to(
&conn,
PRIVMSG("test".to_owned(), "€ŠšŽžŒœŸ".to_owned()),
"l9",
).is_ok()
);
let data = conn.written("l9").unwrap(); let data = conn.written("l9").unwrap();
assert_eq!(&data[..], "PRIVMSG test :€ŠšŽžŒœŸ\r\n"); assert_eq!(&data[..], "PRIVMSG test :€ŠšŽžŒœŸ\r\n");
} }
@ -421,7 +477,10 @@ mod test {
#[cfg(feature = "encode")] #[cfg(feature = "encode")]
fn recv_utf8() { fn recv_utf8() {
let conn = MockConnection::new("PRIVMSG test :Testing!\r\n"); let conn = MockConnection::new("PRIVMSG test :Testing!\r\n");
assert_eq!(&conn.recv("UTF-8").unwrap()[..], "PRIVMSG test :Testing!\r\n"); assert_eq!(
&conn.recv("UTF-8").unwrap()[..],
"PRIVMSG test :Testing!\r\n"
);
} }
#[test] #[test]
@ -435,6 +494,9 @@ mod test {
vec.extend("\r\n".as_bytes()); vec.extend("\r\n".as_bytes());
vec.into_iter().collect::<Vec<_>>() vec.into_iter().collect::<Vec<_>>()
}); });
assert_eq!(&conn.recv("l9").unwrap()[..], "PRIVMSG test :€ŠšŽžŒœŸ\r\n"); assert_eq!(
&conn.recv("l9").unwrap()[..],
"PRIVMSG test :€ŠšŽžŒœŸ\r\n"
);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -66,22 +66,33 @@ impl Config {
let mut file = try!(File::open(path)); let mut file = try!(File::open(path));
let mut data = String::new(); let mut data = String::new();
try!(file.read_to_string(&mut data)); try!(file.read_to_string(&mut data));
serde_json::from_str(&data[..]).map_err(|_| serde_json::from_str(&data[..]).map_err(|_| {
Error::new(ErrorKind::InvalidInput, "Failed to decode configuration file.") Error::new(
) ErrorKind::InvalidInput,
"Failed to decode configuration file.",
)
})
} }
/// Saves a JSON configuration to the desired path. /// Saves a JSON configuration to the desired path.
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut file = try!(File::create(path)); let mut file = try!(File::create(path));
file.write_all(try!(serde_json::to_string(self).map_err(|_| file.write_all(
Error::new(ErrorKind::InvalidInput, "Failed to encode configuration file.") try!(serde_json::to_string(self).map_err(|_| {
)).as_bytes()) Error::new(
ErrorKind::InvalidInput,
"Failed to encode configuration file.",
)
})).as_bytes(),
)
} }
/// Determines whether or not the nickname provided is the owner of the bot. /// Determines whether or not the nickname provided is the owner of the bot.
pub fn is_owner(&self, nickname: &str) -> bool { pub fn is_owner(&self, nickname: &str) -> bool {
self.owners.as_ref().map(|o| o.contains(&nickname.to_owned())).unwrap() self.owners
.as_ref()
.map(|o| o.contains(&nickname.to_owned()))
.unwrap()
} }
/// Gets the nickname specified in the configuration. /// Gets the nickname specified in the configuration.
@ -99,7 +110,9 @@ impl Config {
/// Gets the alternate nicknames specified in the configuration. /// Gets the alternate nicknames specified in the configuration.
/// This defaults to an empty vector when not specified. /// This defaults to an empty vector when not specified.
pub fn alternate_nicknames(&self) -> Vec<&str> { pub fn alternate_nicknames(&self) -> Vec<&str> {
self.alt_nicks.as_ref().map_or(vec![], |v| v.iter().map(|s| &s[..]).collect()) self.alt_nicks.as_ref().map_or(vec![], |v| {
v.iter().map(|s| &s[..]).collect()
})
} }
@ -152,13 +165,17 @@ impl Config {
/// Gets the channels to join upon connection. /// Gets the channels to join upon connection.
/// This defaults to an empty vector if it's not specified. /// This defaults to an empty vector if it's not specified.
pub fn channels(&self) -> Vec<&str> { pub fn channels(&self) -> Vec<&str> {
self.channels.as_ref().map_or(vec![], |v| v.iter().map(|s| &s[..]).collect()) self.channels.as_ref().map_or(vec![], |v| {
v.iter().map(|s| &s[..]).collect()
})
} }
/// Gets the key for the specified channel if it exists in the configuration. /// Gets the key for the specified channel if it exists in the configuration.
pub fn channel_key(&self, chan: &str) -> Option<&str> { pub fn channel_key(&self, chan: &str) -> Option<&str> {
self.channel_keys.as_ref().and_then(|m| m.get(&chan.to_owned()).map(|s| &s[..])) self.channel_keys.as_ref().and_then(|m| {
m.get(&chan.to_owned()).map(|s| &s[..])
})
} }
/// Gets the user modes to set on connect specified in the configuration. /// Gets the user modes to set on connect specified in the configuration.
@ -182,7 +199,10 @@ impl Config {
/// Gets the string to be sent in response to CTCP SOURCE requests. /// Gets the string to be sent in response to CTCP SOURCE requests.
/// This defaults to `https://github.com/aatxe/irc` when not specified. /// This defaults to `https://github.com/aatxe/irc` when not specified.
pub fn source(&self) -> &str { pub fn source(&self) -> &str {
self.source.as_ref().map_or("https://github.com/aatxe/irc", |s| &s[..]) self.source.as_ref().map_or(
"https://github.com/aatxe/irc",
|s| &s[..],
)
} }
/// Gets the amount of time in seconds since last activity necessary for the client to ping the /// Gets the amount of time in seconds since last activity necessary for the client to ping the
@ -207,14 +227,19 @@ impl Config {
/// Gets the NickServ command sequence to recover a nickname. /// Gets the NickServ command sequence to recover a nickname.
/// This defaults to `["GHOST"]` when not specified. /// This defaults to `["GHOST"]` when not specified.
pub fn ghost_sequence(&self) -> Vec<&str> { pub fn ghost_sequence(&self) -> Vec<&str> {
self.ghost_sequence.as_ref().map_or(vec!["GHOST"], |v| v.iter().map(|s| &s[..]).collect()) self.ghost_sequence.as_ref().map_or(vec!["GHOST"], |v| {
v.iter().map(|s| &s[..]).collect()
})
} }
/// Looks up the specified string in the options map. /// Looks up the specified string in the options map.
/// This uses indexing, and thus panics when the string is not present. /// This uses indexing, and thus panics when the string is not present.
/// This will also panic if used and there are no options. /// This will also panic if used and there are no options.
pub fn get_option(&self, option: &str) -> &str { pub fn get_option(&self, option: &str) -> &str {
self.options.as_ref().map(|o| &o[&option.to_owned()][..]).unwrap() self.options
.as_ref()
.map(|o| &o[&option.to_owned()][..])
.unwrap()
} }
} }
@ -288,7 +313,7 @@ mod test {
fn is_owner() { fn is_owner() {
let cfg = Config { let cfg = Config {
owners: Some(vec![format!("test"), format!("test2")]), owners: Some(vec![format!("test"), format!("test2")]),
.. Default::default() ..Default::default()
}; };
assert!(cfg.is_owner("test")); assert!(cfg.is_owner("test"));
assert!(cfg.is_owner("test2")); assert!(cfg.is_owner("test2"));
@ -303,7 +328,7 @@ mod test {
map.insert(format!("testing"), format!("test")); map.insert(format!("testing"), format!("test"));
Some(map) Some(map)
}, },
.. Default::default() ..Default::default()
}; };
assert_eq!(cfg.get_option("testing"), "test"); assert_eq!(cfg.get_option("testing"), "test");
} }

View file

@ -1,7 +1,7 @@
//! Messages to and from the server. //! Messages to and from the server.
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fmt::{Display, Formatter, Result as FmtResult};
use std::io::{Result as IoResult}; use std::io::Result as IoResult;
use std::str::FromStr; use std::str::FromStr;
use client::data::Command; use client::data::Command;
@ -18,14 +18,23 @@ pub struct Message {
impl Message { impl Message {
/// Creates a new Message. /// Creates a new Message.
pub fn new(prefix: Option<&str>, command: &str, args: Vec<&str>, suffix: Option<&str>) pub fn new(
-> IoResult<Message> { prefix: Option<&str>,
command: &str,
args: Vec<&str>,
suffix: Option<&str>,
) -> IoResult<Message> {
Message::with_tags(None, prefix, command, args, suffix) Message::with_tags(None, prefix, command, args, suffix)
} }
/// Creates a new Message optionally including IRCv3.2 message tags. /// Creates a new Message optionally including IRCv3.2 message tags.
pub fn with_tags(tags: Option<Vec<Tag>>, prefix: Option<&str>, command: &str, pub fn with_tags(
args: Vec<&str>, suffix: Option<&str>) -> IoResult<Message> { tags: Option<Vec<Tag>>,
prefix: Option<&str>,
command: &str,
args: Vec<&str>,
suffix: Option<&str>,
) -> IoResult<Message> {
Ok(Message { Ok(Message {
tags: tags, tags: tags,
prefix: prefix.map(|s| s.to_owned()), prefix: prefix.map(|s| s.to_owned()),
@ -37,14 +46,16 @@ 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| self.prefix.as_ref().and_then(|s| match (
match (s.find('!'), s.find('@'), s.find('.')) { s.find('!'),
(Some(i), _, _) => Some(&s[..i]), // <nick> '!' <user> [ '@' <host> ] s.find('@'),
(None, Some(i), _) => Some(&s[..i]), // <nick> '@' <host> s.find('.'),
(None, None, None) => Some(s), // <nick> ) {
_ => None // <servername> (Some(i), _, _) => Some(&s[..i]), // <nick> '!' <user> [ '@' <host> ]
} (None, Some(i), _) => Some(&s[..i]), // <nick> '@' <host>
) (None, None, None) => Some(s), // <nick>
_ => None, // <servername>
})
} }
/// Converts a Message into a String according to the IRC protocol. /// Converts a Message into a String according to the IRC protocol.
@ -65,7 +76,11 @@ impl Message {
impl From<Command> for Message { impl From<Command> for Message {
fn from(cmd: Command) -> Message { fn from(cmd: Command) -> Message {
Message { tags: None, prefix: None, command: cmd } Message {
tags: None,
prefix: None,
command: cmd,
}
} }
} }
@ -73,44 +88,52 @@ impl FromStr for Message {
type Err = &'static str; type Err = &'static str;
fn from_str(s: &str) -> Result<Message, &'static str> { fn from_str(s: &str) -> Result<Message, &'static str> {
let mut state = s; let mut state = s;
if s.is_empty() { return Err("Cannot parse an empty string as a message.") } if s.is_empty() {
return Err("Cannot parse an empty string as a message.");
}
let tags = if state.starts_with('@') { let tags = if state.starts_with('@') {
let tags = state.find(' ').map(|i| &state[1..i]); let tags = 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..]);
tags.map(|ts| ts.split(';').filter(|s| !s.is_empty()).map(|s: &str| { tags.map(|ts| {
let mut iter = s.splitn(2, '='); ts.split(';')
let (fst, snd) = (iter.next(), iter.next()); .filter(|s| !s.is_empty())
Tag(fst.unwrap_or("").to_owned(), snd.map(|s| s.to_owned())) .map(|s: &str| {
}).collect::<Vec<_>>()) 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 { } else {
None 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..]);
prefix prefix
} else { } else {
None None
}; };
let suffix = if state.contains(" :") { let suffix = if state.contains(" :") {
let suffix = state.find(" :").map(|i| &state[i+2..state.len()-2]); let suffix = state.find(" :").map(|i| &state[i + 2..state.len() - 2]);
state = state.find(" :").map_or("", |i| &state[..i+1]); state = state.find(" :").map_or("", |i| &state[..i + 1]);
suffix suffix
} else { } else {
None None
}; };
let command = match state.find(' ').map(|i| &state[..i]) { let command = match state.find(' ').map(|i| &state[..i]) {
Some(cmd) => { Some(cmd) => {
state = state.find(' ').map_or("", |i| &state[i+1..]); state = state.find(' ').map_or("", |i| &state[i + 1..]);
cmd cmd
} }
_ => return Err("Cannot parse a message without a command.") _ => return Err("Cannot parse a message without a command."),
}; };
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.is_empty()).collect(); let args: Vec<_> = state.splitn(14, ' ').filter(|s| !s.is_empty()).collect();
Message::with_tags( Message::with_tags(tags, prefix, command, args, suffix)
tags, prefix, command, args, suffix .map_err(|_| "Invalid input for Command.")
).map_err(|_| "Invalid input for Command.")
} }
} }
@ -142,42 +165,69 @@ mod test {
prefix: None, prefix: None,
command: PRIVMSG(format!("test"), format!("Testing!")), command: PRIVMSG(format!("test"), format!("Testing!")),
}; };
assert_eq!(Message::new(None, "PRIVMSG", vec!["test"], Some("Testing!")).unwrap(), message) assert_eq!(
Message::new(None, "PRIVMSG", vec!["test"], Some("Testing!")).unwrap(),
message
)
} }
#[test] #[test]
fn source_nickname() { fn source_nickname() {
assert_eq!(Message::new( assert_eq!(
None, "PING", vec![], Some("data") Message::new(None, "PING", vec![], Some("data"))
).unwrap().source_nickname(), None); .unwrap()
.source_nickname(),
None
);
assert_eq!(Message::new( assert_eq!(
Some("irc.test.net"), "PING", vec![], Some("data") Message::new(Some("irc.test.net"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), None); .unwrap()
.source_nickname(),
None
);
assert_eq!(Message::new( assert_eq!(
Some("test!test@test"), "PING", vec![], Some("data") Message::new(Some("test!test@test"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), Some("test")); .unwrap()
.source_nickname(),
Some("test")
);
assert_eq!(Message::new( assert_eq!(
Some("test@test"), "PING", vec![], Some("data") Message::new(Some("test@test"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), Some("test")); .unwrap()
.source_nickname(),
Some("test")
);
assert_eq!(Message::new( assert_eq!(
Some("test!test@irc.test.com"), "PING", vec![], Some("data") Message::new(Some("test!test@irc.test.com"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), Some("test")); .unwrap()
.source_nickname(),
Some("test")
);
assert_eq!(Message::new( assert_eq!(
Some("test!test@127.0.0.1"), "PING", vec![], Some("data") Message::new(Some("test!test@127.0.0.1"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), Some("test")); .unwrap()
.source_nickname(),
Some("test")
);
assert_eq!(Message::new( assert_eq!(
Some("test@test.com"), "PING", vec![], Some("data") Message::new(Some("test@test.com"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), Some("test")); .unwrap()
.source_nickname(),
Some("test")
);
assert_eq!(Message::new( assert_eq!(
Some("test"), "PING", vec![], Some("data") Message::new(Some("test"), "PING", vec![], Some("data"))
).unwrap().source_nickname(), Some("test")); .unwrap()
.source_nickname(),
Some("test")
);
} }
#[test] #[test]
@ -193,7 +243,10 @@ mod test {
prefix: Some(format!("test!test@test")), prefix: Some(format!("test!test@test")),
command: PRIVMSG(format!("test"), format!("Still testing!")), command: PRIVMSG(format!("test"), format!("Still testing!")),
}; };
assert_eq!(&message.to_string()[..], ":test!test@test PRIVMSG test :Still testing!\r\n"); assert_eq!(
&message.to_string()[..],
":test!test@test PRIVMSG test :Still testing!\r\n"
);
} }
#[test] #[test]
@ -209,16 +262,25 @@ mod test {
prefix: Some(format!("test!test@test")), prefix: Some(format!("test!test@test")),
command: PRIVMSG(format!("test"), format!("Still testing!")), command: PRIVMSG(format!("test"), 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 { let message = Message {
tags: Some(vec![Tag(format!("aaa"), Some(format!("bbb"))), tags: Some(vec![
Tag(format!("ccc"), None), Tag(format!("aaa"), Some(format!("bbb"))),
Tag(format!("example.com/ddd"), Some(format!("eee")))]), Tag(format!("ccc"), None),
Tag(format!("example.com/ddd"), Some(format!("eee"))),
]),
prefix: Some(format!("test!test@test")), prefix: Some(format!("test!test@test")),
command: PRIVMSG(format!("test"), format!("Testing with tags!")), command: PRIVMSG(format!("test"), format!("Testing with tags!")),
}; };
assert_eq!("@aaa=bbb;ccc;example.com/ddd=eee :test!test@test PRIVMSG test :Testing with \ assert_eq!(
tags!\r\n".parse(), Ok(message)) "@aaa=bbb;ccc;example.com/ddd=eee :test!test@test PRIVMSG test :Testing with \
tags!\r\n"
.parse(),
Ok(message)
)
} }
#[test] #[test]
@ -247,7 +309,9 @@ mod test {
tags: None, tags: None,
prefix: Some(format!("test!test@test")), prefix: Some(format!("test!test@test")),
command: Raw( command: Raw(
format!("COMMAND"), vec![format!("ARG:test")], Some(format!("Testing!")) format!("COMMAND"),
vec![format!("ARG:test")],
Some(format!("Testing!")),
), ),
}; };
let msg: Message = ":test!test@test COMMAND ARG:test :Testing!\r\n".into(); let msg: Message = ":test!test@test COMMAND ARG:test :Testing!\r\n".into();

View file

@ -13,11 +13,19 @@ pub mod kinds {
/// Trait describing all possible Writers for this library. /// Trait describing all possible Writers for this library.
pub trait IrcWrite: Write + Sized + Send + 'static {} pub trait IrcWrite: Write + Sized + Send + 'static {}
impl<T> IrcWrite for T where T: Write + Sized + Send + 'static {} impl<T> IrcWrite for T
where
T: Write + Sized + Send + 'static,
{
}
/// Trait describing all possible Readers for this library. /// Trait describing all possible Readers for this library.
pub trait IrcRead: BufRead + Sized + Send + 'static {} pub trait IrcRead: BufRead + Sized + Send + 'static {}
impl<T> IrcRead for T where T: BufRead + Sized + Send + 'static {} impl<T> IrcRead for T
where
T: BufRead + Sized + Send + 'static,
{
}
} }
pub mod caps; pub mod caps;

View file

@ -369,7 +369,7 @@ impl FromStr for Response {
if let Ok(rc) = s.parse() { if let Ok(rc) = s.parse() {
match Response::from_u16(rc) { match Response::from_u16(rc) {
Some(r) => Ok(r), Some(r) => Ok(r),
None => Err("Failed to parse due to unknown response code.") None => Err("Failed to parse due to unknown response code."),
} }
} else { } else {
Err("Failed to parse response code.") Err("Failed to parse response code.")

View file

@ -25,9 +25,9 @@ impl User {
let ranks: Vec<_> = AccessLevelIterator::new(string).collect(); let ranks: Vec<_> = AccessLevelIterator::new(string).collect();
let mut state = &string[ranks.len()..]; let mut state = &string[ranks.len()..];
let nickname = state.find('!').map_or(state, |i| &state[..i]).to_owned(); let nickname = state.find('!').map_or(state, |i| &state[..i]).to_owned();
state = state.find('!').map_or("", |i| &state[i+1..]); state = state.find('!').map_or("", |i| &state[i + 1..]);
let username = state.find('@').map(|i| state[..i].to_owned()); let username = state.find('@').map(|i| state[..i].to_owned());
let hostname = state.find('@').map(|i| state[i+1..].to_owned()); let hostname = state.find('@').map(|i| state[i + 1..].to_owned());
User { User {
nickname: nickname, nickname: nickname,
username: username, username: username,
@ -89,8 +89,8 @@ impl User {
"-h" => self.sub_access_level(AccessLevel::HalfOp), "-h" => self.sub_access_level(AccessLevel::HalfOp),
"+v" => self.add_access_level(AccessLevel::Voice), "+v" => self.add_access_level(AccessLevel::Voice),
"-v" => self.sub_access_level(AccessLevel::Voice), "-v" => self.sub_access_level(AccessLevel::Voice),
_ => {}, _ => {}
} }
} }
/// Adds an access level to the list, and updates the highest level if necessary. /// Adds an access level to the list, and updates the highest level if necessary.
@ -123,7 +123,7 @@ impl User {
impl PartialEq for User { impl PartialEq for User {
fn eq(&self, other: &User) -> bool { fn eq(&self, other: &User) -> bool {
self.nickname == other.nickname && self.username == other.username && self.nickname == other.nickname && self.username == other.username &&
self.hostname == other.hostname self.hostname == other.hostname
} }
} }
@ -146,7 +146,9 @@ pub enum AccessLevel {
impl PartialOrd for AccessLevel { impl PartialOrd for AccessLevel {
fn partial_cmp(&self, other: &AccessLevel) -> Option<Ordering> { fn partial_cmp(&self, other: &AccessLevel) -> Option<Ordering> {
if self == other { return Some(Equal) } if self == other {
return Some(Equal);
}
match *self { match *self {
AccessLevel::Owner => Some(Greater), AccessLevel::Owner => Some(Greater),
AccessLevel::Admin => { AccessLevel::Admin => {
@ -155,28 +157,28 @@ impl PartialOrd for AccessLevel {
} else { } else {
Some(Greater) Some(Greater)
} }
}, }
AccessLevel::Oper => { AccessLevel::Oper => {
if other == &AccessLevel::Owner || other == &AccessLevel::Admin { if other == &AccessLevel::Owner || other == &AccessLevel::Admin {
Some(Less) Some(Less)
} else { } else {
Some(Greater) Some(Greater)
} }
}, }
AccessLevel::HalfOp => { AccessLevel::HalfOp => {
if other == &AccessLevel::Voice || other == &AccessLevel::Member { if other == &AccessLevel::Voice || other == &AccessLevel::Member {
Some(Greater) Some(Greater)
} else { } else {
Some(Less) Some(Less)
} }
}, }
AccessLevel::Voice => { AccessLevel::Voice => {
if other == &AccessLevel::Member { if other == &AccessLevel::Member {
Some(Greater) Some(Greater)
} else { } else {
Some(Less) Some(Less)
} }
}, }
AccessLevel::Member => Some(Less), AccessLevel::Member => Some(Less),
} }
} }
@ -192,7 +194,7 @@ impl FromStr for AccessLevel {
Some('%') => Ok(AccessLevel::HalfOp), Some('%') => Ok(AccessLevel::HalfOp),
Some('+') => Ok(AccessLevel::Voice), Some('+') => Ok(AccessLevel::Voice),
None => Err("No access level in an empty string."), None => Err("No access level in an empty string."),
_ => Err("Failed to parse access level."), _ => Err("Failed to parse access level."),
} }
} }
} }
@ -258,7 +260,7 @@ mod test {
username: None, username: None,
hostname: None, hostname: None,
highest_access_level: Owner, highest_access_level: Owner,
access_levels: vec![Owner, Admin, Voice, Member] access_levels: vec![Owner, Admin, Voice, Member],
}; };
assert_eq!(user, exp); assert_eq!(user, exp);
assert_eq!(user.highest_access_level, exp.highest_access_level); assert_eq!(user.highest_access_level, exp.highest_access_level);
@ -324,7 +326,10 @@ mod test {
fn derank_user_in_full() { fn derank_user_in_full() {
let mut user = User::new("~&@%+user"); let mut user = User::new("~&@%+user");
assert_eq!(user.highest_access_level, Owner); assert_eq!(user.highest_access_level, Owner);
assert_eq!(user.access_levels, vec![Owner, Admin, Oper, HalfOp, Voice, Member]); assert_eq!(
user.access_levels,
vec![Owner, Admin, Oper, HalfOp, Voice, Member]
);
user.update_access_level("-h"); user.update_access_level("-h");
assert_eq!(user.highest_access_level, Owner); assert_eq!(user.highest_access_level, Owner);
assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]); assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]);

View file

@ -23,7 +23,9 @@ pub trait Server {
fn config(&self) -> &Config; fn config(&self) -> &Config;
/// Sends a Command to this Server. /// Sends a Command to this Server.
fn send<M: Into<Message>>(&self, message: M) -> Result<()> where Self: Sized; fn send<M: Into<Message>>(&self, message: M) -> Result<()>
where
Self: Sized;
/// Gets an iterator over received messages. /// Gets an iterator over received messages.
fn iter<'a>(&'a self) -> Box<Iterator<Item = Result<Message>> + 'a>; fn iter<'a>(&'a self) -> Box<Iterator<Item = Result<Message>> + 'a>;
@ -66,7 +68,10 @@ struct ServerState {
} }
impl ServerState { impl ServerState {
fn new<C>(conn: C, config: Config) -> ServerState where C: Connection + Send + Sync + 'static { fn new<C>(conn: C, config: Config) -> ServerState
where
C: Connection + Send + Sync + 'static,
{
ServerState { ServerState {
conn: Box::new(conn), conn: Box::new(conn),
config: config, config: config,
@ -135,9 +140,11 @@ impl ServerState {
/// terminiating phrase (`\r\n`, `\r`, or `\n`). /// terminiating phrase (`\r\n`, `\r`, or `\n`).
fn sanitize(data: &str) -> &str { fn sanitize(data: &str) -> &str {
// n.b. ordering matters here to prefer "\r\n" over "\r" // n.b. ordering matters here to prefer "\r\n" over "\r"
if let Some((pos, len)) = ["\r\n", "\r", "\n"].iter().flat_map(|needle| { if let Some((pos, len)) = ["\r\n", "\r", "\n"]
data.find(needle).map(|pos| (pos, needle.len())) .iter()
}).min_by_key(|&(pos, _)| pos) { .flat_map(|needle| data.find(needle).map(|pos| (pos, needle.len())))
.min_by_key(|&(pos, _)| pos)
{
data.split_at(pos + len).0 data.split_at(pos + len).0
} else { } else {
data data
@ -146,12 +153,17 @@ impl ServerState {
#[cfg(feature = "encode")] #[cfg(feature = "encode")]
fn write<M: Into<Message>>(&self, msg: M) -> Result<()> { fn write<M: Into<Message>>(&self, msg: M) -> Result<()> {
self.conn.send(ServerState::sanitize(&msg.into().to_string()), self.config.encoding()) self.conn.send(
ServerState::sanitize(&msg.into().to_string()),
self.config.encoding(),
)
} }
#[cfg(not(feature = "encode"))] #[cfg(not(feature = "encode"))]
fn write<M: Into<Message>>(&self, msg: M) -> Result<()> { fn write<M: Into<Message>>(&self, msg: M) -> Result<()> {
self.conn.send(ServerState::sanitize(&msg.into().to_string())) self.conn.send(
ServerState::sanitize(&msg.into().to_string()),
)
} }
} }
@ -178,7 +190,7 @@ impl Clone for IrcServer {
fn clone(&self) -> IrcServer { fn clone(&self) -> IrcServer {
IrcServer { IrcServer {
state: self.state.clone(), state: self.state.clone(),
reconnect_count: self.reconnect_count.clone() reconnect_count: self.reconnect_count.clone(),
} }
} }
} }
@ -188,7 +200,10 @@ impl<'a> Server for ServerState {
&self.config &self.config
} }
fn send<M: Into<Message>>(&self, msg: M) -> Result<()> where Self: Sized { fn send<M: Into<Message>>(&self, msg: M) -> Result<()>
where
Self: Sized,
{
self.send_impl(msg.into()); self.send_impl(msg.into());
Ok(()) Ok(())
} }
@ -199,7 +214,14 @@ impl<'a> Server for ServerState {
#[cfg(not(feature = "nochanlists"))] #[cfg(not(feature = "nochanlists"))]
fn list_channels(&self) -> Option<Vec<String>> { fn list_channels(&self) -> Option<Vec<String>> {
Some(self.chanlists.lock().unwrap().keys().map(|k| k.to_owned()).collect()) Some(
self.chanlists
.lock()
.unwrap()
.keys()
.map(|k| k.to_owned())
.collect(),
)
} }
#[cfg(feature = "nochanlists")] #[cfg(feature = "nochanlists")]
@ -209,7 +231,11 @@ impl<'a> Server for ServerState {
#[cfg(not(feature = "nochanlists"))] #[cfg(not(feature = "nochanlists"))]
fn list_users(&self, chan: &str) -> Option<Vec<User>> { fn list_users(&self, chan: &str) -> Option<Vec<User>> {
self.chanlists.lock().unwrap().get(&chan.to_owned()).cloned() self.chanlists
.lock()
.unwrap()
.get(&chan.to_owned())
.cloned()
} }
#[cfg(feature = "nochanlists")] #[cfg(feature = "nochanlists")]
@ -223,7 +249,10 @@ impl Server for IrcServer {
&self.state.config &self.state.config
} }
fn send<M: Into<Message>>(&self, msg: M) -> Result<()> where Self: Sized { fn send<M: Into<Message>>(&self, msg: M) -> Result<()>
where
Self: Sized,
{
let msg = msg.into(); let msg = msg.into();
try!(self.handle_sent_message(&msg)); try!(self.handle_sent_message(&msg));
self.state.send(msg) self.state.send(msg)
@ -235,7 +264,15 @@ impl Server for IrcServer {
#[cfg(not(feature = "nochanlists"))] #[cfg(not(feature = "nochanlists"))]
fn list_channels(&self) -> Option<Vec<String>> { fn list_channels(&self) -> Option<Vec<String>> {
Some(self.state.chanlists.lock().unwrap().keys().map(|k| k.to_owned()).collect()) Some(
self.state
.chanlists
.lock()
.unwrap()
.keys()
.map(|k| k.to_owned())
.collect(),
)
} }
#[cfg(feature = "nochanlists")] #[cfg(feature = "nochanlists")]
@ -245,7 +282,12 @@ impl Server for IrcServer {
#[cfg(not(feature = "nochanlists"))] #[cfg(not(feature = "nochanlists"))]
fn list_users(&self, chan: &str) -> Option<Vec<User>> { fn list_users(&self, chan: &str) -> Option<Vec<User>> {
self.state.chanlists.lock().unwrap().get(&chan.to_owned()).cloned() self.state
.chanlists
.lock()
.unwrap()
.get(&chan.to_owned())
.cloned()
} }
#[cfg(feature = "nochanlists")] #[cfg(feature = "nochanlists")]
@ -257,7 +299,9 @@ impl Server for IrcServer {
impl IrcServer { impl IrcServer {
/// Creates an IRC server from the specified configuration, and any arbitrary sync connection. /// Creates an IRC server from the specified configuration, and any arbitrary sync connection.
pub fn from_connection<C>(config: Config, conn: C) -> IrcServer pub fn from_connection<C>(config: Config, conn: C) -> IrcServer
where C: Connection + Send + Sync + 'static { where
C: Connection + Send + Sync + 'static,
{
let state = Arc::new(ServerState::new(conn, config)); let state = Arc::new(ServerState::new(conn, config));
let ping_time = (state.config.ping_time() as i64) * 1000; let ping_time = (state.config.ping_time() as i64) * 1000;
let ping_timeout = (state.config.ping_timeout() as i64) * 1000; let ping_timeout = (state.config.ping_timeout() as i64) * 1000;
@ -277,7 +321,8 @@ impl IrcServer {
} }
}; };
let now_timespec = now().to_timespec(); let now_timespec = now().to_timespec();
let sleep_dur_ping_time = ping_time - (now_timespec - ping_idle_timespec).num_milliseconds(); let sleep_dur_ping_time = ping_time -
(now_timespec - ping_idle_timespec).num_milliseconds();
let sleep_dur_ping_timeout = if let Some(time) = strong.last_ping_data() { let sleep_dur_ping_timeout = if let Some(time) = strong.last_ping_data() {
ping_timeout - (now_timespec - time).num_milliseconds() ping_timeout - (now_timespec - time).num_milliseconds()
} else { } else {
@ -312,7 +357,10 @@ impl IrcServer {
} }
} }
}); });
IrcServer { state: state, reconnect_count: Cell::new(0) } IrcServer {
state: state,
reconnect_count: Cell::new(0),
}
} }
/// Gets a reference to the IRC server's connection. /// Gets a reference to the IRC server's connection.
@ -339,7 +387,7 @@ impl IrcServer {
let index = self.state.alt_nick_index.read().unwrap(); let index = self.state.alt_nick_index.read().unwrap();
match *index { match *index {
0 => self.config().nickname(), 0 => self.config().nickname(),
i => alt_nicks[i - 1] i => alt_nicks[i - 1],
} }
} }
@ -353,8 +401,8 @@ impl IrcServer {
match msg.command { match msg.command {
PART(ref chan, _) => { PART(ref chan, _) => {
let _ = self.state.chanlists.lock().unwrap().remove(chan); let _ = self.state.chanlists.lock().unwrap().remove(chan);
}, }
_ => () _ => (),
} }
Ok(()) Ok(())
} }
@ -363,30 +411,35 @@ impl IrcServer {
fn handle_message(&self, msg: &Message) -> Result<()> { fn handle_message(&self, msg: &Message) -> Result<()> {
match msg.command { match msg.command {
PING(ref data, _) => try!(self.send_pong(data)), PING(ref data, _) => try!(self.send_pong(data)),
PONG(ref pingdata, None) | PONG(_, Some(ref pingdata)) => self.state.check_pong(pingdata), PONG(ref pingdata, None) |
PONG(_, Some(ref pingdata)) => self.state.check_pong(pingdata),
JOIN(ref chan, _, _) => self.handle_join(msg.source_nickname().unwrap_or(""), chan), JOIN(ref chan, _, _) => self.handle_join(msg.source_nickname().unwrap_or(""), chan),
PART(ref chan, _) => self.handle_part(msg.source_nickname().unwrap_or(""), chan), PART(ref chan, _) => self.handle_part(msg.source_nickname().unwrap_or(""), chan),
QUIT(_) => self.handle_quit(msg.source_nickname().unwrap_or("")), QUIT(_) => self.handle_quit(msg.source_nickname().unwrap_or("")),
NICK(ref new_nick) => self.handle_nick_change(msg.source_nickname().unwrap_or(""), new_nick), NICK(ref new_nick) => {
self.handle_nick_change(msg.source_nickname().unwrap_or(""), new_nick)
}
MODE(ref chan, ref mode, Some(ref user)) => self.handle_mode(chan, mode, user), MODE(ref chan, ref mode, Some(ref user)) => self.handle_mode(chan, mode, user),
PRIVMSG(ref target, ref body) => if body.starts_with('\u{001}') { PRIVMSG(ref target, ref body) => {
let tokens: Vec<_> = { if body.starts_with('\u{001}') {
let end = if body.ends_with('\u{001}') { let tokens: Vec<_> = {
body.len() - 1 let end = if body.ends_with('\u{001}') {
} else { body.len() - 1
body.len() } else {
body.len()
};
body[1..end].split(' ').collect()
}; };
body[1..end].split(' ').collect() if target.starts_with('#') {
}; try!(self.handle_ctcp(target, tokens))
if target.starts_with('#') { } else if let Some(user) = msg.source_nickname() {
try!(self.handle_ctcp(target, tokens)) try!(self.handle_ctcp(user, tokens))
} else if let Some(user) = msg.source_nickname() { }
try!(self.handle_ctcp(user, tokens))
} }
}, }
Command::Response(Response::RPL_NAMREPLY, ref args, ref suffix) => { Command::Response(Response::RPL_NAMREPLY, ref args, ref suffix) => {
self.handle_namreply(args, suffix) self.handle_namreply(args, suffix)
}, }
Command::Response(Response::RPL_ENDOFMOTD, _, _) | Command::Response(Response::RPL_ENDOFMOTD, _, _) |
Command::Response(Response::ERR_NOMOTD, _, _) => { Command::Response(Response::ERR_NOMOTD, _, _) => {
try!(self.send_nick_password()); try!(self.send_nick_password());
@ -396,14 +449,17 @@ impl IrcServer {
for chan in &config_chans { for chan in &config_chans {
match self.config().channel_key(chan) { match self.config().channel_key(chan) {
Some(key) => try!(self.send_join_with_keys(chan, key)), Some(key) => try!(self.send_join_with_keys(chan, key)),
None => try!(self.send_join(chan)) None => try!(self.send_join(chan)),
} }
} }
let joined_chans = self.state.chanlists.lock().unwrap(); let joined_chans = self.state.chanlists.lock().unwrap();
for chan in joined_chans.keys().filter(|x| !config_chans.contains(&x.as_str())) { for chan in joined_chans.keys().filter(
|x| !config_chans.contains(&x.as_str()),
)
{
try!(self.send_join(chan)) try!(self.send_join(chan))
} }
}, }
Command::Response(Response::ERR_NICKNAMEINUSE, _, _) | Command::Response(Response::ERR_NICKNAMEINUSE, _, _) |
Command::Response(Response::ERR_ERRONEOUSNICKNAME, _, _) => { Command::Response(Response::ERR_ERRONEOUSNICKNAME, _, _) => {
let alt_nicks = self.config().alternate_nicknames(); let alt_nicks = self.config().alternate_nicknames();
@ -414,8 +470,8 @@ impl IrcServer {
try!(self.send(NICK(alt_nicks[*index].to_owned()))); try!(self.send(NICK(alt_nicks[*index].to_owned())));
*index += 1; *index += 1;
} }
}, }
_ => () _ => (),
} }
Ok(()) Ok(())
} }
@ -428,14 +484,18 @@ impl IrcServer {
if self.config().should_ghost() && *index != 0 { if self.config().should_ghost() && *index != 0 {
for seq in &self.config().ghost_sequence() { for seq in &self.config().ghost_sequence() {
try!(self.send(NICKSERV(format!( try!(self.send(NICKSERV(format!(
"{} {} {}", seq, self.config().nickname(), "{} {} {}",
seq,
self.config().nickname(),
self.config().nick_password() self.config().nick_password()
)))); ))));
} }
*index = 0; *index = 0;
try!(self.send(NICK(self.config().nickname().to_owned()))) try!(self.send(NICK(self.config().nickname().to_owned())))
} }
self.send(NICKSERV(format!("IDENTIFY {}", self.config().nick_password()))) self.send(NICKSERV(
format!("IDENTIFY {}", self.config().nick_password()),
))
} }
} }
@ -478,7 +538,9 @@ impl IrcServer {
#[cfg(not(feature = "nochanlists"))] #[cfg(not(feature = "nochanlists"))]
fn handle_quit(&self, src: &str) { fn handle_quit(&self, src: &str) {
if src.is_empty() { return; } if src.is_empty() {
return;
}
let mut chanlists = self.chanlists().lock().unwrap(); let mut chanlists = self.chanlists().lock().unwrap();
for channel in chanlists.clone().keys() { for channel in chanlists.clone().keys() {
if let Some(vec) = chanlists.get_mut(&channel.to_owned()) { if let Some(vec) = chanlists.get_mut(&channel.to_owned()) {
@ -494,7 +556,9 @@ impl IrcServer {
#[cfg(not(feature = "nochanlists"))] #[cfg(not(feature = "nochanlists"))]
fn handle_nick_change(&self, old_nick: &str, new_nick: &str) { fn handle_nick_change(&self, old_nick: &str, new_nick: &str) {
if old_nick.is_empty() || new_nick.is_empty() { return; } if old_nick.is_empty() || new_nick.is_empty() {
return;
}
let mut chanlists = self.chanlists().lock().unwrap(); let mut chanlists = self.chanlists().lock().unwrap();
for channel in chanlists.clone().keys() { for channel in chanlists.clone().keys() {
if let Some(vec) = chanlists.get_mut(&channel.to_owned()) { if let Some(vec) = chanlists.get_mut(&channel.to_owned()) {
@ -528,8 +592,10 @@ impl IrcServer {
let chan = &args[2]; let chan = &args[2];
for user in users.split(' ') { for user in users.split(' ') {
let mut chanlists = self.state.chanlists.lock().unwrap(); let mut chanlists = self.state.chanlists.lock().unwrap();
chanlists.entry(chan.clone()).or_insert_with(Vec::new) chanlists
.push(User::new(user)) .entry(chan.clone())
.or_insert_with(Vec::new)
.push(User::new(user))
} }
} }
} }
@ -538,28 +604,38 @@ impl IrcServer {
/// Handles CTCP requests if the CTCP feature is enabled. /// Handles CTCP requests if the CTCP feature is enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn handle_ctcp(&self, resp: &str, tokens: Vec<&str>) -> Result<()> { fn handle_ctcp(&self, resp: &str, tokens: Vec<&str>) -> Result<()> {
if tokens.is_empty() { return Ok(()) } if tokens.is_empty() {
return Ok(());
}
match tokens[0] { match tokens[0] {
"FINGER" => self.send_ctcp_internal(resp, &format!( "FINGER" => {
"FINGER :{} ({})", self.config().real_name(), self.config().username() self.send_ctcp_internal(
)), resp,
&format!(
"FINGER :{} ({})",
self.config().real_name(),
self.config().username()
),
)
}
"VERSION" => { "VERSION" => {
self.send_ctcp_internal(resp, &format!("VERSION {}", self.config().version())) self.send_ctcp_internal(resp, &format!("VERSION {}", self.config().version()))
}, }
"SOURCE" => { "SOURCE" => {
try!(self.send_ctcp_internal(resp, &format!("SOURCE {}", self.config().source()))); try!(self.send_ctcp_internal(
resp,
&format!("SOURCE {}", self.config().source()),
));
self.send_ctcp_internal(resp, "SOURCE") self.send_ctcp_internal(resp, "SOURCE")
}, }
"PING" if tokens.len() > 1 => { "PING" if tokens.len() > 1 => {
self.send_ctcp_internal(resp, &format!("PING {}", tokens[1])) self.send_ctcp_internal(resp, &format!("PING {}", tokens[1]))
}, }
"TIME" => self.send_ctcp_internal(resp, &format!( "TIME" => self.send_ctcp_internal(resp, &format!("TIME :{}", now().rfc822z())),
"TIME :{}", now().rfc822z() "USERINFO" => {
)), self.send_ctcp_internal(resp, &format!("USERINFO :{}", self.config().user_info()))
"USERINFO" => self.send_ctcp_internal(resp, &format!( }
"USERINFO :{}", self.config().user_info() _ => Ok(()),
)),
_ => Ok(())
} }
} }
@ -578,7 +654,7 @@ impl IrcServer {
/// An `Iterator` over an `IrcServer`'s incoming `Messages`. /// An `Iterator` over an `IrcServer`'s incoming `Messages`.
pub struct ServerIterator<'a> { pub struct ServerIterator<'a> {
server: &'a IrcServer server: &'a IrcServer,
} }
impl<'a> ServerIterator<'a> { impl<'a> ServerIterator<'a> {
@ -605,19 +681,24 @@ impl<'a> Iterator for ServerIterator<'a> {
fn next(&mut self) -> Option<Result<Message>> { fn next(&mut self) -> Option<Result<Message>> {
loop { loop {
match self.get_next_line() { match self.get_next_line() {
Ok(msg) => match msg.parse() { Ok(msg) => {
Ok(res) => { match msg.parse() {
match self.server.handle_message(&res) { Ok(res) => {
Ok(()) => (), match self.server.handle_message(&res) {
Err(err) => return Some(Err(err)) Ok(()) => (),
Err(err) => return Some(Err(err)),
}
self.server.state.action_taken();
return Some(Ok(res));
} }
self.server.state.action_taken(); Err(_) => {
return Some(Ok(res)) return Some(Err(Error::new(
}, ErrorKind::InvalidInput,
Err(_) => return Some(Err(Error::new(ErrorKind::InvalidInput, &format!("Failed to parse message. (Message: {})", msg)[..],
&format!("Failed to parse message. (Message: {})", msg)[..] )))
))) }
}, }
}
Err(ref err) if err.description() == "EOF" => return None, Err(ref err) if err.description() == "EOF" => return None,
Err(_) => { Err(_) => {
let _ = self.server.reconnect().and_then(|_| self.server.identify()); let _ = self.server.reconnect().and_then(|_| self.server.identify());
@ -634,7 +715,8 @@ mod test {
use std::default::Default; use std::default::Default;
use client::conn::MockConnection; use client::conn::MockConnection;
use client::data::Config; use client::data::Config;
#[cfg(not(feature = "nochanlists"))] use client::data::User; #[cfg(not(feature = "nochanlists"))]
use client::data::User;
use client::data::command::Command::{PART, PRIVMSG}; use client::data::command::Command::{PART, PRIVMSG};
pub fn test_config() -> Config { pub fn test_config() -> Config {
@ -645,7 +727,7 @@ mod test {
server: Some(format!("irc.test.net")), server: Some(format!("irc.test.net")),
channels: Some(vec![format!("#test"), format!("#test2")]), channels: Some(vec![format!("#test"), format!("#test2")]),
user_info: Some(format!("Testing.")), user_info: Some(format!("Testing.")),
.. Default::default() ..Default::default()
} }
} }
@ -678,98 +760,129 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], assert_eq!(
"PONG :irc.test.net\r\nJOIN #test\r\nJOIN #test2\r\n"); &get_server_value(server)[..],
"PONG :irc.test.net\r\nJOIN #test\r\nJOIN #test2\r\n"
);
} }
#[test] #[test]
fn handle_end_motd_with_nick_password() { fn handle_end_motd_with_nick_password() {
let value = ":irc.test.net 376 test :End of /MOTD command.\r\n"; let value = ":irc.test.net 376 test :End of /MOTD command.\r\n";
let server = IrcServer::from_connection(Config { let server = IrcServer::from_connection(
nick_password: Some(format!("password")), Config {
channels: Some(vec![format!("#test"), format!("#test2")]), nick_password: Some(format!("password")),
.. Default::default() channels: Some(vec![format!("#test"), format!("#test2")]),
}, MockConnection::new(value)); ..Default::default()
},
MockConnection::new(value),
);
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NICKSERV IDENTIFY password\r\nJOIN #test\r\n\ assert_eq!(
JOIN #test2\r\n"); &get_server_value(server)[..],
"NICKSERV IDENTIFY password\r\nJOIN #test\r\n\
JOIN #test2\r\n"
);
} }
#[test] #[test]
fn handle_end_motd_with_chan_keys() { fn handle_end_motd_with_chan_keys() {
let value = ":irc.test.net 376 test :End of /MOTD command\r\n"; let value = ":irc.test.net 376 test :End of /MOTD command\r\n";
let server = IrcServer::from_connection(Config { let server = IrcServer::from_connection(
nickname: Some(format!("test")), Config {
channels: Some(vec![format!("#test"), format!("#test2")]), nickname: Some(format!("test")),
channel_keys: { channels: Some(vec![format!("#test"), format!("#test2")]),
let mut map = HashMap::new(); channel_keys: {
map.insert(format!("#test2"), format!("password")); let mut map = HashMap::new();
Some(map) map.insert(format!("#test2"), format!("password"));
Some(map)
},
..Default::default()
}, },
.. Default::default() MockConnection::new(value),
}, MockConnection::new(value)); );
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "JOIN #test\r\nJOIN #test2 password\r\n"); assert_eq!(
&get_server_value(server)[..],
"JOIN #test\r\nJOIN #test2 password\r\n"
);
} }
#[test] #[test]
fn handle_end_motd_with_ghost() { fn handle_end_motd_with_ghost() {
let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\
:irc.test.net 376 test2 :End of /MOTD command.\r\n"; :irc.test.net 376 test2 :End of /MOTD command.\r\n";
let server = IrcServer::from_connection(Config { let server = IrcServer::from_connection(
nickname: Some(format!("test")), Config {
alt_nicks: Some(vec![format!("test2")]), nickname: Some(format!("test")),
nick_password: Some(format!("password")), alt_nicks: Some(vec![format!("test2")]),
channels: Some(vec![format!("#test"), format!("#test2")]), nick_password: Some(format!("password")),
should_ghost: Some(true), channels: Some(vec![format!("#test"), format!("#test2")]),
.. Default::default() should_ghost: Some(true),
}, MockConnection::new(value)); ..Default::default()
},
MockConnection::new(value),
);
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NICK :test2\r\nNICKSERV GHOST test password\r\n\ assert_eq!(
NICK :test\r\nNICKSERV IDENTIFY password\r\nJOIN #test\r\nJOIN #test2\r\n"); &get_server_value(server)[..],
"NICK :test2\r\nNICKSERV GHOST test password\r\n\
NICK :test\r\nNICKSERV IDENTIFY password\r\nJOIN #test\r\nJOIN #test2\r\n"
);
} }
#[test] #[test]
fn handle_end_motd_with_ghost_seq() { fn handle_end_motd_with_ghost_seq() {
let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\
:irc.test.net 376 test2 :End of /MOTD command.\r\n"; :irc.test.net 376 test2 :End of /MOTD command.\r\n";
let server = IrcServer::from_connection(Config { let server = IrcServer::from_connection(
nickname: Some(format!("test")), Config {
alt_nicks: Some(vec![format!("test2")]), nickname: Some(format!("test")),
nick_password: Some(format!("password")), alt_nicks: Some(vec![format!("test2")]),
channels: Some(vec![format!("#test"), format!("#test2")]), nick_password: Some(format!("password")),
should_ghost: Some(true), channels: Some(vec![format!("#test"), format!("#test2")]),
ghost_sequence: Some(vec![format!("RECOVER"), format!("RELEASE")]), should_ghost: Some(true),
.. Default::default() ghost_sequence: Some(vec![format!("RECOVER"), format!("RELEASE")]),
}, MockConnection::new(value)); ..Default::default()
},
MockConnection::new(value),
);
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NICK :test2\r\nNICKSERV RECOVER test password\ assert_eq!(
&get_server_value(server)[..],
"NICK :test2\r\nNICKSERV RECOVER test password\
\r\nNICKSERV RELEASE test password\r\nNICK :test\r\nNICKSERV IDENTIFY password\ \r\nNICKSERV RELEASE test password\r\nNICK :test\r\nNICKSERV IDENTIFY password\
\r\nJOIN #test\r\nJOIN #test2\r\n"); \r\nJOIN #test\r\nJOIN #test2\r\n"
);
} }
#[test] #[test]
fn handle_end_motd_with_umodes() { fn handle_end_motd_with_umodes() {
let value = ":irc.test.net 376 test :End of /MOTD command.\r\n"; let value = ":irc.test.net 376 test :End of /MOTD command.\r\n";
let server = IrcServer::from_connection(Config { let server = IrcServer::from_connection(
nickname: Some(format!("test")), Config {
umodes: Some(format!("+B")), nickname: Some(format!("test")),
channels: Some(vec![format!("#test"), format!("#test2")]), umodes: Some(format!("+B")),
.. Default::default() channels: Some(vec![format!("#test"), format!("#test2")]),
}, MockConnection::new(value)); ..Default::default()
},
MockConnection::new(value),
);
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], assert_eq!(
"MODE test +B\r\nJOIN #test\r\nJOIN #test2\r\n"); &get_server_value(server)[..],
"MODE test +B\r\nJOIN #test\r\nJOIN #test2\r\n"
);
} }
#[test] #[test]
@ -796,14 +909,25 @@ mod test {
#[test] #[test]
fn send() { fn send() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
assert!(server.send(PRIVMSG(format!("#test"), format!("Hi there!"))).is_ok()); assert!(
assert_eq!(&get_server_value(server)[..], "PRIVMSG #test :Hi there!\r\n"); server
.send(PRIVMSG(format!("#test"), format!("Hi there!")))
.is_ok()
);
assert_eq!(
&get_server_value(server)[..],
"PRIVMSG #test :Hi there!\r\n"
);
} }
#[test] #[test]
fn send_no_newline_injection() { fn send_no_newline_injection() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
assert!(server.send(PRIVMSG(format!("#test"), format!("Hi there!\nJOIN #bad"))).is_ok()); assert!(
server
.send(PRIVMSG(format!("#test"), format!("Hi there!\nJOIN #bad")))
.is_ok()
);
assert_eq!(&get_server_value(server)[..], "PRIVMSG #test :Hi there!\n"); assert_eq!(&get_server_value(server)[..], "PRIVMSG #test :Hi there!\n");
} }
@ -838,8 +962,10 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(server.list_users("#test").unwrap(), assert_eq!(
vec![User::new("test"), User::new("~owner"), User::new("&admin")]) server.list_users("#test").unwrap(),
vec![User::new("test"), User::new("~owner"), User::new("&admin")]
)
} }
#[test] #[test]
@ -851,8 +977,15 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(server.list_users("#test").unwrap(), assert_eq!(
vec![User::new("test"), User::new("~owner"), User::new("&admin"), User::new("test2")]) server.list_users("#test").unwrap(),
vec![
User::new("test"),
User::new("~owner"),
User::new("&admin"),
User::new("test2"),
]
)
} }
#[test] #[test]
@ -864,8 +997,10 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(server.list_users("#test").unwrap(), assert_eq!(
vec![User::new("test"), User::new("&admin")]) server.list_users("#test").unwrap(),
vec![User::new("test"), User::new("&admin")]
)
} }
#[test] #[test]
@ -877,12 +1012,16 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(server.list_users("#test").unwrap(), assert_eq!(
vec![User::new("@test"), User::new("~owner"), User::new("&admin")]); server.list_users("#test").unwrap(),
vec![User::new("@test"), User::new("~owner"), User::new("&admin")]
);
let mut exp = User::new("@test"); let mut exp = User::new("@test");
exp.update_access_level("+v"); exp.update_access_level("+v");
assert_eq!(server.list_users("#test").unwrap()[0].highest_access_level(), assert_eq!(
exp.highest_access_level()); server.list_users("#test").unwrap()[0].highest_access_level(),
exp.highest_access_level()
);
// The following tests if the maintained user contains the same entries as what is expected // The following tests if the maintained user contains the same entries as what is expected
// but ignores the ordering of these entries. // but ignores the ordering of these entries.
let mut levels = server.list_users("#test").unwrap()[0].access_levels(); let mut levels = server.list_users("#test").unwrap()[0].access_levels();
@ -909,8 +1048,11 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NOTICE test :\u{001}FINGER :test (test)\u{001}\ assert_eq!(
\r\n"); &get_server_value(server)[..],
"NOTICE test :\u{001}FINGER :test (test)\u{001}\
\r\n"
);
} }
#[test] #[test]
@ -921,8 +1063,11 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NOTICE test :\u{001}VERSION irc:git:Rust\u{001}\ assert_eq!(
\r\n"); &get_server_value(server)[..],
"NOTICE test :\u{001}VERSION irc:git:Rust\u{001}\
\r\n"
);
} }
#[test] #[test]
@ -933,9 +1078,11 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], assert_eq!(
"NOTICE test :\u{001}SOURCE https://github.com/aatxe/irc\u{001}\r\n\ &get_server_value(server)[..],
NOTICE test :\u{001}SOURCE\u{001}\r\n"); "NOTICE test :\u{001}SOURCE https://github.com/aatxe/irc\u{001}\r\n\
NOTICE test :\u{001}SOURCE\u{001}\r\n"
);
} }
#[test] #[test]
@ -946,7 +1093,10 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NOTICE test :\u{001}PING test\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"NOTICE test :\u{001}PING test\u{001}\r\n"
);
} }
#[test] #[test]
@ -970,8 +1120,11 @@ mod test {
for message in server.iter() { for message in server.iter() {
println!("{:?}", message); println!("{:?}", message);
} }
assert_eq!(&get_server_value(server)[..], "NOTICE test :\u{001}USERINFO :Testing.\u{001}\ assert_eq!(
\r\n"); &get_server_value(server)[..],
"NOTICE test :\u{001}USERINFO :Testing.\u{001}\
\r\n"
);
} }
#[test] #[test]

View file

@ -5,89 +5,144 @@ use client::data::{Capability, NegotiationVersion};
use client::data::Command::{AUTHENTICATE, CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NOTICE}; use client::data::Command::{AUTHENTICATE, CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NOTICE};
use client::data::Command::{OPER, PART, PASS, PONG, PRIVMSG, QUIT, SAMODE, SANICK, TOPIC, USER}; use client::data::Command::{OPER, PART, PASS, PONG, PRIVMSG, QUIT, SAMODE, SANICK, TOPIC, USER};
use client::data::command::CapSubCommand::{END, LS, REQ}; use client::data::command::CapSubCommand::{END, LS, REQ};
#[cfg(feature = "ctcp")] use time::get_time; #[cfg(feature = "ctcp")]
use time::get_time;
use client::server::Server; use client::server::Server;
/// Extensions for Server capabilities that make it easier to work directly with the protocol. /// Extensions for Server capabilities that make it easier to work directly with the protocol.
pub trait ServerExt: Server { pub trait ServerExt: Server {
/// Sends a request for a list of server capabilities for a specific IRCv3 version. /// Sends a request for a list of server capabilities for a specific IRCv3 version.
fn send_cap_ls(&self, version: NegotiationVersion) -> Result<()> where Self: Sized { fn send_cap_ls(&self, version: NegotiationVersion) -> Result<()>
self.send(CAP(None, LS, match version { where
NegotiationVersion::V301 => None, Self: Sized,
NegotiationVersion::V302 => Some("302".to_owned()), {
}, None)) self.send(CAP(
None,
LS,
match version {
NegotiationVersion::V301 => None,
NegotiationVersion::V302 => Some("302".to_owned()),
},
None,
))
} }
/// Sends an IRCv3 capabilities request for the specified extensions. /// Sends an IRCv3 capabilities request for the specified extensions.
fn send_cap_req(&self, extensions: &[Capability]) -> Result<()> where Self: Sized { fn send_cap_req(&self, extensions: &[Capability]) -> Result<()>
let append = |mut s: String, c| { s.push_str(c); s.push(' '); s }; where
let mut exts = extensions.iter().map(|c| c.as_ref()).fold(String::new(), append); Self: Sized,
{
let append = |mut s: String, c| {
s.push_str(c);
s.push(' ');
s
};
let mut exts = extensions.iter().map(|c| c.as_ref()).fold(
String::new(),
append,
);
let len = exts.len() - 1; let len = exts.len() - 1;
exts.truncate(len); exts.truncate(len);
self.send(CAP(None, REQ, None, Some(exts))) self.send(CAP(None, REQ, None, Some(exts)))
} }
/// Sends a CAP END, NICK and USER to identify. /// Sends a CAP END, NICK and USER to identify.
fn identify(&self) -> Result<()> where Self: Sized { fn identify(&self) -> Result<()>
where
Self: Sized,
{
// Send a CAP END to signify that we're IRCv3-compliant (and to end negotiations!). // Send a CAP END to signify that we're IRCv3-compliant (and to end negotiations!).
try!(self.send(CAP(None, END, None, None))); try!(self.send(CAP(None, END, None, None)));
if self.config().password() != "" { if self.config().password() != "" {
try!(self.send(PASS(self.config().password().to_owned()))); try!(self.send(PASS(self.config().password().to_owned())));
} }
try!(self.send(NICK(self.config().nickname().to_owned()))); try!(self.send(NICK(self.config().nickname().to_owned())));
try!(self.send(USER(self.config().username().to_owned(), "0".to_owned(), try!(self.send(USER(
self.config().real_name().to_owned()))); self.config().username().to_owned(),
"0".to_owned(),
self.config().real_name().to_owned(),
)));
Ok(()) Ok(())
} }
/// Sends a SASL AUTHENTICATE message with the specified data. /// Sends a SASL AUTHENTICATE message with the specified data.
fn send_sasl(&self, data: &str) -> Result<()> where Self: Sized { fn send_sasl(&self, data: &str) -> Result<()>
where
Self: Sized,
{
self.send(AUTHENTICATE(data.to_owned())) self.send(AUTHENTICATE(data.to_owned()))
} }
/// Sends a SASL AUTHENTICATE request to use the PLAIN mechanism. /// Sends a SASL AUTHENTICATE request to use the PLAIN mechanism.
fn send_sasl_plain(&self) -> Result<()> where Self: Sized { fn send_sasl_plain(&self) -> Result<()>
where
Self: Sized,
{
self.send_sasl("PLAIN") self.send_sasl("PLAIN")
} }
/// Sends a SASL AUTHENTICATE request to use the EXTERNAL mechanism. /// Sends a SASL AUTHENTICATE request to use the EXTERNAL mechanism.
fn send_sasl_external(&self) -> Result<()> where Self: Sized { fn send_sasl_external(&self) -> Result<()>
where
Self: Sized,
{
self.send_sasl("EXTERNAL") self.send_sasl("EXTERNAL")
} }
/// Sends a SASL AUTHENTICATE request to abort authentication. /// Sends a SASL AUTHENTICATE request to abort authentication.
fn send_sasl_abort(&self) -> Result<()> where Self: Sized { fn send_sasl_abort(&self) -> Result<()>
where
Self: Sized,
{
self.send_sasl("*") self.send_sasl("*")
} }
/// Sends a PONG with the specified message. /// Sends a PONG with the specified message.
fn send_pong(&self, msg: &str) -> Result<()> where Self: Sized { fn send_pong(&self, msg: &str) -> Result<()>
where
Self: Sized,
{
self.send(PONG(msg.to_owned(), None)) self.send(PONG(msg.to_owned(), None))
} }
/// Joins the specified channel or chanlist. /// Joins the specified channel or chanlist.
fn send_join(&self, chanlist: &str) -> Result<()> where Self: Sized { fn send_join(&self, chanlist: &str) -> Result<()>
where
Self: Sized,
{
self.send(JOIN(chanlist.to_owned(), None, None)) self.send(JOIN(chanlist.to_owned(), None, None))
} }
/// Joins the specified channel or chanlist using the specified key or keylist. /// Joins the specified channel or chanlist using the specified key or keylist.
fn send_join_with_keys(&self, chanlist: &str, keylist: &str) -> Result<()> where Self: Sized { fn send_join_with_keys(&self, chanlist: &str, keylist: &str) -> Result<()>
where
Self: Sized,
{
self.send(JOIN(chanlist.to_owned(), Some(keylist.to_owned()), None)) self.send(JOIN(chanlist.to_owned(), Some(keylist.to_owned()), None))
} }
/// Parts the specified channel or chanlist. /// Parts the specified channel or chanlist.
fn send_part(&self, chanlist: &str) -> Result<()> where Self: Sized { fn send_part(&self, chanlist: &str) -> Result<()>
where
Self: Sized,
{
self.send(PART(chanlist.to_owned(), None)) self.send(PART(chanlist.to_owned(), None))
} }
/// Attempts to oper up using the specified username and password. /// Attempts to oper up using the specified username and password.
fn send_oper(&self, username: &str, password: &str) -> Result<()> where Self: Sized { fn send_oper(&self, username: &str, password: &str) -> Result<()>
where
Self: Sized,
{
self.send(OPER(username.to_owned(), password.to_owned())) self.send(OPER(username.to_owned(), password.to_owned()))
} }
/// Sends a message to the specified target. /// Sends a message to the specified target.
fn send_privmsg(&self, target: &str, message: &str) -> Result<()> where Self: Sized { fn send_privmsg(&self, target: &str, message: &str) -> Result<()>
where
Self: Sized,
{
for line in message.split("\r\n") { for line in message.split("\r\n") {
try!(self.send(PRIVMSG(target.to_owned(), line.to_owned()))) try!(self.send(PRIVMSG(target.to_owned(), line.to_owned())))
} }
@ -95,7 +150,10 @@ pub trait ServerExt: Server {
} }
/// Sends a notice to the specified target. /// Sends a notice to the specified target.
fn send_notice(&self, target: &str, message: &str) -> Result<()> where Self: Sized { fn send_notice(&self, target: &str, message: &str) -> Result<()>
where
Self: Sized,
{
for line in message.split("\r\n") { for line in message.split("\r\n") {
try!(self.send(NOTICE(target.to_owned(), line.to_owned()))) try!(self.send(NOTICE(target.to_owned(), line.to_owned())))
} }
@ -104,65 +162,101 @@ pub trait ServerExt: Server {
/// Sets the topic of a channel or requests the current one. /// Sets the topic of a channel or requests the current one.
/// If `topic` is an empty string, it won't be included in the message. /// If `topic` is an empty string, it won't be included in the message.
fn send_topic(&self, channel: &str, topic: &str) -> Result<()> where Self: Sized { fn send_topic(&self, channel: &str, topic: &str) -> Result<()>
self.send(TOPIC(channel.to_owned(), if topic.is_empty() { where
None Self: Sized,
} else { {
Some(topic.to_owned()) self.send(TOPIC(
})) channel.to_owned(),
if topic.is_empty() {
None
} else {
Some(topic.to_owned())
},
))
} }
/// Kills the target with the provided message. /// Kills the target with the provided message.
fn send_kill(&self, target: &str, message: &str) -> Result<()> where Self: Sized { fn send_kill(&self, target: &str, message: &str) -> Result<()>
where
Self: Sized,
{
self.send(KILL(target.to_owned(), message.to_owned())) self.send(KILL(target.to_owned(), message.to_owned()))
} }
/// Kicks the listed nicknames from the listed channels with a comment. /// Kicks the listed nicknames from the listed channels with a comment.
/// If `message` is an empty string, it won't be included in the message. /// If `message` is an empty string, it won't be included in the message.
fn send_kick(&self, chanlist: &str, nicklist: &str, message: &str) -> Result<()> fn send_kick(&self, chanlist: &str, nicklist: &str, message: &str) -> Result<()>
where Self: Sized { where
self.send(KICK(chanlist.to_owned(), nicklist.to_owned(), if message.is_empty() { Self: Sized,
None {
} else { self.send(KICK(
Some(message.to_owned()) chanlist.to_owned(),
})) nicklist.to_owned(),
if message.is_empty() {
None
} else {
Some(message.to_owned())
},
))
} }
/// Changes the mode of the target. /// Changes the mode of the target.
/// If `modeparmas` is an empty string, it won't be included in the message. /// If `modeparmas` is an empty string, it won't be included in the message.
fn send_mode(&self, target: &str, mode: &str, modeparams: &str) -> Result<()> fn send_mode(&self, target: &str, mode: &str, modeparams: &str) -> Result<()>
where Self: Sized { where
self.send(MODE(target.to_owned(), mode.to_owned(), if modeparams.is_empty() { Self: Sized,
None {
} else { self.send(MODE(
Some(modeparams.to_owned()) target.to_owned(),
})) mode.to_owned(),
if modeparams.is_empty() {
None
} else {
Some(modeparams.to_owned())
},
))
} }
/// Changes the mode of the target by force. /// Changes the mode of the target by force.
/// If `modeparams` is an empty string, it won't be included in the message. /// If `modeparams` is an empty string, it won't be included in the message.
fn send_samode(&self, target: &str, mode: &str, modeparams: &str) -> Result<()> fn send_samode(&self, target: &str, mode: &str, modeparams: &str) -> Result<()>
where Self: Sized { where
self.send(SAMODE(target.to_owned(), mode.to_owned(), if modeparams.is_empty() { Self: Sized,
None {
} else { self.send(SAMODE(
Some(modeparams.to_owned()) target.to_owned(),
})) mode.to_owned(),
if modeparams.is_empty() {
None
} else {
Some(modeparams.to_owned())
},
))
} }
/// Forces a user to change from the old nickname to the new nickname. /// Forces a user to change from the old nickname to the new nickname.
fn send_sanick(&self, old_nick: &str, new_nick: &str) -> Result<()> where Self: Sized { fn send_sanick(&self, old_nick: &str, new_nick: &str) -> Result<()>
where
Self: Sized,
{
self.send(SANICK(old_nick.to_owned(), new_nick.to_owned())) self.send(SANICK(old_nick.to_owned(), new_nick.to_owned()))
} }
/// Invites a user to the specified channel. /// Invites a user to the specified channel.
fn send_invite(&self, nick: &str, chan: &str) -> Result<()> where Self: Sized { fn send_invite(&self, nick: &str, chan: &str) -> Result<()>
where
Self: Sized,
{
self.send(INVITE(nick.to_owned(), chan.to_owned())) self.send(INVITE(nick.to_owned(), chan.to_owned()))
} }
/// Quits the server entirely with a message. /// Quits the server entirely with a message.
/// This defaults to `Powered by Rust.` if none is specified. /// This defaults to `Powered by Rust.` if none is specified.
fn send_quit(&self, msg: &str) -> Result<()> where Self: Sized { fn send_quit(&self, msg: &str) -> Result<()>
where
Self: Sized,
{
self.send(QUIT(Some(if msg.is_empty() { self.send(QUIT(Some(if msg.is_empty() {
"Powered by Rust.".to_owned() "Powered by Rust.".to_owned()
} else { } else {
@ -173,49 +267,70 @@ pub trait ServerExt: Server {
/// Sends a CTCP-escaped message to the specified target. /// Sends a CTCP-escaped message to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_ctcp(&self, target: &str, msg: &str) -> Result<()> where Self: Sized { fn send_ctcp(&self, target: &str, msg: &str) -> Result<()>
where
Self: Sized,
{
self.send_privmsg(target, &format!("\u{001}{}\u{001}", msg)[..]) self.send_privmsg(target, &format!("\u{001}{}\u{001}", msg)[..])
} }
/// Sends an action command to the specified target. /// Sends an action command to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_action(&self, target: &str, msg: &str) -> Result<()> where Self: Sized { fn send_action(&self, target: &str, msg: &str) -> Result<()>
where
Self: Sized,
{
self.send_ctcp(target, &format!("ACTION {}", msg)[..]) self.send_ctcp(target, &format!("ACTION {}", msg)[..])
} }
/// Sends a finger request to the specified target. /// Sends a finger request to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_finger(&self, target: &str) -> Result<()> where Self: Sized { fn send_finger(&self, target: &str) -> Result<()>
where
Self: Sized,
{
self.send_ctcp(target, "FINGER") self.send_ctcp(target, "FINGER")
} }
/// Sends a version request to the specified target. /// Sends a version request to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_version(&self, target: &str) -> Result<()> where Self: Sized { fn send_version(&self, target: &str) -> Result<()>
where
Self: Sized,
{
self.send_ctcp(target, "VERSION") self.send_ctcp(target, "VERSION")
} }
/// Sends a source request to the specified target. /// Sends a source request to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_source(&self, target: &str) -> Result<()> where Self: Sized { fn send_source(&self, target: &str) -> Result<()>
where
Self: Sized,
{
self.send_ctcp(target, "SOURCE") self.send_ctcp(target, "SOURCE")
} }
/// Sends a user info request to the specified target. /// Sends a user info request to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_user_info(&self, target: &str) -> Result<()> where Self: Sized { fn send_user_info(&self, target: &str) -> Result<()>
where
Self: Sized,
{
self.send_ctcp(target, "USERINFO") self.send_ctcp(target, "USERINFO")
} }
/// Sends a finger request to the specified target. /// Sends a finger request to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_ctcp_ping(&self, target: &str) -> Result<()> where Self: Sized { fn send_ctcp_ping(&self, target: &str) -> Result<()>
where
Self: Sized,
{
let time = get_time(); let time = get_time();
self.send_ctcp(target, &format!("PING {}.{}", time.sec, time.nsec)[..]) self.send_ctcp(target, &format!("PING {}.{}", time.sec, time.nsec)[..])
} }
@ -223,12 +338,19 @@ pub trait ServerExt: Server {
/// Sends a time request to the specified target. /// Sends a time request to the specified target.
/// This requires the CTCP feature to be enabled. /// This requires the CTCP feature to be enabled.
#[cfg(feature = "ctcp")] #[cfg(feature = "ctcp")]
fn send_time(&self, target: &str) -> Result<()> where Self: Sized { fn send_time(&self, target: &str) -> Result<()>
where
Self: Sized,
{
self.send_ctcp(target, "TIME") self.send_ctcp(target, "TIME")
} }
} }
impl<S> ServerExt for S where S: Server {} impl<S> ServerExt for S
where
S: Server,
{
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -243,20 +365,29 @@ mod test {
fn identify() { fn identify() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.identify().unwrap(); server.identify().unwrap();
assert_eq!(&get_server_value(server)[..], "CAP END\r\nNICK :test\r\n\ assert_eq!(
USER test 0 * :test\r\n"); &get_server_value(server)[..],
"CAP END\r\nNICK :test\r\n\
USER test 0 * :test\r\n"
);
} }
#[test] #[test]
fn identify_with_password() { fn identify_with_password() {
let server = IrcServer::from_connection(Config { let server = IrcServer::from_connection(
nickname: Some(format!("test")), Config {
password: Some(format!("password")), nickname: Some(format!("test")),
.. Default::default() password: Some(format!("password")),
}, MockConnection::empty()); ..Default::default()
},
MockConnection::empty(),
);
server.identify().unwrap(); server.identify().unwrap();
assert_eq!(&get_server_value(server)[..], "CAP END\r\nPASS :password\r\nNICK :test\r\n\ assert_eq!(
USER test 0 * :test\r\n"); &get_server_value(server)[..],
"CAP END\r\nPASS :password\r\nNICK :test\r\n\
USER test 0 * :test\r\n"
);
} }
#[test] #[test]
@ -270,7 +401,10 @@ mod test {
fn send_join() { fn send_join() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_join("#test,#test2,#test3").unwrap(); server.send_join("#test,#test2,#test3").unwrap();
assert_eq!(&get_server_value(server)[..], "JOIN #test,#test2,#test3\r\n"); assert_eq!(
&get_server_value(server)[..],
"JOIN #test,#test2,#test3\r\n"
);
} }
#[test] #[test]
@ -291,14 +425,20 @@ mod test {
fn send_privmsg() { fn send_privmsg() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_privmsg("#test", "Hi, everybody!").unwrap(); server.send_privmsg("#test", "Hi, everybody!").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG #test :Hi, everybody!\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG #test :Hi, everybody!\r\n"
);
} }
#[test] #[test]
fn send_notice() { fn send_notice() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_notice("#test", "Hi, everybody!").unwrap(); server.send_notice("#test", "Hi, everybody!").unwrap();
assert_eq!(&get_server_value(server)[..], "NOTICE #test :Hi, everybody!\r\n"); assert_eq!(
&get_server_value(server)[..],
"NOTICE #test :Hi, everybody!\r\n"
);
} }
#[test] #[test]
@ -312,14 +452,20 @@ mod test {
fn send_topic() { fn send_topic() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_topic("#test", "Testing stuff.").unwrap(); server.send_topic("#test", "Testing stuff.").unwrap();
assert_eq!(&get_server_value(server)[..], "TOPIC #test :Testing stuff.\r\n"); assert_eq!(
&get_server_value(server)[..],
"TOPIC #test :Testing stuff.\r\n"
);
} }
#[test] #[test]
fn send_kill() { fn send_kill() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_kill("test", "Testing kills.").unwrap(); server.send_kill("test", "Testing kills.").unwrap();
assert_eq!(&get_server_value(server)[..], "KILL test :Testing kills.\r\n"); assert_eq!(
&get_server_value(server)[..],
"KILL test :Testing kills.\r\n"
);
} }
#[test] #[test]
@ -333,7 +479,10 @@ mod test {
fn send_kick() { fn send_kick() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_kick("#test", "test", "Testing kicks.").unwrap(); server.send_kick("#test", "test", "Testing kicks.").unwrap();
assert_eq!(&get_server_value(server)[..], "KICK #test test :Testing kicks.\r\n"); assert_eq!(
&get_server_value(server)[..],
"KICK #test test :Testing kicks.\r\n"
);
} }
#[test] #[test]
@ -383,7 +532,10 @@ mod test {
fn send_ctcp() { fn send_ctcp() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_ctcp("test", "MESSAGE").unwrap(); server.send_ctcp("test", "MESSAGE").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}MESSAGE\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}MESSAGE\u{001}\r\n"
);
} }
#[test] #[test]
@ -391,7 +543,10 @@ mod test {
fn send_action() { fn send_action() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_action("test", "tests.").unwrap(); server.send_action("test", "tests.").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}ACTION tests.\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}ACTION tests.\u{001}\r\n"
);
} }
#[test] #[test]
@ -399,7 +554,10 @@ mod test {
fn send_finger() { fn send_finger() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_finger("test").unwrap(); server.send_finger("test").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}FINGER\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}FINGER\u{001}\r\n"
);
} }
#[test] #[test]
@ -407,7 +565,10 @@ mod test {
fn send_version() { fn send_version() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_version("test").unwrap(); server.send_version("test").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}VERSION\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}VERSION\u{001}\r\n"
);
} }
#[test] #[test]
@ -415,7 +576,10 @@ mod test {
fn send_source() { fn send_source() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_source("test").unwrap(); server.send_source("test").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}SOURCE\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}SOURCE\u{001}\r\n"
);
} }
#[test] #[test]
@ -423,7 +587,10 @@ mod test {
fn send_user_info() { fn send_user_info() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_user_info("test").unwrap(); server.send_user_info("test").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}USERINFO\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}USERINFO\u{001}\r\n"
);
} }
#[test] #[test]
@ -442,6 +609,9 @@ mod test {
fn send_time() { fn send_time() {
let server = IrcServer::from_connection(test_config(), MockConnection::empty()); let server = IrcServer::from_connection(test_config(), MockConnection::empty());
server.send_time("test").unwrap(); server.send_time("test").unwrap();
assert_eq!(&get_server_value(server)[..], "PRIVMSG test :\u{001}TIME\u{001}\r\n"); assert_eq!(
&get_server_value(server)[..],
"PRIVMSG test :\u{001}TIME\u{001}\r\n"
);
} }
} }

View file

@ -3,11 +3,14 @@
#![warn(missing_docs)] #![warn(missing_docs)]
extern crate time; extern crate time;
#[cfg(feature = "encode")] extern crate encoding; #[cfg(feature = "encode")]
extern crate encoding;
extern crate serde; extern crate serde;
#[macro_use] extern crate serde_derive; #[macro_use]
extern crate serde_derive;
extern crate serde_json; extern crate serde_json;
#[cfg(feature = "ssl")] extern crate openssl; #[cfg(feature = "ssl")]
extern crate openssl;
pub mod client; pub mod client;
pub mod server; pub mod server;