use std::fmt;
use crate::command::Command;
use crate::error::MessageParseError;
use crate::error::MessageParseError::InvalidModeString;
use crate::error::ModeParseError::*;
pub trait ModeType: fmt::Display + fmt::Debug + Clone + PartialEq {
fn mode(target: &str, modes: &[Mode<Self>]) -> Command;
fn takes_arg(&self) -> bool;
fn from_char(c: char) -> Self;
}
#[derive(Clone, Debug, PartialEq)]
pub enum UserMode {
Away,
Invisible,
Wallops,
Restricted,
Oper,
LocalOper,
ServerNotices,
MaskedHost,
Unknown(char),
}
impl ModeType for UserMode {
fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
Command::UserMODE(target.to_owned(), modes.to_owned())
}
fn takes_arg(&self) -> bool {
false
}
fn from_char(c: char) -> UserMode {
use self::UserMode::*;
match c {
'a' => Away,
'i' => Invisible,
'w' => Wallops,
'r' => Restricted,
'o' => Oper,
'O' => LocalOper,
's' => ServerNotices,
'x' => MaskedHost,
_ => Unknown(c),
}
}
}
impl fmt::Display for UserMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::UserMode::*;
write!(
f,
"{}",
match *self {
Away => 'a',
Invisible => 'i',
Wallops => 'w',
Restricted => 'r',
Oper => 'o',
LocalOper => 'O',
ServerNotices => 's',
MaskedHost => 'x',
Unknown(c) => c,
}
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ChannelMode {
Ban,
Exception,
Limit,
InviteOnly,
InviteException,
Key,
Moderated,
RegisteredOnly,
Secret,
ProtectedTopic,
NoExternalMessages,
Founder,
Admin,
Oper,
Halfop,
Voice,
Unknown(char),
}
impl ModeType for ChannelMode {
fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
Command::ChannelMODE(target.to_owned(), modes.to_owned())
}
fn takes_arg(&self) -> bool {
use self::ChannelMode::*;
match *self {
Ban | Exception | Limit | InviteException | Key | Founder | Admin | Oper | Halfop
| Voice => true,
_ => false,
}
}
fn from_char(c: char) -> ChannelMode {
use self::ChannelMode::*;
match c {
'b' => Ban,
'e' => Exception,
'l' => Limit,
'i' => InviteOnly,
'I' => InviteException,
'k' => Key,
'm' => Moderated,
'r' => RegisteredOnly,
's' => Secret,
't' => ProtectedTopic,
'n' => NoExternalMessages,
'q' => Founder,
'a' => Admin,
'o' => Oper,
'h' => Halfop,
'v' => Voice,
_ => Unknown(c),
}
}
}
impl fmt::Display for ChannelMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ChannelMode::*;
write!(
f,
"{}",
match *self {
Ban => 'b',
Exception => 'e',
Limit => 'l',
InviteOnly => 'i',
InviteException => 'I',
Key => 'k',
Moderated => 'm',
RegisteredOnly => 'r',
Secret => 's',
ProtectedTopic => 't',
NoExternalMessages => 'n',
Founder => 'q',
Admin => 'a',
Oper => 'o',
Halfop => 'h',
Voice => 'v',
Unknown(c) => c,
}
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Mode<T>
where
T: ModeType,
{
Plus(T, Option<String>),
Minus(T, Option<String>),
}
impl<T> Mode<T>
where
T: ModeType,
{
pub fn plus(inner: T, arg: Option<&str>) -> Mode<T> {
Mode::Plus(inner, arg.map(|s| s.to_owned()))
}
pub fn minus(inner: T, arg: Option<&str>) -> Mode<T> {
Mode::Minus(inner, arg.map(|s| s.to_owned()))
}
}
impl<T> fmt::Display for Mode<T>
where
T: ModeType,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Mode::Plus(ref mode, Some(ref arg)) => write!(f, "{}{} {}", "+", mode, arg),
Mode::Minus(ref mode, Some(ref arg)) => write!(f, "{}{} {}", "-", mode, arg),
Mode::Plus(ref mode, None) => write!(f, "{}{}", "+", mode),
Mode::Minus(ref mode, None) => write!(f, "{}{}", "-", mode),
}
}
}
enum PlusMinus {
Plus,
Minus,
}
impl Mode<UserMode> {
pub fn as_user_modes(pieces: &[&str]) -> Result<Vec<Mode<UserMode>>, MessageParseError> {
parse_modes(pieces)
}
}
impl Mode<ChannelMode> {
pub fn as_channel_modes(pieces: &[&str]) -> Result<Vec<Mode<ChannelMode>>, MessageParseError> {
parse_modes(pieces)
}
}
fn parse_modes<T>(pieces: &[&str]) -> Result<Vec<Mode<T>>, MessageParseError>
where
T: ModeType,
{
use self::PlusMinus::*;
let mut res = vec![];
if let Some((first, rest)) = pieces.split_first() {
let mut modes = first.chars();
let mut args = rest.iter();
let mut cur_mod = match modes.next() {
Some('+') => Plus,
Some('-') => Minus,
Some(c) => {
return Err(InvalidModeString {
string: pieces.join(" ").to_owned(),
cause: InvalidModeModifier { modifier: c },
})
}
None => {
return Ok(res);
}
};
for c in modes {
match c {
'+' => cur_mod = Plus,
'-' => cur_mod = Minus,
_ => {
let mode = T::from_char(c);
let arg = if mode.takes_arg() {
args.next()
} else {
None
};
res.push(match cur_mod {
Plus => Mode::Plus(mode, arg.map(|s| s.to_string())),
Minus => Mode::Minus(mode, arg.map(|s| s.to_string())),
})
}
}
}
Ok(res)
} else {
Ok(res)
}
}
#[cfg(test)]
mod test {
use super::{ChannelMode, Mode};
use crate::Command;
use crate::Message;
#[test]
fn parse_channel_mode() {
let cmd = "MODE #foo +r".parse::<Message>().unwrap().command;
assert_eq!(
Command::ChannelMODE(
"#foo".to_string(),
vec![Mode::Plus(ChannelMode::RegisteredOnly, None)]
),
cmd
);
}
#[test]
fn parse_no_mode() {
let cmd = "MODE #foo".parse::<Message>().unwrap().command;
assert_eq!(Command::ChannelMODE("#foo".to_string(), vec![]), cmd);
}
}