//! Data for tracking user information. use std::borrow::ToOwned; use std::cmp::Ordering; use std::cmp::Ordering::{Less, Equal, Greater}; use std::str::FromStr; /// IRC User data. #[derive(Clone, Debug)] pub struct User { /// The user's nickname. nickname: String, /// The user's username. username: Option, /// The user's hostname. hostname: Option, /// The user's highest access level. highest_access_level: AccessLevel, /// All of the user's current access levels. access_levels: Vec, } impl User { /// Creates a new User. pub fn new(string: &str) -> 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..]); let username = state.find('@').map(|i| state[..i].to_owned()); let hostname = state.find('@').map(|i| state[i + 1..].to_owned()); User { nickname: nickname, username: username, hostname: hostname, access_levels: { let mut ranks = ranks.clone(); ranks.push(AccessLevel::Member); ranks }, highest_access_level: { let mut max = AccessLevel::Member; for rank in ranks { if rank > max { max = rank } } max }, } } /// Gets the nickname of the user. pub fn get_nickname(&self) -> &str { &self.nickname } /// Gets the username of the user, if it's known. /// This requires the IRCv3.2 extension `userhost-in-name`. pub fn get_username(&self) -> Option<&str> { self.username.as_ref().map(|s| &s[..]) } /// Gets the hostname of the user, if it's known. /// This requires the IRCv3.2 extension `userhost-in-name`. pub fn get_hostname(&self) -> Option<&str> { self.hostname.as_ref().map(|s| &s[..]) } /// Gets the user's highest access level. pub fn highest_access_level(&self) -> AccessLevel { self.highest_access_level } /// Gets all the user's access levels. pub fn access_levels(&self) -> Vec { self.access_levels.clone() } /// Updates the user's access level. pub fn update_access_level(&mut self, mode: &str) { match mode { "+q" => self.add_access_level(AccessLevel::Owner), "-q" => self.sub_access_level(AccessLevel::Owner), "+a" => self.add_access_level(AccessLevel::Admin), "-a" => self.sub_access_level(AccessLevel::Admin), "+o" => self.add_access_level(AccessLevel::Oper), "-o" => self.sub_access_level(AccessLevel::Oper), "+h" => self.add_access_level(AccessLevel::HalfOp), "-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. fn add_access_level(&mut self, level: AccessLevel) { if level > self.highest_access_level() { self.highest_access_level = level } self.access_levels.push(level) } /// Removes an access level from the list, and updates the highest level if necessary. fn sub_access_level(&mut self, level: AccessLevel) { if let Some(n) = self.access_levels.iter().position(|x| *x == level) { self.access_levels.swap_remove(n); } if level == self.highest_access_level() { self.highest_access_level = { let mut max = AccessLevel::Member; for level in &self.access_levels { if level > &max { max = *level } } max } } } } impl PartialEq for User { fn eq(&self, other: &User) -> bool { self.nickname == other.nickname && self.username == other.username && self.hostname == other.hostname } } /// The user's access level. #[derive(Copy, PartialEq, Clone, Debug)] pub enum AccessLevel { /// The channel owner (~). Owner, /// A channel administrator (&). Admin, /// A channel operator (@), Oper, /// A channel half-oper (%), HalfOp, /// A user with voice (+), Voice, /// A normal user, Member, } impl PartialOrd for AccessLevel { fn partial_cmp(&self, other: &AccessLevel) -> Option { if self == other { return Some(Equal); } match *self { AccessLevel::Owner => Some(Greater), AccessLevel::Admin => { if other == &AccessLevel::Owner { Some(Less) } 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), } } } impl FromStr for AccessLevel { type Err = &'static str; fn from_str(s: &str) -> Result { match s.chars().next() { Some('~') => Ok(AccessLevel::Owner), Some('&') => Ok(AccessLevel::Admin), Some('@') => Ok(AccessLevel::Oper), Some('%') => Ok(AccessLevel::HalfOp), Some('+') => Ok(AccessLevel::Voice), None => Err("No access level in an empty string."), _ => Err("Failed to parse access level."), } } } /// An iterator used to parse access levels from strings. struct AccessLevelIterator { value: String, } impl AccessLevelIterator { pub fn new(value: &str) -> AccessLevelIterator { AccessLevelIterator { value: value.to_owned() } } } impl Iterator for AccessLevelIterator { type Item = AccessLevel; fn next(&mut self) -> Option { let ret = self.value.parse(); if !self.value.is_empty() { self.value = self.value.chars().skip(1).collect() } ret.ok() } } #[cfg(test)] mod test { use super::{AccessLevel, User}; use super::AccessLevel::*; #[test] fn parse_access_level() { assert!("member".parse::().is_err()); assert_eq!("~owner".parse::().unwrap(), Owner); assert_eq!("&admin".parse::().unwrap(), Admin); assert_eq!("@oper".parse::().unwrap(), Oper); assert_eq!("%halfop".parse::().unwrap(), HalfOp); assert_eq!("+voice".parse::().unwrap(), Voice); assert!("".parse::().is_err()); } #[test] fn create_user() { let user = User::new("~owner"); let exp = User { nickname: format!("owner"), username: None, hostname: None, highest_access_level: Owner, access_levels: vec![Owner, Member], }; assert_eq!(user, exp); assert_eq!(user.highest_access_level, exp.highest_access_level); assert_eq!(user.access_levels, exp.access_levels); } #[test] fn create_user_complex() { let user = User::new("~&+user"); let exp = User { nickname: format!("user"), username: None, hostname: None, highest_access_level: Owner, access_levels: vec![Owner, Admin, Voice, Member], }; assert_eq!(user, exp); assert_eq!(user.highest_access_level, exp.highest_access_level); assert_eq!(user.access_levels, exp.access_levels); } #[test] fn get_nickname() { let user = User::new("~owner"); assert_eq!(user.get_nickname(), "owner"); } #[test] fn get_username() { let user = User::new("user!username@hostname"); assert_eq!(user.get_username(), Some("username")); let user = User::new("user"); assert_eq!(user.get_username(), None); } #[test] fn get_hostname() { let user = User::new("user!username@hostname"); assert_eq!(user.get_hostname(), Some("hostname")); let user = User::new("user"); assert_eq!(user.get_hostname(), None); } #[test] fn access_level() { let user = User::new("~owner"); assert_eq!(user.highest_access_level(), Owner); } #[test] fn update_user_rank() { let mut user = User::new("user"); assert_eq!(user.highest_access_level, Member); user.update_access_level("+q"); assert_eq!(user.highest_access_level, Owner); user.update_access_level("-q"); assert_eq!(user.highest_access_level, Member); user.update_access_level("+a"); assert_eq!(user.highest_access_level, Admin); user.update_access_level("-a"); assert_eq!(user.highest_access_level, Member); user.update_access_level("+o"); assert_eq!(user.highest_access_level, Oper); user.update_access_level("-o"); assert_eq!(user.highest_access_level, Member); user.update_access_level("+h"); assert_eq!(user.highest_access_level, HalfOp); user.update_access_level("-h"); assert_eq!(user.highest_access_level, Member); user.update_access_level("+v"); assert_eq!(user.highest_access_level, Voice); user.update_access_level("-v"); assert_eq!(user.highest_access_level, Member); } #[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] ); user.update_access_level("-h"); assert_eq!(user.highest_access_level, Owner); assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]); user.update_access_level("-q"); assert_eq!(user.highest_access_level, Admin); assert_eq!(user.access_levels, vec![Voice, Admin, Oper, Member]); user.update_access_level("-a"); assert_eq!(user.highest_access_level, Oper); assert_eq!(user.access_levels, vec![Voice, Member, Oper]); user.update_access_level("-o"); assert_eq!(user.highest_access_level, Voice); assert_eq!(user.access_levels, vec![Voice, Member]); user.update_access_level("-v"); assert_eq!(user.highest_access_level, Member); assert_eq!(user.access_levels, vec![Member]); } }