Merge branch 'master' into async

This commit is contained in:
Aaron Weiss 2017-06-19 14:33:02 -04:00
commit cf3ee671ed
No known key found for this signature in database
GPG key ID: 0237035D9BF03AE2
17 changed files with 2145 additions and 1252 deletions

View file

@ -1,4 +1,4 @@
//! JSON configuration files using libserialize.
//! JSON configuration files using serde
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::fs::File;
@ -6,10 +6,10 @@ use std::io::prelude::*;
use std::io::{Error, ErrorKind, Result};
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::Path;
use rustc_serialize::json::{decode, encode};
use serde_json;
/// Configuration data.
#[derive(Clone, RustcDecodable, RustcEncodable, Default, PartialEq, Debug)]
#[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)]
pub struct Config {
/// A list of the owners of the client by nickname (for bots).
pub owners: Option<Vec<String>>,
@ -43,6 +43,10 @@ pub struct Config {
pub umodes: Option<String>,
/// The text that'll be sent in response to CTCP USERINFO requests.
pub user_info: Option<String>,
/// The text that'll be sent in response to CTCP VERSION requests.
pub version: Option<String>,
/// The text that'll be sent in response to CTCP SOURCE requests.
pub source: Option<String>,
/// The amount of inactivity in seconds before the client will ping the server.
pub ping_time: Option<u32>,
/// The amount of time in seconds for a client to reconnect due to no ping response.
@ -63,22 +67,33 @@ impl Config {
let mut file = try!(File::open(path));
let mut data = String::new();
try!(file.read_to_string(&mut data));
decode(&data[..]).map_err(|_|
Error::new(ErrorKind::InvalidInput, "Failed to decode configuration file.")
)
serde_json::from_str(&data[..]).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to decode configuration file.",
)
})
}
/// Saves a JSON configuration to the desired path.
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut file = try!(File::create(path));
file.write_all(try!(encode(self).map_err(|_|
Error::new(ErrorKind::InvalidInput, "Failed to encode configuration file.")
)).as_bytes())
file.write_all(
try!(serde_json::to_string(self).map_err(|_| {
Error::new(
ErrorKind::InvalidInput,
"Failed to encode configuration file.",
)
})).as_bytes(),
)
}
/// Determines whether or not the nickname provided is the owner of the bot.
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.
@ -96,7 +111,9 @@ impl Config {
/// Gets the alternate nicknames specified in the configuration.
/// This defaults to an empty vector when not specified.
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()
})
}
@ -155,13 +172,17 @@ impl Config {
/// Gets the channels to join upon connection.
/// This defaults to an empty vector if it's not specified.
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.
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.
@ -176,6 +197,21 @@ impl Config {
self.user_info.as_ref().map_or("", |s| &s[..])
}
/// Gets the string to be sent in response to CTCP VERSION requests.
/// This defaults to `irc:git:Rust` when not specified.
pub fn version(&self) -> &str {
self.version.as_ref().map_or("irc:git:Rust", |s| &s[..])
}
/// Gets the string to be sent in response to CTCP SOURCE requests.
/// This defaults to `https://github.com/aatxe/irc` when not specified.
pub fn source(&self) -> &str {
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
/// server.
/// This defaults to 180 seconds when not specified.
@ -198,14 +234,19 @@ impl Config {
/// Gets the NickServ command sequence to recover a nickname.
/// This defaults to `["GHOST"]` when not specified.
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.
/// This uses indexing, and thus panics when the string is not present.
/// This will also panic if used and there are no options.
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()
}
}
@ -234,6 +275,8 @@ mod test {
channels: Some(vec![format!("#test"), format!("#test2")]),
channel_keys: None,
user_info: None,
version: None,
source: None,
ping_time: None,
ping_timeout: None,
should_ghost: None,
@ -261,6 +304,8 @@ mod test {
channels: Some(vec![format!("#test"), format!("#test2")]),
channel_keys: None,
user_info: None,
version: None,
source: None,
ping_time: None,
ping_timeout: None,
should_ghost: None,
@ -275,7 +320,7 @@ mod test {
fn is_owner() {
let cfg = Config {
owners: Some(vec![format!("test"), format!("test2")]),
.. Default::default()
..Default::default()
};
assert!(cfg.is_owner("test"));
assert!(cfg.is_owner("test2"));
@ -290,7 +335,7 @@ mod test {
map.insert(format!("testing"), format!("test"));
Some(map)
},
.. Default::default()
..Default::default()
};
assert_eq!(cfg.get_option("testing"), "test");
}

View file

@ -13,11 +13,19 @@ pub mod kinds {
/// Trait describing all possible Writers for this library.
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.
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 config;

View file

@ -25,9 +25,9 @@ impl User {
let ranks: Vec<_> = AccessLevelIterator::new(string).collect();
let mut state = &string[ranks.len()..];
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 hostname = state.find('@').map(|i| state[i+1..].to_owned());
let hostname = state.find('@').map(|i| state[i + 1..].to_owned());
User {
nickname: nickname,
username: username,
@ -39,7 +39,7 @@ impl User {
},
highest_access_level: {
let mut max = AccessLevel::Member;
for rank in ranks.into_iter() {
for rank in ranks {
if rank > max {
max = rank
}
@ -89,8 +89,8 @@ impl User {
"-h" => self.sub_access_level(AccessLevel::HalfOp),
"+v" => self.add_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.
@ -123,7 +123,7 @@ impl User {
impl PartialEq for User {
fn eq(&self, other: &User) -> bool {
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 {
fn partial_cmp(&self, other: &AccessLevel) -> Option<Ordering> {
if self == other { return Some(Equal) }
if self == other {
return Some(Equal);
}
match *self {
AccessLevel::Owner => Some(Greater),
AccessLevel::Admin => {
@ -155,28 +157,28 @@ impl PartialOrd for AccessLevel {
} else {
Some(Greater)
}
},
}
AccessLevel::Oper => {
if other == &AccessLevel::Owner || other == &AccessLevel::Admin {
Some(Less)
} else {
Some(Greater)
}
},
}
AccessLevel::HalfOp => {
if other == &AccessLevel::Voice || other == &AccessLevel::Member {
Some(Greater)
} else {
Some(Less)
}
},
}
AccessLevel::Voice => {
if other == &AccessLevel::Member {
Some(Greater)
} else {
Some(Less)
}
},
}
AccessLevel::Member => Some(Less),
}
}
@ -192,7 +194,7 @@ impl FromStr for AccessLevel {
Some('%') => Ok(AccessLevel::HalfOp),
Some('+') => Ok(AccessLevel::Voice),
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,
hostname: None,
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.highest_access_level, exp.highest_access_level);
@ -324,7 +326,10 @@ mod test {
fn derank_user_in_full() {
let mut user = User::new("~&@%+user");
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");
assert_eq!(user.highest_access_level, Owner);
assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]);