1use std::fmt;
3
4use crate::command::Command;
5use crate::error::MessageParseError;
6
7pub trait ModeType: fmt::Display + fmt::Debug + Clone + PartialEq {
9 fn mode(target: &str, modes: &[Mode<Self>]) -> Command;
11
12 fn takes_arg(&self) -> bool;
14
15 fn from_char(c: char) -> Self;
17}
18
19#[derive(Clone, Debug, PartialEq)]
21pub enum UserMode {
22 Away,
24 Invisible,
26 Wallops,
28 Restricted,
30 Oper,
32 LocalOper,
34 ServerNotices,
36 MaskedHost,
38
39 Unknown(char),
41}
42
43impl ModeType for UserMode {
44 fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
45 Command::UserMODE(target.to_owned(), modes.to_owned())
46 }
47
48 fn takes_arg(&self) -> bool {
49 false
50 }
51
52 fn from_char(c: char) -> UserMode {
53 use self::UserMode::*;
54
55 match c {
56 'a' => Away,
57 'i' => Invisible,
58 'w' => Wallops,
59 'r' => Restricted,
60 'o' => Oper,
61 'O' => LocalOper,
62 's' => ServerNotices,
63 'x' => MaskedHost,
64 _ => Unknown(c),
65 }
66 }
67}
68
69impl fmt::Display for UserMode {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 use self::UserMode::*;
72
73 write!(
74 f,
75 "{}",
76 match *self {
77 Away => 'a',
78 Invisible => 'i',
79 Wallops => 'w',
80 Restricted => 'r',
81 Oper => 'o',
82 LocalOper => 'O',
83 ServerNotices => 's',
84 MaskedHost => 'x',
85 Unknown(c) => c,
86 }
87 )
88 }
89}
90
91#[derive(Clone, Debug, PartialEq)]
93pub enum ChannelMode {
94 Ban,
96 Exception,
98 Limit,
100 InviteOnly,
102 InviteException,
104 Key,
106 Moderated,
108 RegisteredOnly,
110 Secret,
112 ProtectedTopic,
114 NoExternalMessages,
116
117 Founder,
119 Admin,
121 Oper,
123 Halfop,
125 Voice,
127
128 Unknown(char),
130}
131
132impl ModeType for ChannelMode {
133 fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
134 Command::ChannelMODE(target.to_owned(), modes.to_owned())
135 }
136
137 fn takes_arg(&self) -> bool {
138 use self::ChannelMode::*;
139
140 matches!(
141 *self,
142 Ban | Exception
143 | Limit
144 | InviteException
145 | Key
146 | Founder
147 | Admin
148 | Oper
149 | Halfop
150 | Voice
151 )
152 }
153
154 fn from_char(c: char) -> ChannelMode {
155 use self::ChannelMode::*;
156
157 match c {
158 'b' => Ban,
159 'e' => Exception,
160 'l' => Limit,
161 'i' => InviteOnly,
162 'I' => InviteException,
163 'k' => Key,
164 'm' => Moderated,
165 'r' => RegisteredOnly,
166 's' => Secret,
167 't' => ProtectedTopic,
168 'n' => NoExternalMessages,
169 'q' => Founder,
170 'a' => Admin,
171 'o' => Oper,
172 'h' => Halfop,
173 'v' => Voice,
174 _ => Unknown(c),
175 }
176 }
177}
178
179impl fmt::Display for ChannelMode {
180 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181 use self::ChannelMode::*;
182
183 write!(
184 f,
185 "{}",
186 match *self {
187 Ban => 'b',
188 Exception => 'e',
189 Limit => 'l',
190 InviteOnly => 'i',
191 InviteException => 'I',
192 Key => 'k',
193 Moderated => 'm',
194 RegisteredOnly => 'r',
195 Secret => 's',
196 ProtectedTopic => 't',
197 NoExternalMessages => 'n',
198 Founder => 'q',
199 Admin => 'a',
200 Oper => 'o',
201 Halfop => 'h',
202 Voice => 'v',
203 Unknown(c) => c,
204 }
205 )
206 }
207}
208
209#[derive(Clone, Debug, PartialEq)]
211pub enum Mode<T>
212where
213 T: ModeType,
214{
215 Plus(T, Option<String>),
217 Minus(T, Option<String>),
219 NoPrefix(T),
221}
222
223impl<T> Mode<T>
224where
225 T: ModeType,
226{
227 pub fn plus(inner: T, arg: Option<&str>) -> Mode<T> {
229 Mode::Plus(inner, arg.map(|s| s.to_owned()))
230 }
231
232 pub fn minus(inner: T, arg: Option<&str>) -> Mode<T> {
234 Mode::Minus(inner, arg.map(|s| s.to_owned()))
235 }
236
237 pub fn no_prefix(inner: T) -> Mode<T> {
239 Mode::NoPrefix(inner)
240 }
241
242 pub fn flag(&self) -> String {
244 match self {
245 Mode::Plus(mode, _) => format!("+{}", mode),
246 Mode::Minus(mode, _) => format!("-{}", mode),
247 Mode::NoPrefix(mode) => mode.to_string(),
248 }
249 }
250
251 pub fn arg(&self) -> Option<&str> {
254 match self {
255 Mode::Plus(_, arg) | Mode::Minus(_, arg) => arg.as_deref(),
256 _ => None,
257 }
258 }
259}
260
261impl<T> fmt::Display for Mode<T>
262where
263 T: ModeType,
264{
265 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
266 match *self {
267 Mode::Plus(_, Some(ref arg)) | Mode::Minus(_, Some(ref arg)) => {
268 write!(f, "{} {}", self.flag(), arg)
269 }
270 _ => write!(f, "{}", self.flag()),
271 }
272 }
273}
274
275enum PlusMinus {
276 Plus,
277 Minus,
278 NoPrefix,
279}
280
281impl Mode<UserMode> {
283 pub fn as_user_modes(pieces: &[&str]) -> Result<Vec<Mode<UserMode>>, MessageParseError> {
286 parse_modes(pieces)
287 }
288}
289
290impl Mode<ChannelMode> {
292 pub fn as_channel_modes(pieces: &[&str]) -> Result<Vec<Mode<ChannelMode>>, MessageParseError> {
295 parse_modes(pieces)
296 }
297}
298
299fn parse_modes<T>(pieces: &[&str]) -> Result<Vec<Mode<T>>, MessageParseError>
300where
301 T: ModeType,
302{
303 use self::PlusMinus::*;
304
305 let mut res = vec![];
306
307 if let Some((first, rest)) = pieces.split_first() {
308 let mut modes = first.chars();
309 let mut args = rest.iter();
310
311 let mut cur_mod = match modes.next() {
312 Some('+') => Plus,
313 Some('-') => Minus,
314 Some(_) => {
315 modes = first.chars();
317 NoPrefix
318 }
319 None => {
320 return Ok(res);
322 }
323 };
324
325 for c in modes {
326 match c {
327 '+' => cur_mod = Plus,
328 '-' => cur_mod = Minus,
329 _ => {
330 let mode = T::from_char(c);
331 let arg = if mode.takes_arg() {
332 args.next()
334 } else {
335 None
336 };
337 res.push(match cur_mod {
338 Plus => Mode::Plus(mode, arg.map(|s| s.to_string())),
339 Minus => Mode::Minus(mode, arg.map(|s| s.to_string())),
340 NoPrefix => Mode::NoPrefix(mode),
341 })
342 }
343 }
344 }
345
346 } else {
348 };
350
351 Ok(res)
352}
353
354#[cfg(test)]
355mod test {
356 use super::{ChannelMode, Mode};
357 use crate::Command;
358 use crate::Message;
359
360 #[test]
361 fn parse_channel_mode() {
362 let cmd = "MODE #foo +r".parse::<Message>().unwrap().command;
363 assert_eq!(
364 Command::ChannelMODE(
365 "#foo".to_string(),
366 vec![Mode::Plus(ChannelMode::RegisteredOnly, None)]
367 ),
368 cmd
369 );
370 }
371
372 #[test]
373 fn parse_no_mode() {
374 let cmd = "MODE #foo".parse::<Message>().unwrap().command;
375 assert_eq!(Command::ChannelMODE("#foo".to_string(), vec![]), cmd);
376 }
377
378 #[test]
379 fn parse_no_plus() {
380 let cmd = "MODE #foo b".parse::<Message>().unwrap().command;
381 assert_eq!(
382 Command::ChannelMODE("#foo".to_string(), vec![Mode::NoPrefix(ChannelMode::Ban)]),
383 cmd
384 );
385 }
386}