Implemented new Mode API (fixes #48).
This commit is contained in:
parent
54326e0047
commit
d9f4f82051
7 changed files with 396 additions and 88 deletions
|
@ -42,7 +42,7 @@ pub struct Config {
|
|||
pub channels: Option<Vec<String>>,
|
||||
/// A mapping of channel names to keys for join-on-connect.
|
||||
pub channel_keys: Option<HashMap<String, String>>,
|
||||
/// User modes to set on connect. Example: "+RB-x"
|
||||
/// User modes to set on connect. Example: "+RB -x"
|
||||
pub umodes: Option<String>,
|
||||
/// The text that'll be sent in response to CTCP USERINFO requests.
|
||||
pub user_info: Option<String>,
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::borrow::ToOwned;
|
|||
use std::cmp::Ordering;
|
||||
use std::cmp::Ordering::{Less, Equal, Greater};
|
||||
use std::str::FromStr;
|
||||
use proto::{Mode, ChannelMode};
|
||||
|
||||
/// IRC User data.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -77,18 +78,18 @@ impl User {
|
|||
}
|
||||
|
||||
/// Updates the user's access level.
|
||||
pub fn update_access_level(&mut self, mode: &str) {
|
||||
pub fn update_access_level(&mut self, mode: &Mode<ChannelMode>) {
|
||||
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),
|
||||
&Mode::Plus(ChannelMode::Founder, _) => self.add_access_level(AccessLevel::Owner),
|
||||
&Mode::Minus(ChannelMode::Founder, _) => self.sub_access_level(AccessLevel::Owner),
|
||||
&Mode::Plus(ChannelMode::Admin, _) => self.add_access_level(AccessLevel::Admin),
|
||||
&Mode::Minus(ChannelMode::Admin, _) => self.sub_access_level(AccessLevel::Admin),
|
||||
&Mode::Plus(ChannelMode::Oper, _) => self.add_access_level(AccessLevel::Oper),
|
||||
&Mode::Minus(ChannelMode::Oper, _) => self.sub_access_level(AccessLevel::Oper),
|
||||
&Mode::Plus(ChannelMode::Halfop, _) => self.add_access_level(AccessLevel::HalfOp),
|
||||
&Mode::Minus(ChannelMode::Halfop, _) => self.sub_access_level(AccessLevel::HalfOp),
|
||||
&Mode::Plus(ChannelMode::Voice, _) => self.add_access_level(AccessLevel::Voice),
|
||||
&Mode::Minus(ChannelMode::Voice, _) => self.sub_access_level(AccessLevel::Voice),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -225,6 +226,8 @@ impl Iterator for AccessLevelIterator {
|
|||
mod test {
|
||||
use super::{AccessLevel, User};
|
||||
use super::AccessLevel::*;
|
||||
use proto::Mode::*;
|
||||
use proto::ChannelMode as M;
|
||||
|
||||
#[test]
|
||||
fn parse_access_level() {
|
||||
|
@ -300,25 +303,25 @@ mod test {
|
|||
fn update_user_rank() {
|
||||
let mut user = User::new("user");
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
user.update_access_level("+q");
|
||||
user.update_access_level(&Plus(M::Founder, None));
|
||||
assert_eq!(user.highest_access_level, Owner);
|
||||
user.update_access_level("-q");
|
||||
user.update_access_level(&Minus(M::Founder, None));
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
user.update_access_level("+a");
|
||||
user.update_access_level(&Plus(M::Admin, None));
|
||||
assert_eq!(user.highest_access_level, Admin);
|
||||
user.update_access_level("-a");
|
||||
user.update_access_level(&Minus(M::Admin, None));
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
user.update_access_level("+o");
|
||||
user.update_access_level(&Plus(M::Oper, None));
|
||||
assert_eq!(user.highest_access_level, Oper);
|
||||
user.update_access_level("-o");
|
||||
user.update_access_level(&Minus(M::Oper, None));
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
user.update_access_level("+h");
|
||||
user.update_access_level(&Plus(M::Halfop, None));
|
||||
assert_eq!(user.highest_access_level, HalfOp);
|
||||
user.update_access_level("-h");
|
||||
user.update_access_level(&Minus(M::Halfop, None));
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
user.update_access_level("+v");
|
||||
user.update_access_level(&Plus(M::Voice, None));
|
||||
assert_eq!(user.highest_access_level, Voice);
|
||||
user.update_access_level("-v");
|
||||
user.update_access_level(&Minus(M::Voice, None));
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
}
|
||||
|
||||
|
@ -330,19 +333,19 @@ mod test {
|
|||
user.access_levels,
|
||||
vec![Owner, Admin, Oper, HalfOp, Voice, Member]
|
||||
);
|
||||
user.update_access_level("-h");
|
||||
user.update_access_level(&Minus(M::Halfop, None));
|
||||
assert_eq!(user.highest_access_level, Owner);
|
||||
assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]);
|
||||
user.update_access_level("-q");
|
||||
user.update_access_level(&Minus(M::Founder, None));
|
||||
assert_eq!(user.highest_access_level, Admin);
|
||||
assert_eq!(user.access_levels, vec![Voice, Admin, Oper, Member]);
|
||||
user.update_access_level("-a");
|
||||
user.update_access_level(&Minus(M::Admin, None));
|
||||
assert_eq!(user.highest_access_level, Oper);
|
||||
assert_eq!(user.access_levels, vec![Voice, Member, Oper]);
|
||||
user.update_access_level("-o");
|
||||
user.update_access_level(&Minus(M::Oper, None));
|
||||
assert_eq!(user.highest_access_level, Voice);
|
||||
assert_eq!(user.access_levels, vec![Voice, Member]);
|
||||
user.update_access_level("-v");
|
||||
user.update_access_level(&Minus(M::Voice, None));
|
||||
assert_eq!(user.highest_access_level, Member);
|
||||
assert_eq!(user.access_levels, vec![Member]);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ use client::conn::Connection;
|
|||
use client::data::{Config, User};
|
||||
use client::server::utils::ServerExt;
|
||||
use client::transport::LogView;
|
||||
use proto::{Command, Message, Response};
|
||||
use proto::Command::{JOIN, NICK, NICKSERV, PART, PRIVMSG, MODE, QUIT};
|
||||
use proto::{ChannelMode, Command, Message, Mode, Response};
|
||||
use proto::Command::{JOIN, NICK, NICKSERV, PART, PRIVMSG, ChannelMODE, QUIT};
|
||||
use futures::{Async, Poll, Future, Sink, Stream};
|
||||
use futures::stream::SplitStream;
|
||||
use futures::sync::mpsc;
|
||||
|
@ -206,7 +206,7 @@ impl ServerState {
|
|||
NICK(ref new_nick) => {
|
||||
self.handle_nick_change(msg.source_nickname().unwrap_or(""), new_nick)
|
||||
}
|
||||
MODE(ref chan, ref mode, Some(ref user)) => self.handle_mode(chan, mode, user),
|
||||
ChannelMODE(ref chan, ref modes) => self.handle_mode(chan, modes),
|
||||
PRIVMSG(ref target, ref body) => {
|
||||
if body.starts_with('\u{001}') {
|
||||
let tokens: Vec<_> = {
|
||||
|
@ -290,7 +290,7 @@ impl ServerState {
|
|||
if self.config().umodes().is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
self.send_mode(self.current_nickname(), self.config().umodes(), "")
|
||||
self.send_mode(self.current_nickname(), &Mode::as_user_modes(self.config().umodes())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,16 +358,23 @@ impl ServerState {
|
|||
}
|
||||
|
||||
#[cfg(feature = "nochanlists")]
|
||||
fn handle_mode(&self, chan: &str, mode: &str, user: &str) {}
|
||||
fn handle_mode(&self, _: &str, _: &[Mode<ChannelMODE>]) {}
|
||||
|
||||
#[cfg(not(feature = "nochanlists"))]
|
||||
fn handle_mode(&self, chan: &str, mode: &str, user: &str) {
|
||||
fn handle_mode(&self, chan: &str, modes: &[Mode<ChannelMode>]) {
|
||||
for mode in modes {
|
||||
match mode {
|
||||
&Mode::Plus(_, Some(ref user)) | &Mode::Minus(_, Some(ref user)) => {
|
||||
if let Some(vec) = self.chanlists.lock().unwrap().get_mut(chan) {
|
||||
if let Some(n) = vec.iter().position(|x| x.get_nickname() == user) {
|
||||
vec[n].update_access_level(mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nochanlists")]
|
||||
fn handle_namreply(&self, _: &[String], _: &Option<String>) {}
|
||||
|
@ -556,6 +563,7 @@ mod test {
|
|||
use client::data::Config;
|
||||
#[cfg(not(feature = "nochanlists"))]
|
||||
use client::data::User;
|
||||
use proto::{ChannelMode, Mode};
|
||||
use proto::command::Command::{PART, PRIVMSG};
|
||||
use futures::{Future, Stream};
|
||||
|
||||
|
@ -893,7 +901,7 @@ mod test {
|
|||
vec![User::new("@test"), User::new("~owner"), User::new("&admin")]
|
||||
);
|
||||
let mut exp = User::new("@test");
|
||||
exp.update_access_level("+v");
|
||||
exp.update_access_level(&Mode::Plus(ChannelMode::Voice, None));
|
||||
assert_eq!(
|
||||
server.list_users("#test").unwrap()[0].highest_access_level(),
|
||||
exp.highest_access_level()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//! Utilities and shortcuts for working with IRC servers.
|
||||
use std::borrow::ToOwned;
|
||||
use error::Result;
|
||||
use proto::{Capability, NegotiationVersion};
|
||||
use proto::Command::{AUTHENTICATE, CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NOTICE};
|
||||
use proto::Command::{OPER, PART, PASS, PONG, PRIVMSG, QUIT, SAMODE, SANICK, TOPIC, USER};
|
||||
use proto::{Capability, Command, Mode, NegotiationVersion};
|
||||
use proto::command::Command::*;
|
||||
use proto::command::ModeType;
|
||||
use client::server::Server;
|
||||
use proto::command::CapSubCommand::{END, LS, REQ};
|
||||
#[cfg(feature = "ctcp")]
|
||||
|
@ -16,7 +16,7 @@ pub trait ServerExt: Server {
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.send(CAP(
|
||||
self.send(Command::CAP(
|
||||
None,
|
||||
LS,
|
||||
match version {
|
||||
|
@ -201,21 +201,13 @@ pub trait ServerExt: Server {
|
|||
))
|
||||
}
|
||||
|
||||
/// Changes the mode of the target.
|
||||
/// If `modeparmas` is an empty string, it won't be included in the message.
|
||||
fn send_mode(&self, target: &str, mode: &str, modeparams: &str) -> Result<()>
|
||||
/// Changes the modes for the specified target.
|
||||
fn send_mode<T>(&self, target: &str, modes: &[Mode<T>]) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
T: ModeType,
|
||||
{
|
||||
self.send(MODE(
|
||||
target.to_owned(),
|
||||
mode.to_owned(),
|
||||
if modeparams.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(modeparams.to_owned())
|
||||
},
|
||||
))
|
||||
self.send(T::mode(target, modes))
|
||||
}
|
||||
|
||||
/// Changes the mode of the target by force.
|
||||
|
@ -358,6 +350,7 @@ mod test {
|
|||
use client::data::Config;
|
||||
use client::server::IrcServer;
|
||||
use client::server::test::{get_server_value, test_config};
|
||||
use proto::{ChannelMode, Mode};
|
||||
|
||||
#[test]
|
||||
fn identify() {
|
||||
|
@ -483,14 +476,15 @@ mod test {
|
|||
#[test]
|
||||
fn send_mode_no_modeparams() {
|
||||
let server = IrcServer::from_config(test_config()).unwrap();
|
||||
server.send_mode("#test", "+i", "").unwrap();
|
||||
server.send_mode("#test", &[Mode::Plus(ChannelMode::InviteOnly, None)]).unwrap();
|
||||
assert_eq!(&get_server_value(server)[..], "MODE #test +i\r\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_mode() {
|
||||
let server = IrcServer::from_config(test_config()).unwrap();
|
||||
server.send_mode("#test", "+o", "test").unwrap();
|
||||
server.send_mode("#test", &[Mode::Plus(ChannelMode::Oper, Some("test".to_owned()))])
|
||||
.unwrap();
|
||||
assert_eq!(&get_server_value(server)[..], "MODE #test +o test\r\n");
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,11 @@ error_chain! {
|
|||
display("Failed to parse an IRC subcommand.")
|
||||
}
|
||||
|
||||
ModeParsingFailed {
|
||||
description("Failed to parse a mode correctly.")
|
||||
display("Failed to parse a mode correctly.")
|
||||
}
|
||||
|
||||
/// An error occurred on one of the internal channels of the `IrcServer`.
|
||||
ChannelError {
|
||||
description("An error occured on one of the IrcServer's internal channels.")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Enumeration of all available client commands.
|
||||
use std::ascii::AsciiExt;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use error;
|
||||
use proto::Response;
|
||||
|
@ -20,8 +21,7 @@ pub enum Command {
|
|||
/// OPER name :password
|
||||
OPER(String, String),
|
||||
/// MODE nickname modes
|
||||
/// MODE channel modes [modeparams]
|
||||
MODE(String, String, Option<String>),
|
||||
UserMODE(String, Vec<Mode<UserMode>>),
|
||||
/// SERVICE nickname reserved distribution type reserved :info
|
||||
SERVICE(String, String, String, String, String, String),
|
||||
/// QUIT :comment
|
||||
|
@ -34,8 +34,8 @@ pub enum Command {
|
|||
JOIN(String, Option<String>, Option<String>),
|
||||
/// PART chanlist :[comment]
|
||||
PART(String, Option<String>),
|
||||
// MODE is already defined.
|
||||
// MODE(String, String, Option<String>),
|
||||
/// MODE channel [modes [modeparams]]
|
||||
ChannelMODE(String, Vec<Mode<ChannelMode>>),
|
||||
/// TOPIC channel :[topic]
|
||||
TOPIC(String, Option<String>),
|
||||
/// NAMES [chanlist :[target]]
|
||||
|
@ -191,8 +191,13 @@ impl<'a> From<&'a Command> for String {
|
|||
Command::NICK(ref n) => stringify("NICK", &[], Some(n)),
|
||||
Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*"], Some(r)),
|
||||
Command::OPER(ref u, ref p) => stringify("OPER", &[u], Some(p)),
|
||||
Command::MODE(ref t, ref m, Some(ref p)) => stringify("MODE", &[t, m, p], None),
|
||||
Command::MODE(ref t, ref m, None) => stringify("MODE", &[t, m], None),
|
||||
Command::UserMODE(ref u, ref m) => {
|
||||
format!("MODE {}{}", u, m.iter().fold(String::new(), |mut acc, mode| {
|
||||
acc.push_str(" ");
|
||||
acc.push_str(&mode.to_string());
|
||||
acc
|
||||
}))
|
||||
}
|
||||
Command::SERVICE(ref n, ref r, ref d, ref t, ref re, ref i) => {
|
||||
stringify("SERVICE", &[n, r, d, t, re], Some(i))
|
||||
}
|
||||
|
@ -205,6 +210,13 @@ impl<'a> From<&'a Command> for String {
|
|||
Command::JOIN(ref c, None, None) => stringify("JOIN", &[c], None),
|
||||
Command::PART(ref c, Some(ref m)) => stringify("PART", &[c], Some(m)),
|
||||
Command::PART(ref c, None) => stringify("PART", &[c], None),
|
||||
Command::ChannelMODE(ref u, ref m) => {
|
||||
format!("MODE {}{}", u, m.iter().fold(String::new(), |mut acc, mode| {
|
||||
acc.push_str(" ");
|
||||
acc.push_str(&mode.to_string());
|
||||
acc
|
||||
}))
|
||||
}
|
||||
Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c], Some(t)),
|
||||
Command::TOPIC(ref c, None) => stringify("TOPIC", &[c], None),
|
||||
Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c], Some(t)),
|
||||
|
@ -505,32 +517,14 @@ impl Command {
|
|||
}
|
||||
} else if cmd.eq_ignore_ascii_case("MODE") {
|
||||
match suffix {
|
||||
Some(suffix) => {
|
||||
if args.len() == 2 {
|
||||
Command::MODE(
|
||||
args[0].to_owned(),
|
||||
args[1].to_owned(),
|
||||
Some(suffix.to_owned()),
|
||||
)
|
||||
} else if args.len() == 1 {
|
||||
Command::MODE(args[0].to_owned(), suffix.to_owned(), None)
|
||||
Some(suffix) => raw(cmd, args, Some(suffix)),
|
||||
None => if args[0].is_channel_name() {
|
||||
let arg = args[1..].join(" ");
|
||||
Command::ChannelMODE(args[0].to_owned(), Mode::as_channel_modes(&arg)?)
|
||||
} else {
|
||||
raw(cmd, args, Some(suffix))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if args.len() == 3 {
|
||||
Command::MODE(
|
||||
args[0].to_owned(),
|
||||
args[1].to_owned(),
|
||||
Some(args[2].to_owned()),
|
||||
)
|
||||
} else if args.len() == 2 {
|
||||
Command::MODE(args[0].to_owned(), args[1].to_owned(), None)
|
||||
} else {
|
||||
raw(cmd, args, suffix)
|
||||
}
|
||||
}
|
||||
let arg = args[1..].join(" ");
|
||||
Command::UserMODE(args[0].to_owned(), Mode::as_user_modes(&arg)?)
|
||||
},
|
||||
}
|
||||
} else if cmd.eq_ignore_ascii_case("SERVICE") {
|
||||
match suffix {
|
||||
|
@ -1760,3 +1754,307 @@ 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.
|
||||
fn is_channel_name(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<'a> ChannelExt for &'a str {
|
||||
fn is_channel_name(&self) -> bool {
|
||||
self.starts_with("#") ||
|
||||
self.starts_with("&") ||
|
||||
self.starts_with("+") ||
|
||||
self.starts_with("!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ pub mod message;
|
|||
pub mod response;
|
||||
|
||||
pub use self::caps::{Capability, NegotiationVersion};
|
||||
pub use self::command::{BatchSubCommand, CapSubCommand, Command};
|
||||
pub use self::command::{BatchSubCommand, CapSubCommand, ChannelMode, Command, Mode, UserMode};
|
||||
pub use self::irc::IrcCodec;
|
||||
pub use self::message::Message;
|
||||
pub use self::response::Response;
|
||||
|
|
Loading…
Reference in a new issue