Refactored Mode API into its own module and added it to prelude.

This commit is contained in:
Aaron Weiss 2017-06-22 14:07:34 -04:00
parent d9f4f82051
commit eecbe1630c
No known key found for this signature in database
GPG key ID: 0237035D9BF03AE2
5 changed files with 315 additions and 293 deletions

View file

@ -11,6 +11,7 @@ pub mod prelude {
pub use client::server::{IrcServer, Server};
pub use client::server::utils::ServerExt;
pub use proto::{Capability, Command, Message, NegotiationVersion, Response};
pub use proto::{ChannelMode, Mode, UserMode};
pub use futures::{Future, Stream};
}

View file

@ -3,7 +3,7 @@ use std::borrow::ToOwned;
use error::Result;
use proto::{Capability, Command, Mode, NegotiationVersion};
use proto::command::Command::*;
use proto::command::ModeType;
use proto::mode::ModeType;
use client::server::Server;
use proto::command::CapSubCommand::{END, LS, REQ};
#[cfg(feature = "ctcp")]

View file

@ -1,9 +1,8 @@
//! Enumeration of all available client commands.
use std::ascii::AsciiExt;
use std::fmt;
use std::str::FromStr;
use error;
use proto::Response;
use proto::{ChannelMode, Mode, Response, UserMode};
/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
/// also includes commands from the
@ -1755,295 +1754,6 @@ impl FromStr for BatchSubCommand {
}
}
/// A marker trait for different kinds of Modes.
pub trait ModeType: fmt::Display + fmt::Debug + Clone + PartialEq {
/// Creates a command of this kind.
fn mode(target: &str, modes: &[Mode<Self>]) -> Command;
/// Returns true if this mode takes an argument, and false otherwise.
fn takes_arg(&self) -> bool;
}
/// User modes for the MODE command.
#[derive(Clone, Debug, PartialEq)]
pub enum UserMode {
/// a - user is flagged as away
Away,
/// i - marks a users as invisible
Invisible,
/// w - user receives wallops
Wallops,
/// r - restricted user connection
Restricted,
/// o - operator flag
Oper,
/// O - local operator flag
LocalOper,
/// s - marks a user for receipt of server notices
ServerNotices,
/// Any other unknown-to-the-crate mode.
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
}
}
impl UserMode {
fn from_char(c: char) -> error::Result<UserMode> {
use self::UserMode::*;
Ok(match c {
'a' => Away,
'i' => Invisible,
'w' => Wallops,
'r' => Restricted,
'o' => Oper,
'O' => LocalOper,
's' => ServerNotices,
_ => 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',
Unknown(c) => c,
})
}
}
/// Channel modes for the MODE command.
#[derive(Clone, Debug, PartialEq)]
pub enum ChannelMode {
/// b - ban the user from joining or speaking in the channel
Ban,
/// e - exemptions from bans
Exception,
/// l - limit the maximum number of users in a channel
Limit,
/// i - channel becomes invite-only
InviteOnly,
/// I - exception to invite-only rule
InviteException,
/// k - specify channel key
Key,
/// m - channel is in moderated mode
Moderated,
/// s - channel is hidden from listings
Secret,
/// t - require permissions to edit topic
ProtectedTopic,
/// n - users must join channels to message them
NoExternalMessages,
/// q - user gets founder permission
Founder,
/// a - user gets admin or protected permission
Admin,
/// o - user gets oper permission
Oper,
/// h - user gets halfop permission
Halfop,
/// v - user gets voice permission
Voice,
/// Any other unknown-to-the-crate mode.
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,
}
}
}
impl ChannelMode {
fn from_char(c: char) -> error::Result<ChannelMode> {
use self::ChannelMode::*;
Ok(match c {
'b' => Ban,
'e' => Exception,
'l' => Limit,
'i' => InviteOnly,
'I' => InviteException,
'k' => Key,
'm' => Moderated,
'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',
Secret => 's',
ProtectedTopic => 't',
NoExternalMessages => 'n',
Founder => 'q',
Admin => 'a',
Oper => 'o',
Halfop => 'h',
Voice => 'v',
Unknown(c) => c,
})
}
}
/// A mode argument for the MODE command.
#[derive(Clone, Debug, PartialEq)]
pub enum Mode<T>
where
T: ModeType,
{
/// Adding the specified mode, optionally with an argument.
Plus(T, Option<String>),
/// Removing the specified mode, optionally with an argument.
Minus(T, Option<String>),
}
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,
}
// MODE user [modes]
impl Mode<UserMode> {
// TODO: turning more edge cases into errors.
/// Parses the specified mode string as user modes.
pub fn as_user_modes(s: &str) -> error::Result<Vec<Mode<UserMode>>> {
use self::PlusMinus::*;
let mut res = vec![];
let mut pieces = s.split(" ");
for term in pieces.clone() {
if term.starts_with("+") || term.starts_with("-") {
let _ = pieces.next();
let mut chars = term.chars();
let init = match chars.next() {
Some('+') => Plus,
Some('-') => Minus,
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
};
for c in chars {
let mode = UserMode::from_char(c)?;
let arg = if mode.takes_arg() {
pieces.next()
} else {
None
};
res.push(match init {
Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
})
}
}
}
Ok(res)
}
}
// MODE channel [modes [modeparams]]
impl Mode<ChannelMode> {
// TODO: turning more edge cases into errors.
/// Parses the specified mode string as channel modes.
pub fn as_channel_modes(s: &str) -> error::Result<Vec<Mode<ChannelMode>>> {
use self::PlusMinus::*;
let mut res = vec![];
let mut pieces = s.split(" ");
for term in pieces.clone() {
if term.starts_with("+") || term.starts_with("-") {
let _ = pieces.next();
let mut chars = term.chars();
let init = match chars.next() {
Some('+') => Plus,
Some('-') => Minus,
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
};
for c in chars {
let mode = ChannelMode::from_char(c)?;
let arg = if mode.takes_arg() {
pieces.next()
} else {
None
};
res.push(match init {
Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
})
}
}
}
Ok(res)
}
}
/// An extension trait giving strings a function to check if they are a channel.
pub trait ChannelExt {
/// Returns true if the specified name is a channel name.

View file

@ -5,10 +5,12 @@ pub mod command;
pub mod irc;
pub mod line;
pub mod message;
pub mod mode;
pub mod response;
pub use self::caps::{Capability, NegotiationVersion};
pub use self::command::{BatchSubCommand, CapSubCommand, ChannelMode, Command, Mode, UserMode};
pub use self::command::{BatchSubCommand, CapSubCommand, Command};
pub use self::irc::IrcCodec;
pub use self::message::Message;
pub use self::mode::{ChannelMode, Mode, UserMode};
pub use self::response::Response;

309
src/proto/mode.rs Normal file
View file

@ -0,0 +1,309 @@
//! A module defining an API for IRC user and channel modes.
use std::fmt;
use error;
use proto::Command;
/// A marker trait for different kinds of Modes.
pub trait ModeType: fmt::Display + fmt::Debug + Clone + PartialEq {
/// Creates a command of this kind.
fn mode(target: &str, modes: &[Mode<Self>]) -> Command;
/// Returns true if this mode takes an argument, and false otherwise.
fn takes_arg(&self) -> bool;
}
/// User modes for the MODE command.
#[derive(Clone, Debug, PartialEq)]
pub enum UserMode {
/// a - user is flagged as away
Away,
/// i - marks a users as invisible
Invisible,
/// w - user receives wallops
Wallops,
/// r - restricted user connection
Restricted,
/// o - operator flag
Oper,
/// O - local operator flag
LocalOper,
/// s - marks a user for receipt of server notices
ServerNotices,
/// Any other unknown-to-the-crate mode.
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
}
}
impl UserMode {
fn from_char(c: char) -> error::Result<UserMode> {
use self::UserMode::*;
Ok(match c {
'a' => Away,
'i' => Invisible,
'w' => Wallops,
'r' => Restricted,
'o' => Oper,
'O' => LocalOper,
's' => ServerNotices,
_ => 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',
Unknown(c) => c,
})
}
}
/// Channel modes for the MODE command.
#[derive(Clone, Debug, PartialEq)]
pub enum ChannelMode {
/// b - ban the user from joining or speaking in the channel
Ban,
/// e - exemptions from bans
Exception,
/// l - limit the maximum number of users in a channel
Limit,
/// i - channel becomes invite-only
InviteOnly,
/// I - exception to invite-only rule
InviteException,
/// k - specify channel key
Key,
/// m - channel is in moderated mode
Moderated,
/// s - channel is hidden from listings
Secret,
/// t - require permissions to edit topic
ProtectedTopic,
/// n - users must join channels to message them
NoExternalMessages,
/// q - user gets founder permission
Founder,
/// a - user gets admin or protected permission
Admin,
/// o - user gets oper permission
Oper,
/// h - user gets halfop permission
Halfop,
/// v - user gets voice permission
Voice,
/// Any other unknown-to-the-crate mode.
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,
}
}
}
impl ChannelMode {
fn from_char(c: char) -> error::Result<ChannelMode> {
use self::ChannelMode::*;
Ok(match c {
'b' => Ban,
'e' => Exception,
'l' => Limit,
'i' => InviteOnly,
'I' => InviteException,
'k' => Key,
'm' => Moderated,
'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',
Secret => 's',
ProtectedTopic => 't',
NoExternalMessages => 'n',
Founder => 'q',
Admin => 'a',
Oper => 'o',
Halfop => 'h',
Voice => 'v',
Unknown(c) => c,
})
}
}
/// A mode argument for the MODE command.
#[derive(Clone, Debug, PartialEq)]
pub enum Mode<T>
where
T: ModeType,
{
/// Adding the specified mode, optionally with an argument.
Plus(T, Option<String>),
/// Removing the specified mode, optionally with an argument.
Minus(T, Option<String>),
}
impl<T> Mode<T>
where
T: ModeType,
{
/// Creates a plus mode with an `&str` argument.
pub fn plus(inner: T, arg: Option<&str>) -> Mode<T> {
Mode::Plus(inner, arg.map(|s| s.to_owned()))
}
/// Creates a minus mode with an `&str` argument.
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,
}
// MODE user [modes]
impl Mode<UserMode> {
// TODO: turning more edge cases into errors.
/// Parses the specified mode string as user modes.
pub fn as_user_modes(s: &str) -> error::Result<Vec<Mode<UserMode>>> {
use self::PlusMinus::*;
let mut res = vec![];
let mut pieces = s.split(" ");
for term in pieces.clone() {
if term.starts_with("+") || term.starts_with("-") {
let _ = pieces.next();
let mut chars = term.chars();
let init = match chars.next() {
Some('+') => Plus,
Some('-') => Minus,
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
};
for c in chars {
let mode = UserMode::from_char(c)?;
let arg = if mode.takes_arg() {
pieces.next()
} else {
None
};
res.push(match init {
Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
})
}
}
}
Ok(res)
}
}
// MODE channel [modes [modeparams]]
impl Mode<ChannelMode> {
// TODO: turning more edge cases into errors.
/// Parses the specified mode string as channel modes.
pub fn as_channel_modes(s: &str) -> error::Result<Vec<Mode<ChannelMode>>> {
use self::PlusMinus::*;
let mut res = vec![];
let mut pieces = s.split(" ");
for term in pieces.clone() {
if term.starts_with("+") || term.starts_with("-") {
let _ = pieces.next();
let mut chars = term.chars();
let init = match chars.next() {
Some('+') => Plus,
Some('-') => Minus,
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
};
for c in chars {
let mode = ChannelMode::from_char(c)?;
let arg = if mode.takes_arg() {
pieces.next()
} else {
None
};
res.push(match init {
Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
})
}
}
}
Ok(res)
}
}