irc_async/proto/
command.rs

1//! Enumeration of all available client commands.
2use std::str::FromStr;
3
4use crate::proto::{ChannelExt, ChannelMode, MessageParseError, Mode, Response, UserMode};
5
6/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
7/// also includes commands from the
8/// [capabilities extension](https://tools.ietf.org/html/draft-mitchell-irc-capabilities-01).
9/// Additionally, this includes some common additional commands from popular IRCds.
10#[derive(Clone, Debug, PartialEq)]
11pub enum Command {
12    // 3.1 Connection Registration
13    /// PASS :password
14    PASS(String),
15    /// NICK :nickname
16    NICK(String),
17    /// USER user mode * :realname
18    USER(String, String, String),
19    /// OPER name :password
20    OPER(String, String),
21    /// MODE nickname modes
22    UserMODE(String, Vec<Mode<UserMode>>),
23    /// SERVICE nickname reserved distribution type reserved :info
24    SERVICE(String, String, String, String, String, String),
25    /// QUIT :comment
26    QUIT(Option<String>),
27    /// SQUIT server :comment
28    SQUIT(String, String),
29
30    // 3.2 Channel operations
31    /// JOIN chanlist [chankeys] :[Real name]
32    JOIN(String, Option<String>, Option<String>),
33    /// PART chanlist :[comment]
34    PART(String, Option<String>),
35    /// MODE channel [modes [modeparams]]
36    ChannelMODE(String, Vec<Mode<ChannelMode>>),
37    /// TOPIC channel :[topic]
38    TOPIC(String, Option<String>),
39    /// NAMES [chanlist :[target]]
40    NAMES(Option<String>, Option<String>),
41    /// LIST [chanlist :[target]]
42    LIST(Option<String>, Option<String>),
43    /// INVITE nickname channel
44    INVITE(String, String),
45    /// KICK chanlist userlist :[comment]
46    KICK(String, String, Option<String>),
47
48    // 3.3 Sending messages
49    /// PRIVMSG msgtarget :message
50    ///
51    /// ## Responding to a `PRIVMSG`
52    ///
53    /// When responding to a message, it is not sufficient to simply copy the message target
54    /// (msgtarget). This will work just fine for responding to messages in channels where the
55    /// target is the same for all participants. However, when the message is sent directly to a
56    /// user, this target will be that client's username, and responding to that same target will
57    /// actually mean sending itself a response. In such a case, you should instead respond to the
58    /// user sending the message as specified in the message prefix. Since this is a common
59    /// pattern, there is a utility function
60    /// [`Message::response_target`](../message/struct.Message.html#method.response_target)
61    /// which is used for this exact purpose.
62    PRIVMSG(String, String),
63    /// NOTICE msgtarget :message
64    ///
65    /// ## Responding to a `NOTICE`
66    ///
67    /// When responding to a notice, it is not sufficient to simply copy the message target
68    /// (msgtarget). This will work just fine for responding to messages in channels where the
69    /// target is the same for all participants. However, when the message is sent directly to a
70    /// user, this target will be that client's username, and responding to that same target will
71    /// actually mean sending itself a response. In such a case, you should instead respond to the
72    /// user sending the message as specified in the message prefix. Since this is a common
73    /// pattern, there is a utility function
74    /// [`Message::response_target`](../message/struct.Message.html#method.response_target)
75    /// which is used for this exact purpose.
76    NOTICE(String, String),
77
78    // 3.4 Server queries and commands
79    /// MOTD :[target]
80    MOTD(Option<String>),
81    /// LUSERS [mask :[target]]
82    LUSERS(Option<String>, Option<String>),
83    /// VERSION :[target]
84    VERSION(Option<String>),
85    /// STATS [query :[target]]
86    STATS(Option<String>, Option<String>),
87    /// LINKS [[remote server] server :mask]
88    LINKS(Option<String>, Option<String>),
89    /// TIME :[target]
90    TIME(Option<String>),
91    /// CONNECT target server port :[remote server]
92    CONNECT(String, String, Option<String>),
93    /// TRACE :[target]
94    TRACE(Option<String>),
95    /// ADMIN :[target]
96    ADMIN(Option<String>),
97    /// INFO :[target]
98    INFO(Option<String>),
99
100    // 3.5 Service Query and Commands
101    /// SERVLIST [mask :[type]]
102    SERVLIST(Option<String>, Option<String>),
103    /// SQUERY servicename text
104    SQUERY(String, String),
105
106    // 3.6 User based queries
107    /// WHO [mask ["o"]]
108    WHO(Option<String>, Option<bool>),
109    /// WHOIS [target] masklist
110    WHOIS(Option<String>, String),
111    /// WHOWAS nicklist [count :[target]]
112    WHOWAS(String, Option<String>, Option<String>),
113
114    // 3.7 Miscellaneous messages
115    /// KILL nickname :comment
116    KILL(String, String),
117    /// PING server1 :[server2]
118    PING(String, Option<String>),
119    /// PONG server :[server2]
120    PONG(String, Option<String>),
121    /// ERROR :message
122    ERROR(String),
123
124    // 4 Optional Features
125    /// AWAY :[message]
126    AWAY(Option<String>),
127    /// REHASH
128    REHASH,
129    /// DIE
130    DIE,
131    /// RESTART
132    RESTART,
133    /// SUMMON user [target :[channel]]
134    SUMMON(String, Option<String>, Option<String>),
135    /// USERS :[target]
136    USERS(Option<String>),
137    /// WALLOPS :Text to be sent
138    WALLOPS(String),
139    /// USERHOST space-separated nicklist
140    USERHOST(Vec<String>),
141    /// ISON space-separated nicklist
142    ISON(Vec<String>),
143
144    // Non-RFC commands from InspIRCd
145    /// SAJOIN nickname channel
146    SAJOIN(String, String),
147    /// SAMODE target modes [modeparams]
148    SAMODE(String, String, Option<String>),
149    /// SANICK old nickname new nickname
150    SANICK(String, String),
151    /// SAPART nickname :comment
152    SAPART(String, String),
153    /// SAQUIT nickname :comment
154    SAQUIT(String, String),
155    /// NICKSERV message
156    NICKSERV(String),
157    /// CHANSERV message
158    CHANSERV(String),
159    /// OPERSERV message
160    OPERSERV(String),
161    /// BOTSERV message
162    BOTSERV(String),
163    /// HOSTSERV message
164    HOSTSERV(String),
165    /// MEMOSERV message
166    MEMOSERV(String),
167
168    // IRCv3 support
169    /// CAP [*] COMMAND [*] :[param]
170    CAP(
171        Option<String>,
172        CapSubCommand,
173        Option<String>,
174        Option<String>,
175    ),
176
177    // IRCv3.1 extensions
178    /// AUTHENTICATE data
179    AUTHENTICATE(String),
180    /// ACCOUNT [account name]
181    ACCOUNT(String),
182    // AWAY is already defined as a send-only message.
183    // AWAY(Option<String>),
184    // JOIN is already defined.
185    // JOIN(String, Option<String>, Option<String>),
186
187    // IRCv3.2 extensions
188    /// METADATA target COMMAND [params] :[param]
189    METADATA(
190        String,
191        Option<MetadataSubCommand>,
192        Option<Vec<String>>,
193        Option<String>,
194    ),
195    /// MONITOR command [nicklist]
196    MONITOR(String, Option<String>),
197    /// BATCH (+/-)reference-tag [type [params]]
198    BATCH(String, Option<BatchSubCommand>, Option<Vec<String>>),
199    /// CHGHOST user host
200    CHGHOST(String, String),
201
202    // Default option.
203    /// An IRC response code with arguments and optional suffix.
204    Response(Response, Vec<String>, Option<String>),
205    /// A raw IRC command unknown to the crate.
206    Raw(String, Vec<String>, Option<String>),
207}
208
209fn stringify(cmd: &str, args: &[&str], suffix: Option<&str>) -> String {
210    let args = args.join(" ");
211    let sp = if args.is_empty() { "" } else { " " };
212    match suffix {
213        Some(suffix) => format!("{}{}{} :{}", cmd, sp, args, suffix),
214        None => format!("{}{}{}", cmd, sp, args),
215    }
216}
217
218impl<'a> From<&'a Command> for String {
219    #[allow(clippy::many_single_char_names)]
220    fn from(cmd: &'a Command) -> String {
221        match *cmd {
222            Command::PASS(ref p) => stringify("PASS", &[], Some(p)),
223            Command::NICK(ref n) => stringify("NICK", &[], Some(n)),
224            Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*"], Some(r)),
225            Command::OPER(ref u, ref p) => stringify("OPER", &[u], Some(p)),
226            Command::UserMODE(ref u, ref m) => format!(
227                "MODE {}{}",
228                u,
229                m.iter().fold(String::new(), |mut acc, mode| {
230                    acc.push_str(" ");
231                    acc.push_str(&mode.to_string());
232                    acc
233                })
234            ),
235            Command::SERVICE(ref n, ref r, ref d, ref t, ref re, ref i) => {
236                stringify("SERVICE", &[n, r, d, t, re], Some(i))
237            }
238            Command::QUIT(Some(ref m)) => stringify("QUIT", &[], Some(m)),
239            Command::QUIT(None) => stringify("QUIT", &[], None),
240            Command::SQUIT(ref s, ref c) => stringify("SQUIT", &[s], Some(c)),
241            Command::JOIN(ref c, Some(ref k), Some(ref n)) => stringify("JOIN", &[c, k], Some(n)),
242            Command::JOIN(ref c, Some(ref k), None) => stringify("JOIN", &[c, k], None),
243            Command::JOIN(ref c, None, Some(ref n)) => stringify("JOIN", &[c], Some(n)),
244            Command::JOIN(ref c, None, None) => stringify("JOIN", &[c], None),
245            Command::PART(ref c, Some(ref m)) => stringify("PART", &[c], Some(m)),
246            Command::PART(ref c, None) => stringify("PART", &[c], None),
247            Command::ChannelMODE(ref u, ref m) => format!(
248                "MODE {}{}",
249                u,
250                m.iter().fold(String::new(), |mut acc, mode| {
251                    acc.push_str(" ");
252                    acc.push_str(&mode.to_string());
253                    acc
254                })
255            ),
256            Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c], Some(t)),
257            Command::TOPIC(ref c, None) => stringify("TOPIC", &[c], None),
258            Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c], Some(t)),
259            Command::NAMES(Some(ref c), None) => stringify("NAMES", &[c], None),
260            Command::NAMES(None, _) => stringify("NAMES", &[], None),
261            Command::LIST(Some(ref c), Some(ref t)) => stringify("LIST", &[c], Some(t)),
262            Command::LIST(Some(ref c), None) => stringify("LIST", &[c], None),
263            Command::LIST(None, _) => stringify("LIST", &[], None),
264            Command::INVITE(ref n, ref c) => stringify("INVITE", &[n, c], None),
265            Command::KICK(ref c, ref n, Some(ref r)) => stringify("KICK", &[c, n], Some(r)),
266            Command::KICK(ref c, ref n, None) => stringify("KICK", &[c, n], None),
267            Command::PRIVMSG(ref t, ref m) => stringify("PRIVMSG", &[t], Some(m)),
268            Command::NOTICE(ref t, ref m) => stringify("NOTICE", &[t], Some(m)),
269            Command::MOTD(Some(ref t)) => stringify("MOTD", &[], Some(t)),
270            Command::MOTD(None) => stringify("MOTD", &[], None),
271            Command::LUSERS(Some(ref m), Some(ref t)) => stringify("LUSERS", &[m], Some(t)),
272            Command::LUSERS(Some(ref m), None) => stringify("LUSERS", &[m], None),
273            Command::LUSERS(None, _) => stringify("LUSERS", &[], None),
274            Command::VERSION(Some(ref t)) => stringify("VERSION", &[], Some(t)),
275            Command::VERSION(None) => stringify("VERSION", &[], None),
276            Command::STATS(Some(ref q), Some(ref t)) => stringify("STATS", &[q], Some(t)),
277            Command::STATS(Some(ref q), None) => stringify("STATS", &[q], None),
278            Command::STATS(None, _) => stringify("STATS", &[], None),
279            Command::LINKS(Some(ref r), Some(ref s)) => stringify("LINKS", &[r], Some(s)),
280            Command::LINKS(None, Some(ref s)) => stringify("LINKS", &[], Some(s)),
281            Command::LINKS(_, None) => stringify("LINKS", &[], None),
282            Command::TIME(Some(ref t)) => stringify("TIME", &[], Some(t)),
283            Command::TIME(None) => stringify("TIME", &[], None),
284            Command::CONNECT(ref t, ref p, Some(ref r)) => stringify("CONNECT", &[t, p], Some(r)),
285            Command::CONNECT(ref t, ref p, None) => stringify("CONNECT", &[t, p], None),
286            Command::TRACE(Some(ref t)) => stringify("TRACE", &[], Some(t)),
287            Command::TRACE(None) => stringify("TRACE", &[], None),
288            Command::ADMIN(Some(ref t)) => stringify("ADMIN", &[], Some(t)),
289            Command::ADMIN(None) => stringify("ADMIN", &[], None),
290            Command::INFO(Some(ref t)) => stringify("INFO", &[], Some(t)),
291            Command::INFO(None) => stringify("INFO", &[], None),
292            Command::SERVLIST(Some(ref m), Some(ref t)) => stringify("SERVLIST", &[m], Some(t)),
293            Command::SERVLIST(Some(ref m), None) => stringify("SERVLIST", &[m], None),
294            Command::SERVLIST(None, _) => stringify("SERVLIST", &[], None),
295            Command::SQUERY(ref s, ref t) => stringify("SQUERY", &[s, t], None),
296            Command::WHO(Some(ref s), Some(true)) => stringify("WHO", &[s, "o"], None),
297            Command::WHO(Some(ref s), _) => stringify("WHO", &[s], None),
298            Command::WHO(None, _) => stringify("WHO", &[], None),
299            Command::WHOIS(Some(ref t), ref m) => stringify("WHOIS", &[t, m], None),
300            Command::WHOIS(None, ref m) => stringify("WHOIS", &[m], None),
301            Command::WHOWAS(ref n, Some(ref c), Some(ref t)) => {
302                stringify("WHOWAS", &[n, c], Some(t))
303            }
304            Command::WHOWAS(ref n, Some(ref c), None) => stringify("WHOWAS", &[n, c], None),
305            Command::WHOWAS(ref n, None, _) => stringify("WHOWAS", &[n], None),
306            Command::KILL(ref n, ref c) => stringify("KILL", &[n], Some(c)),
307            Command::PING(ref s, Some(ref t)) => stringify("PING", &[s], Some(t)),
308            Command::PING(ref s, None) => stringify("PING", &[], Some(s)),
309            Command::PONG(ref s, Some(ref t)) => stringify("PONG", &[s], Some(t)),
310            Command::PONG(ref s, None) => stringify("PONG", &[], Some(s)),
311            Command::ERROR(ref m) => stringify("ERROR", &[], Some(m)),
312            Command::AWAY(Some(ref m)) => stringify("AWAY", &[], Some(m)),
313            Command::AWAY(None) => stringify("AWAY", &[], None),
314            Command::REHASH => stringify("REHASH", &[], None),
315            Command::DIE => stringify("DIE", &[], None),
316            Command::RESTART => stringify("RESTART", &[], None),
317            Command::SUMMON(ref u, Some(ref t), Some(ref c)) => {
318                stringify("SUMMON", &[u, t], Some(c))
319            }
320            Command::SUMMON(ref u, Some(ref t), None) => stringify("SUMMON", &[u, t], None),
321            Command::SUMMON(ref u, None, _) => stringify("SUMMON", &[u], None),
322            Command::USERS(Some(ref t)) => stringify("USERS", &[], Some(t)),
323            Command::USERS(None) => stringify("USERS", &[], None),
324            Command::WALLOPS(ref t) => stringify("WALLOPS", &[], Some(t)),
325            Command::USERHOST(ref u) => stringify(
326                "USERHOST",
327                &u.iter().map(|s| &s[..]).collect::<Vec<_>>(),
328                None,
329            ),
330            Command::ISON(ref u) => {
331                stringify("ISON", &u.iter().map(|s| &s[..]).collect::<Vec<_>>(), None)
332            }
333
334            Command::SAJOIN(ref n, ref c) => stringify("SAJOIN", &[n, c], None),
335            Command::SAMODE(ref t, ref m, Some(ref p)) => stringify("SAMODE", &[t, m, p], None),
336            Command::SAMODE(ref t, ref m, None) => stringify("SAMODE", &[t, m], None),
337            Command::SANICK(ref o, ref n) => stringify("SANICK", &[o, n], None),
338            Command::SAPART(ref c, ref r) => stringify("SAPART", &[c], Some(r)),
339            Command::SAQUIT(ref c, ref r) => stringify("SAQUIT", &[c], Some(r)),
340
341            Command::NICKSERV(ref m) => stringify("NICKSERV", &[m], None),
342            Command::CHANSERV(ref m) => stringify("CHANSERV", &[m], None),
343            Command::OPERSERV(ref m) => stringify("OPERSERV", &[m], None),
344            Command::BOTSERV(ref m) => stringify("BOTSERV", &[m], None),
345            Command::HOSTSERV(ref m) => stringify("HOSTSERV", &[m], None),
346            Command::MEMOSERV(ref m) => stringify("MEMOSERV", &[m], None),
347
348            Command::CAP(None, ref s, None, Some(ref p)) => {
349                stringify("CAP", &[s.to_str()], Some(p))
350            }
351            Command::CAP(None, ref s, None, None) => stringify("CAP", &[s.to_str()], None),
352            Command::CAP(Some(ref k), ref s, None, Some(ref p)) => {
353                stringify("CAP", &[k, s.to_str()], Some(p))
354            }
355            Command::CAP(Some(ref k), ref s, None, None) => {
356                stringify("CAP", &[k, s.to_str()], None)
357            }
358            Command::CAP(None, ref s, Some(ref c), Some(ref p)) => {
359                stringify("CAP", &[s.to_str(), c], Some(p))
360            }
361            Command::CAP(None, ref s, Some(ref c), None) => {
362                stringify("CAP", &[s.to_str(), c], None)
363            }
364            Command::CAP(Some(ref k), ref s, Some(ref c), Some(ref p)) => {
365                stringify("CAP", &[k, s.to_str(), c], Some(p))
366            }
367            Command::CAP(Some(ref k), ref s, Some(ref c), None) => {
368                stringify("CAP", &[k, s.to_str(), c], None)
369            }
370
371            Command::AUTHENTICATE(ref d) => stringify("AUTHENTICATE", &[d], None),
372            Command::ACCOUNT(ref a) => stringify("ACCOUNT", &[a], None),
373
374            Command::METADATA(ref t, Some(ref c), None, Some(ref p)) => {
375                stringify("METADATA", &[&t[..], c.to_str()], Some(p))
376            }
377            Command::METADATA(ref t, Some(ref c), None, None) => {
378                stringify("METADATA", &[&t[..], c.to_str()], None)
379            }
380
381            Command::METADATA(ref t, Some(ref c), Some(ref a), Some(ref p)) => stringify(
382                "METADATA",
383                &vec![t, &c.to_str().to_owned()]
384                    .iter()
385                    .map(|s| &s[..])
386                    .chain(a.iter().map(|s| &s[..]))
387                    .collect::<Vec<_>>(),
388                Some(p),
389            ),
390            Command::METADATA(ref t, Some(ref c), Some(ref a), None) => stringify(
391                "METADATA",
392                &vec![t, &c.to_str().to_owned()]
393                    .iter()
394                    .map(|s| &s[..])
395                    .chain(a.iter().map(|s| &s[..]))
396                    .collect::<Vec<_>>(),
397                None,
398            ),
399            Command::METADATA(ref t, None, None, Some(ref p)) => {
400                stringify("METADATA", &[t], Some(p))
401            }
402            Command::METADATA(ref t, None, None, None) => stringify("METADATA", &[t], None),
403            Command::METADATA(ref t, None, Some(ref a), Some(ref p)) => stringify(
404                "METADATA",
405                &vec![t]
406                    .iter()
407                    .map(|s| &s[..])
408                    .chain(a.iter().map(|s| &s[..]))
409                    .collect::<Vec<_>>(),
410                Some(p),
411            ),
412            Command::METADATA(ref t, None, Some(ref a), None) => stringify(
413                "METADATA",
414                &vec![t]
415                    .iter()
416                    .map(|s| &s[..])
417                    .chain(a.iter().map(|s| &s[..]))
418                    .collect::<Vec<_>>(),
419                None,
420            ),
421            Command::MONITOR(ref c, Some(ref t)) => stringify("MONITOR", &[c, t], None),
422            Command::MONITOR(ref c, None) => stringify("MONITOR", &[c], None),
423            Command::BATCH(ref t, Some(ref c), Some(ref a)) => stringify(
424                "BATCH",
425                &vec![t, &c.to_str().to_owned()]
426                    .iter()
427                    .map(|s| &s[..])
428                    .chain(a.iter().map(|s| &s[..]))
429                    .collect::<Vec<_>>(),
430                None,
431            ),
432            Command::BATCH(ref t, Some(ref c), None) => stringify("BATCH", &[t, c.to_str()], None),
433            Command::BATCH(ref t, None, Some(ref a)) => stringify(
434                "BATCH",
435                &vec![t]
436                    .iter()
437                    .map(|s| &s[..])
438                    .chain(a.iter().map(|s| &s[..]))
439                    .collect::<Vec<_>>(),
440                None,
441            ),
442            Command::BATCH(ref t, None, None) => stringify("BATCH", &[t], None),
443            Command::CHGHOST(ref u, ref h) => stringify("CHGHOST", &[u, h], None),
444
445            Command::Response(ref resp, ref a, Some(ref s)) => stringify(
446                &format!("{:03}", *resp as u16),
447                &a.iter().map(|s| &s[..]).collect::<Vec<_>>(),
448                Some(s),
449            ),
450            Command::Response(ref resp, ref a, None) => stringify(
451                &format!("{:03}", *resp as u16),
452                &a.iter().map(|s| &s[..]).collect::<Vec<_>>(),
453                None,
454            ),
455            Command::Raw(ref c, ref a, Some(ref s)) => {
456                stringify(c, &a.iter().map(|s| &s[..]).collect::<Vec<_>>(), Some(s))
457            }
458            Command::Raw(ref c, ref a, None) => {
459                stringify(c, &a.iter().map(|s| &s[..]).collect::<Vec<_>>(), None)
460            }
461        }
462    }
463}
464
465impl Command {
466    /// Constructs a new Command.
467    #[allow(clippy::complexity)]
468    pub fn new(
469        cmd: &str,
470        args: Vec<&str>,
471        suffix: Option<&str>,
472    ) -> Result<Command, MessageParseError> {
473        Ok(if cmd.eq_ignore_ascii_case("PASS") {
474            match suffix {
475                Some(suffix) => {
476                    if !args.is_empty() {
477                        raw(cmd, args, Some(suffix))
478                    } else {
479                        Command::PASS(suffix.to_owned())
480                    }
481                }
482                None => {
483                    if args.len() != 1 {
484                        raw(cmd, args, suffix)
485                    } else {
486                        Command::PASS(args[0].to_owned())
487                    }
488                }
489            }
490        } else if cmd.eq_ignore_ascii_case("NICK") {
491            match suffix {
492                Some(suffix) => {
493                    if !args.is_empty() {
494                        raw(cmd, args, Some(suffix))
495                    } else {
496                        Command::NICK(suffix.to_owned())
497                    }
498                }
499                None => {
500                    if args.len() != 1 {
501                        raw(cmd, args, suffix)
502                    } else {
503                        Command::NICK(args[0].to_owned())
504                    }
505                }
506            }
507        } else if cmd.eq_ignore_ascii_case("USER") {
508            match suffix {
509                Some(suffix) => {
510                    if args.len() != 3 {
511                        raw(cmd, args, Some(suffix))
512                    } else {
513                        Command::USER(args[0].to_owned(), args[1].to_owned(), suffix.to_owned())
514                    }
515                }
516                None => {
517                    if args.len() != 4 {
518                        raw(cmd, args, suffix)
519                    } else {
520                        Command::USER(args[0].to_owned(), args[1].to_owned(), args[3].to_owned())
521                    }
522                }
523            }
524        } else if cmd.eq_ignore_ascii_case("OPER") {
525            match suffix {
526                Some(suffix) => {
527                    if args.len() != 1 {
528                        raw(cmd, args, Some(suffix))
529                    } else {
530                        Command::OPER(args[0].to_owned(), suffix.to_owned())
531                    }
532                }
533                None => {
534                    if args.len() != 2 {
535                        raw(cmd, args, suffix)
536                    } else {
537                        Command::OPER(args[0].to_owned(), args[1].to_owned())
538                    }
539                }
540            }
541        } else if cmd.eq_ignore_ascii_case("MODE") {
542            match suffix {
543                Some(suffix) => raw(cmd, args, Some(suffix)),
544                None => {
545                    if args[0].is_channel_name() {
546                        let arg = args[1..].join(" ");
547                        Command::ChannelMODE(
548                            args[0].to_owned(),
549                            Mode::from_channel_mode_string(&arg)?,
550                        )
551                    } else {
552                        let arg = args[1..].join(" ");
553                        Command::UserMODE(args[0].to_owned(), Mode::from_user_mode_string(&arg)?)
554                    }
555                }
556            }
557        } else if cmd.eq_ignore_ascii_case("SERVICE") {
558            match suffix {
559                Some(suffix) => {
560                    if args.len() != 5 {
561                        raw(cmd, args, Some(suffix))
562                    } else {
563                        Command::SERVICE(
564                            args[0].to_owned(),
565                            args[1].to_owned(),
566                            args[2].to_owned(),
567                            args[3].to_owned(),
568                            args[4].to_owned(),
569                            suffix.to_owned(),
570                        )
571                    }
572                }
573                None => {
574                    if args.len() != 6 {
575                        raw(cmd, args, suffix)
576                    } else {
577                        Command::SERVICE(
578                            args[0].to_owned(),
579                            args[1].to_owned(),
580                            args[2].to_owned(),
581                            args[3].to_owned(),
582                            args[4].to_owned(),
583                            args[5].to_owned(),
584                        )
585                    }
586                }
587            }
588        } else if cmd.eq_ignore_ascii_case("QUIT") {
589            if !args.is_empty() {
590                raw(cmd, args, suffix)
591            } else {
592                match suffix {
593                    Some(suffix) => Command::QUIT(Some(suffix.to_owned())),
594                    None => Command::QUIT(None),
595                }
596            }
597        } else if cmd.eq_ignore_ascii_case("SQUIT") {
598            match suffix {
599                Some(suffix) => {
600                    if args.len() != 1 {
601                        raw(cmd, args, Some(suffix))
602                    } else {
603                        Command::SQUIT(args[0].to_owned(), suffix.to_owned())
604                    }
605                }
606                None => {
607                    if args.len() != 2 {
608                        raw(cmd, args, suffix)
609                    } else {
610                        Command::SQUIT(args[0].to_owned(), args[1].to_owned())
611                    }
612                }
613            }
614        } else if cmd.eq_ignore_ascii_case("JOIN") {
615            match suffix {
616                Some(suffix) => {
617                    if args.is_empty() {
618                        Command::JOIN(suffix.to_owned(), None, None)
619                    } else if args.len() == 1 {
620                        Command::JOIN(args[0].to_owned(), Some(suffix.to_owned()), None)
621                    } else if args.len() == 2 {
622                        Command::JOIN(
623                            args[0].to_owned(),
624                            Some(args[1].to_owned()),
625                            Some(suffix.to_owned()),
626                        )
627                    } else {
628                        raw(cmd, args, Some(suffix))
629                    }
630                }
631                None => {
632                    if args.len() == 1 {
633                        Command::JOIN(args[0].to_owned(), None, None)
634                    } else if args.len() == 2 {
635                        Command::JOIN(args[0].to_owned(), Some(args[1].to_owned()), None)
636                    } else if args.len() == 3 {
637                        Command::JOIN(
638                            args[0].to_owned(),
639                            Some(args[1].to_owned()),
640                            Some(args[2].to_owned()),
641                        )
642                    } else {
643                        raw(cmd, args, suffix)
644                    }
645                }
646            }
647        } else if cmd.eq_ignore_ascii_case("PART") {
648            match suffix {
649                Some(suffix) => {
650                    if args.is_empty() {
651                        Command::PART(suffix.to_owned(), None)
652                    } else if args.len() == 1 {
653                        Command::PART(args[0].to_owned(), Some(suffix.to_owned()))
654                    } else {
655                        raw(cmd, args, Some(suffix))
656                    }
657                }
658                None => {
659                    if args.len() == 1 {
660                        Command::PART(args[0].to_owned(), None)
661                    } else if args.len() == 2 {
662                        Command::PART(args[0].to_owned(), Some(args[1].to_owned()))
663                    } else {
664                        raw(cmd, args, suffix)
665                    }
666                }
667            }
668        } else if cmd.eq_ignore_ascii_case("TOPIC") {
669            match suffix {
670                Some(suffix) => {
671                    if args.is_empty() {
672                        Command::TOPIC(suffix.to_owned(), None)
673                    } else if args.len() == 1 {
674                        Command::TOPIC(args[0].to_owned(), Some(suffix.to_owned()))
675                    } else {
676                        raw(cmd, args, Some(suffix))
677                    }
678                }
679                None => {
680                    if args.len() == 1 {
681                        Command::TOPIC(args[0].to_owned(), None)
682                    } else if args.len() == 2 {
683                        Command::TOPIC(args[0].to_owned(), Some(args[1].to_owned()))
684                    } else {
685                        raw(cmd, args, suffix)
686                    }
687                }
688            }
689        } else if cmd.eq_ignore_ascii_case("NAMES") {
690            match suffix {
691                Some(suffix) => {
692                    if args.is_empty() {
693                        Command::NAMES(Some(suffix.to_owned()), None)
694                    } else if args.len() == 1 {
695                        Command::NAMES(Some(args[0].to_owned()), Some(suffix.to_owned()))
696                    } else {
697                        raw(cmd, args, Some(suffix))
698                    }
699                }
700                None => {
701                    if args.is_empty() {
702                        Command::NAMES(None, None)
703                    } else if args.len() == 1 {
704                        Command::NAMES(Some(args[0].to_owned()), None)
705                    } else if args.len() == 2 {
706                        Command::NAMES(Some(args[0].to_owned()), Some(args[1].to_owned()))
707                    } else {
708                        raw(cmd, args, suffix)
709                    }
710                }
711            }
712        } else if cmd.eq_ignore_ascii_case("LIST") {
713            match suffix {
714                Some(suffix) => {
715                    if args.is_empty() {
716                        Command::LIST(Some(suffix.to_owned()), None)
717                    } else if args.len() == 1 {
718                        Command::LIST(Some(args[0].to_owned()), Some(suffix.to_owned()))
719                    } else {
720                        raw(cmd, args, Some(suffix))
721                    }
722                }
723                None => {
724                    if args.is_empty() {
725                        Command::LIST(None, None)
726                    } else if args.len() == 1 {
727                        Command::LIST(Some(args[0].to_owned()), None)
728                    } else if args.len() == 2 {
729                        Command::LIST(Some(args[0].to_owned()), Some(args[1].to_owned()))
730                    } else {
731                        raw(cmd, args, suffix)
732                    }
733                }
734            }
735        } else if cmd.eq_ignore_ascii_case("INVITE") {
736            match suffix {
737                Some(suffix) => {
738                    if args.len() != 1 {
739                        raw(cmd, args, Some(suffix))
740                    } else {
741                        Command::INVITE(args[0].to_owned(), suffix.to_owned())
742                    }
743                }
744                None => {
745                    if args.len() != 2 {
746                        raw(cmd, args, suffix)
747                    } else {
748                        Command::INVITE(args[0].to_owned(), args[1].to_owned())
749                    }
750                }
751            }
752        } else if cmd.eq_ignore_ascii_case("KICK") {
753            match suffix {
754                Some(suffix) => {
755                    if args.len() != 2 {
756                        raw(cmd, args, Some(suffix))
757                    } else {
758                        Command::KICK(
759                            args[0].to_owned(),
760                            args[1].to_owned(),
761                            Some(suffix.to_owned()),
762                        )
763                    }
764                }
765                None => {
766                    if args.len() != 2 {
767                        raw(cmd, args, suffix)
768                    } else {
769                        Command::KICK(args[0].to_owned(), args[1].to_owned(), None)
770                    }
771                }
772            }
773        } else if cmd.eq_ignore_ascii_case("PRIVMSG") {
774            match suffix {
775                Some(suffix) => {
776                    if args.len() != 1 {
777                        raw(cmd, args, Some(suffix))
778                    } else {
779                        Command::PRIVMSG(args[0].to_owned(), suffix.to_owned())
780                    }
781                }
782                None => raw(cmd, args, suffix),
783            }
784        } else if cmd.eq_ignore_ascii_case("NOTICE") {
785            match suffix {
786                Some(suffix) => {
787                    if args.len() != 1 {
788                        raw(cmd, args, Some(suffix))
789                    } else {
790                        Command::NOTICE(args[0].to_owned(), suffix.to_owned())
791                    }
792                }
793                None => raw(cmd, args, suffix),
794            }
795        } else if cmd.eq_ignore_ascii_case("MOTD") {
796            if !args.is_empty() {
797                raw(cmd, args, suffix)
798            } else {
799                match suffix {
800                    Some(suffix) => Command::MOTD(Some(suffix.to_owned())),
801                    None => Command::MOTD(None),
802                }
803            }
804        } else if cmd.eq_ignore_ascii_case("LUSERS") {
805            match suffix {
806                Some(suffix) => {
807                    if args.is_empty() {
808                        Command::LUSERS(Some(suffix.to_owned()), None)
809                    } else if args.len() == 1 {
810                        Command::LUSERS(Some(args[0].to_owned()), Some(suffix.to_owned()))
811                    } else {
812                        raw(cmd, args, Some(suffix))
813                    }
814                }
815                None => {
816                    if args.is_empty() {
817                        Command::LUSERS(None, None)
818                    } else if args.len() == 1 {
819                        Command::LUSERS(Some(args[0].to_owned()), None)
820                    } else if args.len() == 2 {
821                        Command::LUSERS(Some(args[0].to_owned()), Some(args[1].to_owned()))
822                    } else {
823                        raw(cmd, args, suffix)
824                    }
825                }
826            }
827        } else if cmd.eq_ignore_ascii_case("VERSION") {
828            if !args.is_empty() {
829                raw(cmd, args, suffix)
830            } else {
831                match suffix {
832                    Some(suffix) => Command::VERSION(Some(suffix.to_owned())),
833                    None => Command::VERSION(None),
834                }
835            }
836        } else if cmd.eq_ignore_ascii_case("STATS") {
837            match suffix {
838                Some(suffix) => {
839                    if args.is_empty() {
840                        Command::STATS(Some(suffix.to_owned()), None)
841                    } else if args.len() == 1 {
842                        Command::STATS(Some(args[0].to_owned()), Some(suffix.to_owned()))
843                    } else {
844                        raw(cmd, args, Some(suffix))
845                    }
846                }
847                None => {
848                    if args.is_empty() {
849                        Command::STATS(None, None)
850                    } else if args.len() == 1 {
851                        Command::STATS(Some(args[0].to_owned()), None)
852                    } else if args.len() == 2 {
853                        Command::STATS(Some(args[0].to_owned()), Some(args[1].to_owned()))
854                    } else {
855                        raw(cmd, args, suffix)
856                    }
857                }
858            }
859        } else if cmd.eq_ignore_ascii_case("LINKS") {
860            match suffix {
861                Some(suffix) => {
862                    if args.is_empty() {
863                        Command::LINKS(None, Some(suffix.to_owned()))
864                    } else if args.len() == 1 {
865                        Command::LINKS(Some(args[0].to_owned()), Some(suffix.to_owned()))
866                    } else {
867                        raw(cmd, args, Some(suffix))
868                    }
869                }
870                None => {
871                    if args.is_empty() {
872                        Command::LINKS(None, None)
873                    } else {
874                        raw(cmd, args, suffix)
875                    }
876                }
877            }
878        } else if cmd.eq_ignore_ascii_case("TIME") {
879            if !args.is_empty() {
880                raw(cmd, args, suffix)
881            } else {
882                match suffix {
883                    Some(suffix) => Command::TIME(Some(suffix.to_owned())),
884                    None => Command::TIME(None),
885                }
886            }
887        } else if cmd.eq_ignore_ascii_case("CONNECT") {
888            match suffix {
889                Some(suffix) => {
890                    if args.len() != 2 {
891                        raw(cmd, args, Some(suffix))
892                    } else {
893                        Command::CONNECT(
894                            args[0].to_owned(),
895                            args[1].to_owned(),
896                            Some(suffix.to_owned()),
897                        )
898                    }
899                }
900                None => {
901                    if args.len() != 2 {
902                        raw(cmd, args, suffix)
903                    } else {
904                        Command::CONNECT(args[0].to_owned(), args[1].to_owned(), None)
905                    }
906                }
907            }
908        } else if cmd.eq_ignore_ascii_case("TRACE") {
909            if !args.is_empty() {
910                raw(cmd, args, suffix)
911            } else {
912                match suffix {
913                    Some(suffix) => Command::TRACE(Some(suffix.to_owned())),
914                    None => Command::TRACE(None),
915                }
916            }
917        } else if cmd.eq_ignore_ascii_case("ADMIN") {
918            if !args.is_empty() {
919                raw(cmd, args, suffix)
920            } else {
921                match suffix {
922                    Some(suffix) => Command::ADMIN(Some(suffix.to_owned())),
923                    None => Command::ADMIN(None),
924                }
925            }
926        } else if cmd.eq_ignore_ascii_case("INFO") {
927            if !args.is_empty() {
928                raw(cmd, args, suffix)
929            } else {
930                match suffix {
931                    Some(suffix) => Command::INFO(Some(suffix.to_owned())),
932                    None => Command::INFO(None),
933                }
934            }
935        } else if cmd.eq_ignore_ascii_case("SERVLIST") {
936            match suffix {
937                Some(suffix) => {
938                    if args.is_empty() {
939                        Command::SERVLIST(Some(suffix.to_owned()), None)
940                    } else if args.len() == 1 {
941                        Command::SERVLIST(Some(args[0].to_owned()), Some(suffix.to_owned()))
942                    } else {
943                        raw(cmd, args, Some(suffix))
944                    }
945                }
946                None => {
947                    if args.is_empty() {
948                        Command::SERVLIST(None, None)
949                    } else if args.len() == 1 {
950                        Command::SERVLIST(Some(args[0].to_owned()), None)
951                    } else if args.len() == 2 {
952                        Command::SERVLIST(Some(args[0].to_owned()), Some(args[1].to_owned()))
953                    } else {
954                        raw(cmd, args, suffix)
955                    }
956                }
957            }
958        } else if cmd.eq_ignore_ascii_case("SQUERY") {
959            match suffix {
960                Some(suffix) => {
961                    if args.len() != 1 {
962                        raw(cmd, args, Some(suffix))
963                    } else {
964                        Command::SQUERY(args[0].to_owned(), suffix.to_owned())
965                    }
966                }
967                None => {
968                    if args.len() != 2 {
969                        raw(cmd, args, suffix)
970                    } else {
971                        Command::SQUERY(args[0].to_owned(), args[1].to_owned())
972                    }
973                }
974            }
975        } else if cmd.eq_ignore_ascii_case("WHO") {
976            match suffix {
977                Some(suffix) => {
978                    if args.is_empty() {
979                        Command::WHO(Some(suffix.to_owned()), None)
980                    } else if args.len() == 1 {
981                        Command::WHO(Some(args[0].to_owned()), Some(&suffix[..] == "o"))
982                    } else {
983                        raw(cmd, args, Some(suffix))
984                    }
985                }
986                None => {
987                    if args.is_empty() {
988                        Command::WHO(None, None)
989                    } else if args.len() == 1 {
990                        Command::WHO(Some(args[0].to_owned()), None)
991                    } else if args.len() == 2 {
992                        Command::WHO(Some(args[0].to_owned()), Some(&args[1][..] == "o"))
993                    } else {
994                        raw(cmd, args, suffix)
995                    }
996                }
997            }
998        } else if cmd.eq_ignore_ascii_case("WHOIS") {
999            match suffix {
1000                Some(suffix) => {
1001                    if args.is_empty() {
1002                        Command::WHOIS(None, suffix.to_owned())
1003                    } else if args.len() == 1 {
1004                        Command::WHOIS(Some(args[0].to_owned()), suffix.to_owned())
1005                    } else {
1006                        raw(cmd, args, Some(suffix))
1007                    }
1008                }
1009                None => {
1010                    if args.len() == 1 {
1011                        Command::WHOIS(None, args[0].to_owned())
1012                    } else if args.len() == 2 {
1013                        Command::WHOIS(Some(args[0].to_owned()), args[1].to_owned())
1014                    } else {
1015                        raw(cmd, args, suffix)
1016                    }
1017                }
1018            }
1019        } else if cmd.eq_ignore_ascii_case("WHOWAS") {
1020            match suffix {
1021                Some(suffix) => {
1022                    if args.is_empty() {
1023                        Command::WHOWAS(suffix.to_owned(), None, None)
1024                    } else if args.len() == 1 {
1025                        Command::WHOWAS(args[0].to_owned(), None, Some(suffix.to_owned()))
1026                    } else if args.len() == 2 {
1027                        Command::WHOWAS(
1028                            args[0].to_owned(),
1029                            Some(args[1].to_owned()),
1030                            Some(suffix.to_owned()),
1031                        )
1032                    } else {
1033                        raw(cmd, args, Some(suffix))
1034                    }
1035                }
1036                None => {
1037                    if args.len() == 1 {
1038                        Command::WHOWAS(args[0].to_owned(), None, None)
1039                    } else if args.len() == 2 {
1040                        Command::WHOWAS(args[0].to_owned(), None, Some(args[1].to_owned()))
1041                    } else if args.len() == 3 {
1042                        Command::WHOWAS(
1043                            args[0].to_owned(),
1044                            Some(args[1].to_owned()),
1045                            Some(args[2].to_owned()),
1046                        )
1047                    } else {
1048                        raw(cmd, args, suffix)
1049                    }
1050                }
1051            }
1052        } else if cmd.eq_ignore_ascii_case("KILL") {
1053            match suffix {
1054                Some(suffix) => {
1055                    if args.len() != 1 {
1056                        raw(cmd, args, Some(suffix))
1057                    } else {
1058                        Command::KILL(args[0].to_owned(), suffix.to_owned())
1059                    }
1060                }
1061                None => {
1062                    if args.len() != 2 {
1063                        raw(cmd, args, suffix)
1064                    } else {
1065                        Command::KILL(args[0].to_owned(), args[1].to_owned())
1066                    }
1067                }
1068            }
1069        } else if cmd.eq_ignore_ascii_case("PING") {
1070            match suffix {
1071                Some(suffix) => {
1072                    if args.is_empty() {
1073                        Command::PING(suffix.to_owned(), None)
1074                    } else if args.len() == 1 {
1075                        Command::PING(args[0].to_owned(), Some(suffix.to_owned()))
1076                    } else {
1077                        raw(cmd, args, Some(suffix))
1078                    }
1079                }
1080                None => {
1081                    if args.len() == 1 {
1082                        Command::PING(args[0].to_owned(), None)
1083                    } else if args.len() == 2 {
1084                        Command::PING(args[0].to_owned(), Some(args[1].to_owned()))
1085                    } else {
1086                        raw(cmd, args, suffix)
1087                    }
1088                }
1089            }
1090        } else if cmd.eq_ignore_ascii_case("PONG") {
1091            match suffix {
1092                Some(suffix) => {
1093                    if args.is_empty() {
1094                        Command::PONG(suffix.to_owned(), None)
1095                    } else if args.len() == 1 {
1096                        Command::PONG(args[0].to_owned(), Some(suffix.to_owned()))
1097                    } else {
1098                        raw(cmd, args, Some(suffix))
1099                    }
1100                }
1101                None => {
1102                    if args.len() == 1 {
1103                        Command::PONG(args[0].to_owned(), None)
1104                    } else if args.len() == 2 {
1105                        Command::PONG(args[0].to_owned(), Some(args[1].to_owned()))
1106                    } else {
1107                        raw(cmd, args, suffix)
1108                    }
1109                }
1110            }
1111        } else if cmd.eq_ignore_ascii_case("ERROR") {
1112            match suffix {
1113                Some(suffix) => {
1114                    if args.is_empty() {
1115                        Command::ERROR(suffix.to_owned())
1116                    } else {
1117                        raw(cmd, args, Some(suffix))
1118                    }
1119                }
1120                None => raw(cmd, args, suffix),
1121            }
1122        } else if cmd.eq_ignore_ascii_case("AWAY") {
1123            match suffix {
1124                Some(suffix) => {
1125                    if args.is_empty() {
1126                        Command::AWAY(Some(suffix.to_owned()))
1127                    } else {
1128                        raw(cmd, args, Some(suffix))
1129                    }
1130                }
1131                None => raw(cmd, args, suffix),
1132            }
1133        } else if cmd.eq_ignore_ascii_case("REHASH") {
1134            if args.is_empty() {
1135                Command::REHASH
1136            } else {
1137                raw(cmd, args, suffix)
1138            }
1139        } else if cmd.eq_ignore_ascii_case("DIE") {
1140            if args.is_empty() {
1141                Command::DIE
1142            } else {
1143                raw(cmd, args, suffix)
1144            }
1145        } else if cmd.eq_ignore_ascii_case("RESTART") {
1146            if args.is_empty() {
1147                Command::RESTART
1148            } else {
1149                raw(cmd, args, suffix)
1150            }
1151        } else if cmd.eq_ignore_ascii_case("SUMMON") {
1152            match suffix {
1153                Some(suffix) => {
1154                    if args.is_empty() {
1155                        Command::SUMMON(suffix.to_owned(), None, None)
1156                    } else if args.len() == 1 {
1157                        Command::SUMMON(args[0].to_owned(), Some(suffix.to_owned()), None)
1158                    } else if args.len() == 2 {
1159                        Command::SUMMON(
1160                            args[0].to_owned(),
1161                            Some(args[1].to_owned()),
1162                            Some(suffix.to_owned()),
1163                        )
1164                    } else {
1165                        raw(cmd, args, Some(suffix))
1166                    }
1167                }
1168                None => {
1169                    if args.len() == 1 {
1170                        Command::SUMMON(args[0].to_owned(), None, None)
1171                    } else if args.len() == 2 {
1172                        Command::SUMMON(args[0].to_owned(), Some(args[1].to_owned()), None)
1173                    } else if args.len() == 3 {
1174                        Command::SUMMON(
1175                            args[0].to_owned(),
1176                            Some(args[1].to_owned()),
1177                            Some(args[2].to_owned()),
1178                        )
1179                    } else {
1180                        raw(cmd, args, suffix)
1181                    }
1182                }
1183            }
1184        } else if cmd.eq_ignore_ascii_case("USERS") {
1185            match suffix {
1186                Some(suffix) => {
1187                    if !args.is_empty() {
1188                        raw(cmd, args, Some(suffix))
1189                    } else {
1190                        Command::USERS(Some(suffix.to_owned()))
1191                    }
1192                }
1193                None => {
1194                    if args.len() != 1 {
1195                        raw(cmd, args, suffix)
1196                    } else {
1197                        Command::USERS(Some(args[0].to_owned()))
1198                    }
1199                }
1200            }
1201        } else if cmd.eq_ignore_ascii_case("WALLOPS") {
1202            match suffix {
1203                Some(suffix) => {
1204                    if !args.is_empty() {
1205                        raw(cmd, args, Some(suffix))
1206                    } else {
1207                        Command::WALLOPS(suffix.to_owned())
1208                    }
1209                }
1210                None => {
1211                    if args.len() != 1 {
1212                        raw(cmd, args, suffix)
1213                    } else {
1214                        Command::WALLOPS(args[0].to_owned())
1215                    }
1216                }
1217            }
1218        } else if cmd.eq_ignore_ascii_case("USERHOST") {
1219            if suffix.is_none() {
1220                Command::USERHOST(args.into_iter().map(|s| s.to_owned()).collect())
1221            } else {
1222                raw(cmd, args, suffix)
1223            }
1224        } else if cmd.eq_ignore_ascii_case("ISON") {
1225            if suffix.is_none() {
1226                Command::USERHOST(args.into_iter().map(|s| s.to_owned()).collect())
1227            } else {
1228                raw(cmd, args, suffix)
1229            }
1230        } else if cmd.eq_ignore_ascii_case("SAJOIN") {
1231            match suffix {
1232                Some(suffix) => {
1233                    if args.len() != 1 {
1234                        raw(cmd, args, Some(suffix))
1235                    } else {
1236                        Command::SAJOIN(args[0].to_owned(), suffix.to_owned())
1237                    }
1238                }
1239                None => {
1240                    if args.len() != 2 {
1241                        raw(cmd, args, suffix)
1242                    } else {
1243                        Command::SAJOIN(args[0].to_owned(), args[1].to_owned())
1244                    }
1245                }
1246            }
1247        } else if cmd.eq_ignore_ascii_case("SAMODE") {
1248            match suffix {
1249                Some(suffix) => {
1250                    if args.len() == 1 {
1251                        Command::SAMODE(args[0].to_owned(), suffix.to_owned(), None)
1252                    } else if args.len() == 2 {
1253                        Command::SAMODE(
1254                            args[0].to_owned(),
1255                            args[1].to_owned(),
1256                            Some(suffix.to_owned()),
1257                        )
1258                    } else {
1259                        raw(cmd, args, Some(suffix))
1260                    }
1261                }
1262                None => {
1263                    if args.len() == 2 {
1264                        Command::SAMODE(args[0].to_owned(), args[1].to_owned(), None)
1265                    } else if args.len() == 3 {
1266                        Command::SAMODE(
1267                            args[0].to_owned(),
1268                            args[1].to_owned(),
1269                            Some(args[2].to_owned()),
1270                        )
1271                    } else {
1272                        raw(cmd, args, suffix)
1273                    }
1274                }
1275            }
1276        } else if cmd.eq_ignore_ascii_case("SANICK") {
1277            match suffix {
1278                Some(suffix) => {
1279                    if args.len() != 1 {
1280                        raw(cmd, args, Some(suffix))
1281                    } else {
1282                        Command::SANICK(args[0].to_owned(), suffix.to_owned())
1283                    }
1284                }
1285                None => {
1286                    if args.len() != 2 {
1287                        raw(cmd, args, suffix)
1288                    } else {
1289                        Command::SANICK(args[0].to_owned(), args[1].to_owned())
1290                    }
1291                }
1292            }
1293        } else if cmd.eq_ignore_ascii_case("SAPART") {
1294            match suffix {
1295                Some(suffix) => {
1296                    if args.len() != 1 {
1297                        raw(cmd, args, Some(suffix))
1298                    } else {
1299                        Command::SAPART(args[0].to_owned(), suffix.to_owned())
1300                    }
1301                }
1302                None => {
1303                    if args.len() != 2 {
1304                        raw(cmd, args, suffix)
1305                    } else {
1306                        Command::SAPART(args[0].to_owned(), args[1].to_owned())
1307                    }
1308                }
1309            }
1310        } else if cmd.eq_ignore_ascii_case("SAQUIT") {
1311            match suffix {
1312                Some(suffix) => {
1313                    if args.len() != 1 {
1314                        raw(cmd, args, Some(suffix))
1315                    } else {
1316                        Command::SAQUIT(args[0].to_owned(), suffix.to_owned())
1317                    }
1318                }
1319                None => {
1320                    if args.len() != 2 {
1321                        raw(cmd, args, suffix)
1322                    } else {
1323                        Command::SAQUIT(args[0].to_owned(), args[1].to_owned())
1324                    }
1325                }
1326            }
1327        } else if cmd.eq_ignore_ascii_case("NICKSERV") {
1328            match suffix {
1329                Some(suffix) => {
1330                    if !args.is_empty() {
1331                        raw(cmd, args, Some(suffix))
1332                    } else {
1333                        Command::NICKSERV(suffix.to_owned())
1334                    }
1335                }
1336                None => {
1337                    if args.len() != 1 {
1338                        raw(cmd, args, suffix)
1339                    } else {
1340                        Command::NICKSERV(args[0].to_owned())
1341                    }
1342                }
1343            }
1344        } else if cmd.eq_ignore_ascii_case("CHANSERV") {
1345            match suffix {
1346                Some(suffix) => {
1347                    if !args.is_empty() {
1348                        raw(cmd, args, Some(suffix))
1349                    } else {
1350                        Command::CHANSERV(suffix.to_owned())
1351                    }
1352                }
1353                None => {
1354                    if args.len() != 1 {
1355                        raw(cmd, args, suffix)
1356                    } else {
1357                        Command::CHANSERV(args[0].to_owned())
1358                    }
1359                }
1360            }
1361        } else if cmd.eq_ignore_ascii_case("OPERSERV") {
1362            match suffix {
1363                Some(suffix) => {
1364                    if !args.is_empty() {
1365                        raw(cmd, args, Some(suffix))
1366                    } else {
1367                        Command::OPERSERV(suffix.to_owned())
1368                    }
1369                }
1370                None => {
1371                    if args.len() != 1 {
1372                        raw(cmd, args, suffix)
1373                    } else {
1374                        Command::OPERSERV(args[0].to_owned())
1375                    }
1376                }
1377            }
1378        } else if cmd.eq_ignore_ascii_case("BOTSERV") {
1379            match suffix {
1380                Some(suffix) => {
1381                    if !args.is_empty() {
1382                        raw(cmd, args, Some(suffix))
1383                    } else {
1384                        Command::BOTSERV(suffix.to_owned())
1385                    }
1386                }
1387                None => {
1388                    if args.len() != 1 {
1389                        raw(cmd, args, suffix)
1390                    } else {
1391                        Command::BOTSERV(args[0].to_owned())
1392                    }
1393                }
1394            }
1395        } else if cmd.eq_ignore_ascii_case("HOSTSERV") {
1396            match suffix {
1397                Some(suffix) => {
1398                    if !args.is_empty() {
1399                        raw(cmd, args, Some(suffix))
1400                    } else {
1401                        Command::HOSTSERV(suffix.to_owned())
1402                    }
1403                }
1404                None => {
1405                    if args.len() != 1 {
1406                        raw(cmd, args, suffix)
1407                    } else {
1408                        Command::HOSTSERV(args[0].to_owned())
1409                    }
1410                }
1411            }
1412        } else if cmd.eq_ignore_ascii_case("MEMOSERV") {
1413            match suffix {
1414                Some(suffix) => {
1415                    if !args.is_empty() {
1416                        raw(cmd, args, Some(suffix))
1417                    } else {
1418                        Command::MEMOSERV(suffix.to_owned())
1419                    }
1420                }
1421                None => {
1422                    if args.len() != 1 {
1423                        raw(cmd, args, suffix)
1424                    } else {
1425                        Command::MEMOSERV(args[0].to_owned())
1426                    }
1427                }
1428            }
1429        } else if cmd.eq_ignore_ascii_case("CAP") {
1430            if args.len() == 1 {
1431                if let Ok(cmd) = args[0].parse() {
1432                    match suffix {
1433                        Some(suffix) => Command::CAP(None, cmd, None, Some(suffix.to_owned())),
1434                        None => Command::CAP(None, cmd, None, None),
1435                    }
1436                } else {
1437                    raw(cmd, args, suffix)
1438                }
1439            } else if args.len() == 2 {
1440                if let Ok(cmd) = args[0].parse() {
1441                    match suffix {
1442                        Some(suffix) => Command::CAP(
1443                            None,
1444                            cmd,
1445                            Some(args[1].to_owned()),
1446                            Some(suffix.to_owned()),
1447                        ),
1448                        None => Command::CAP(None, cmd, Some(args[1].to_owned()), None),
1449                    }
1450                } else if let Ok(cmd) = args[1].parse() {
1451                    match suffix {
1452                        Some(suffix) => Command::CAP(
1453                            Some(args[0].to_owned()),
1454                            cmd,
1455                            None,
1456                            Some(suffix.to_owned()),
1457                        ),
1458                        None => Command::CAP(Some(args[0].to_owned()), cmd, None, None),
1459                    }
1460                } else {
1461                    raw(cmd, args, suffix)
1462                }
1463            } else if args.len() == 3 {
1464                if let Ok(cmd) = args[1].parse() {
1465                    match suffix {
1466                        Some(suffix) => Command::CAP(
1467                            Some(args[0].to_owned()),
1468                            cmd,
1469                            Some(args[2].to_owned()),
1470                            Some(suffix.to_owned()),
1471                        ),
1472                        None => Command::CAP(
1473                            Some(args[0].to_owned()),
1474                            cmd,
1475                            Some(args[2].to_owned()),
1476                            None,
1477                        ),
1478                    }
1479                } else {
1480                    raw(cmd, args, suffix)
1481                }
1482            } else {
1483                raw(cmd, args, suffix)
1484            }
1485        } else if cmd.eq_ignore_ascii_case("AUTHENTICATE") {
1486            match suffix {
1487                Some(suffix) => {
1488                    if args.is_empty() {
1489                        Command::AUTHENTICATE(suffix.to_owned())
1490                    } else {
1491                        raw(cmd, args, Some(suffix))
1492                    }
1493                }
1494                None => {
1495                    if args.len() == 1 {
1496                        Command::AUTHENTICATE(args[0].to_owned())
1497                    } else {
1498                        raw(cmd, args, suffix)
1499                    }
1500                }
1501            }
1502        } else if cmd.eq_ignore_ascii_case("ACCOUNT") {
1503            match suffix {
1504                Some(suffix) => {
1505                    if args.is_empty() {
1506                        Command::ACCOUNT(suffix.to_owned())
1507                    } else {
1508                        raw(cmd, args, Some(suffix))
1509                    }
1510                }
1511                None => {
1512                    if args.len() == 1 {
1513                        Command::ACCOUNT(args[0].to_owned())
1514                    } else {
1515                        raw(cmd, args, suffix)
1516                    }
1517                }
1518            }
1519        } else if cmd.eq_ignore_ascii_case("METADATA") {
1520            if args.len() == 2 {
1521                match suffix {
1522                    Some(_) => raw(cmd, args, suffix),
1523                    None => match args[1].parse() {
1524                        Ok(c) => Command::METADATA(args[0].to_owned(), Some(c), None, None),
1525                        Err(_) => raw(cmd, args, suffix),
1526                    },
1527                }
1528            } else if args.len() > 2 {
1529                match args[1].parse() {
1530                    Ok(c) => Command::METADATA(
1531                        args[0].to_owned(),
1532                        Some(c),
1533                        Some(args.into_iter().skip(1).map(|s| s.to_owned()).collect()),
1534                        suffix.map(|s| s.to_owned()),
1535                    ),
1536                    Err(_) => {
1537                        if args.len() == 3 && suffix.is_some() {
1538                            Command::METADATA(
1539                                args[0].to_owned(),
1540                                None,
1541                                Some(args.into_iter().skip(1).map(|s| s.to_owned()).collect()),
1542                                suffix.map(|s| s.to_owned()),
1543                            )
1544                        } else {
1545                            raw(cmd, args, suffix)
1546                        }
1547                    }
1548                }
1549            } else {
1550                raw(cmd, args, suffix)
1551            }
1552        } else if cmd.eq_ignore_ascii_case("MONITOR") {
1553            if args.len() == 1 {
1554                Command::MONITOR(args[0].to_owned(), suffix.map(|s| s.to_owned()))
1555            } else {
1556                raw(cmd, args, suffix)
1557            }
1558        } else if cmd.eq_ignore_ascii_case("BATCH") {
1559            match suffix {
1560                Some(suffix) => {
1561                    if args.is_empty() {
1562                        Command::BATCH(suffix.to_owned(), None, None)
1563                    } else if args.len() == 1 {
1564                        Command::BATCH(args[0].to_owned(), Some(suffix.parse().unwrap()), None)
1565                    } else if args.len() > 1 {
1566                        Command::BATCH(
1567                            args[0].to_owned(),
1568                            Some(args[1].parse().unwrap()),
1569                            Some(
1570                                vec![suffix.to_owned()]
1571                                    .into_iter()
1572                                    .chain(args.into_iter().skip(2).map(|s| s.to_owned()))
1573                                    .collect(),
1574                            ),
1575                        )
1576                    } else {
1577                        raw(cmd, args, Some(suffix))
1578                    }
1579                }
1580                None => {
1581                    if args.len() == 1 {
1582                        Command::BATCH(args[0].to_owned(), None, None)
1583                    } else if args.len() == 2 {
1584                        Command::BATCH(args[0].to_owned(), Some(args[1].parse().unwrap()), None)
1585                    } else if args.len() > 2 {
1586                        Command::BATCH(
1587                            args[0].to_owned(),
1588                            Some(args[1].parse().unwrap()),
1589                            Some(args.iter().skip(2).map(|&s| s.to_owned()).collect()),
1590                        )
1591                    } else {
1592                        raw(cmd, args, suffix)
1593                    }
1594                }
1595            }
1596        } else if cmd.eq_ignore_ascii_case("CHGHOST") {
1597            match suffix {
1598                Some(suffix) => {
1599                    if args.len() == 1 {
1600                        Command::CHGHOST(args[0].to_owned(), suffix.to_owned())
1601                    } else {
1602                        raw(cmd, args, Some(suffix))
1603                    }
1604                }
1605                None => {
1606                    if args.len() == 2 {
1607                        Command::CHGHOST(args[0].to_owned(), args[1].to_owned())
1608                    } else {
1609                        raw(cmd, args, suffix)
1610                    }
1611                }
1612            }
1613        } else if let Ok(resp) = cmd.parse() {
1614            Command::Response(
1615                resp,
1616                args.into_iter().map(|s| s.to_owned()).collect(),
1617                suffix.map(|s| s.to_owned()),
1618            )
1619        } else {
1620            raw(cmd, args, suffix)
1621        })
1622    }
1623}
1624
1625/// Makes a raw message from the specified command, arguments, and suffix.
1626fn raw(cmd: &str, args: Vec<&str>, suffix: Option<&str>) -> Command {
1627    Command::Raw(
1628        cmd.to_owned(),
1629        args.into_iter().map(|s| s.to_owned()).collect(),
1630        suffix.map(|s| s.to_owned()),
1631    )
1632}
1633
1634/// A list of all of the subcommands for the capabilities extension.
1635#[derive(Clone, Copy, Debug, PartialEq)]
1636pub enum CapSubCommand {
1637    /// Requests a list of the server's capabilities.
1638    LS,
1639    /// Requests a list of the server's capabilities.
1640    LIST,
1641    /// Requests specific capabilities blindly.
1642    REQ,
1643    /// Acknowledges capabilities.
1644    ACK,
1645    /// Does not acknowledge certain capabilities.
1646    NAK,
1647    /// Ends the capability negotiation before registration.
1648    END,
1649    /// Signals that new capabilities are now being offered.
1650    NEW,
1651    /// Signasl that the specified capabilities are cancelled and no longer available.
1652    DEL,
1653}
1654
1655impl CapSubCommand {
1656    /// Gets the string that corresponds to this subcommand.
1657    pub fn to_str(&self) -> &str {
1658        match *self {
1659            CapSubCommand::LS => "LS",
1660            CapSubCommand::LIST => "LIST",
1661            CapSubCommand::REQ => "REQ",
1662            CapSubCommand::ACK => "ACK",
1663            CapSubCommand::NAK => "NAK",
1664            CapSubCommand::END => "END",
1665            CapSubCommand::NEW => "NEW",
1666            CapSubCommand::DEL => "DEL",
1667        }
1668    }
1669}
1670
1671impl FromStr for CapSubCommand {
1672    type Err = MessageParseError;
1673
1674    fn from_str(s: &str) -> Result<CapSubCommand, Self::Err> {
1675        if s.eq_ignore_ascii_case("LS") {
1676            Ok(CapSubCommand::LS)
1677        } else if s.eq_ignore_ascii_case("LIST") {
1678            Ok(CapSubCommand::LIST)
1679        } else if s.eq_ignore_ascii_case("REQ") {
1680            Ok(CapSubCommand::REQ)
1681        } else if s.eq_ignore_ascii_case("ACK") {
1682            Ok(CapSubCommand::ACK)
1683        } else if s.eq_ignore_ascii_case("NAK") {
1684            Ok(CapSubCommand::NAK)
1685        } else if s.eq_ignore_ascii_case("END") {
1686            Ok(CapSubCommand::END)
1687        } else if s.eq_ignore_ascii_case("NEW") {
1688            Ok(CapSubCommand::NEW)
1689        } else if s.eq_ignore_ascii_case("DEL") {
1690            Ok(CapSubCommand::DEL)
1691        } else {
1692            Err(MessageParseError::InvalidSubcommand {
1693                cmd: "CAP",
1694                sub: s.to_owned(),
1695            })
1696        }
1697    }
1698}
1699
1700/// A list of all the subcommands for the
1701/// [metadata extension](http://ircv3.net/specs/core/metadata-3.2.html).
1702#[derive(Clone, Copy, Debug, PartialEq)]
1703pub enum MetadataSubCommand {
1704    /// Looks up the value for some keys.
1705    GET,
1706    /// Lists all of the metadata keys and values.
1707    LIST,
1708    /// Sets the value for some key.
1709    SET,
1710    /// Removes all metadata.
1711    CLEAR,
1712}
1713
1714impl MetadataSubCommand {
1715    /// Gets the string that corresponds to this subcommand.
1716    pub fn to_str(&self) -> &str {
1717        match *self {
1718            MetadataSubCommand::GET => "GET",
1719            MetadataSubCommand::LIST => "LIST",
1720            MetadataSubCommand::SET => "SET",
1721            MetadataSubCommand::CLEAR => "CLEAR",
1722        }
1723    }
1724}
1725
1726impl FromStr for MetadataSubCommand {
1727    type Err = MessageParseError;
1728
1729    fn from_str(s: &str) -> Result<MetadataSubCommand, Self::Err> {
1730        if s.eq_ignore_ascii_case("GET") {
1731            Ok(MetadataSubCommand::GET)
1732        } else if s.eq_ignore_ascii_case("LIST") {
1733            Ok(MetadataSubCommand::LIST)
1734        } else if s.eq_ignore_ascii_case("SET") {
1735            Ok(MetadataSubCommand::SET)
1736        } else if s.eq_ignore_ascii_case("CLEAR") {
1737            Ok(MetadataSubCommand::CLEAR)
1738        } else {
1739            Err(MessageParseError::InvalidSubcommand {
1740                cmd: "METADATA",
1741                sub: s.to_owned(),
1742            })
1743        }
1744    }
1745}
1746
1747/// [batch extension](http://ircv3.net/specs/extensions/batch-3.2.html).
1748#[derive(Clone, Debug, PartialEq)]
1749pub enum BatchSubCommand {
1750    /// [NETSPLIT](http://ircv3.net/specs/extensions/batch/netsplit.html)
1751    NETSPLIT,
1752    /// [NETJOIN](http://ircv3.net/specs/extensions/batch/netsplit.html)
1753    NETJOIN,
1754    /// Vendor-specific BATCH subcommands.
1755    CUSTOM(String),
1756}
1757
1758impl BatchSubCommand {
1759    /// Gets the string that corresponds to this subcommand.
1760    pub fn to_str(&self) -> &str {
1761        match *self {
1762            BatchSubCommand::NETSPLIT => "NETSPLIT",
1763            BatchSubCommand::NETJOIN => "NETJOIN",
1764            BatchSubCommand::CUSTOM(ref s) => s,
1765        }
1766    }
1767}
1768
1769impl FromStr for BatchSubCommand {
1770    type Err = MessageParseError;
1771
1772    fn from_str(s: &str) -> Result<BatchSubCommand, Self::Err> {
1773        if s.eq_ignore_ascii_case("NETSPLIT") {
1774            Ok(BatchSubCommand::NETSPLIT)
1775        } else if s.eq_ignore_ascii_case("NETJOIN") {
1776            Ok(BatchSubCommand::NETJOIN)
1777        } else {
1778            Ok(BatchSubCommand::CUSTOM(s.to_uppercase()))
1779        }
1780    }
1781}
1782
1783#[cfg(test)]
1784mod test {
1785    use super::Command;
1786    use super::Response;
1787    use crate::proto::Message;
1788
1789    #[test]
1790    fn format_response() {
1791        assert!(
1792            String::from(&Command::Response(
1793                Response::RPL_WELCOME,
1794                vec!["foo".into()],
1795                None
1796            )) == "001 foo"
1797        );
1798    }
1799
1800    #[test]
1801    fn user_round_trip() {
1802        let cmd = Command::USER("a".to_string(), "b".to_string(), "c".to_string());
1803        let line = Message::from(cmd.clone()).to_string();
1804        let returned_cmd = line.parse::<Message>().unwrap().command;
1805        assert_eq!(cmd, returned_cmd);
1806    }
1807
1808    #[test]
1809    fn parse_user_message() {
1810        let cmd = "USER a 0 * b".parse::<Message>().unwrap().command;
1811        assert_eq!(
1812            Command::USER("a".to_string(), "0".to_string(), "b".to_string()),
1813            cmd
1814        );
1815    }
1816}