Merge branch 'master' into async
This commit is contained in:
commit
cf3ee671ed
17 changed files with 2145 additions and 1252 deletions
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue