use std::fmt; use std::str::{from_utf8, from_utf8_unchecked}; /// An IRC command #[derive(Copy,Clone,Debug,Eq,PartialEq,Ord,PartialOrd)] pub enum IrcCommand<'a> { Stringy(Stringy<'a>), Numeric(Numeric<'a>) } /// A stringy IRC command #[derive(Copy,Clone,Debug,Eq,PartialEq,Ord,PartialOrd)] pub struct Stringy<'a>(&'a [u8]); /// A numeric IRC command. #[derive(Copy,Clone,Debug,Eq,PartialEq,Ord,PartialOrd)] pub struct Numeric<'a>(u16, &'a [u8; 3]); /// A full IRC command line. pub trait IrcCommandLine { fn get_command<'a>(&'a self) -> IrcCommand<'a>; /// Experimental. Do not use. fn get_argument<'a>(&'a self, arg: usize) -> &'a [u8]; /// Experimental. Do not use. fn get_argument_count(&self) -> usize; /// Experimental. Do not use. fn get_source<'a>(&'a self) -> &'a [u8]; } impl<'a> Stringy<'a> { #[inline] pub fn new(cmd: &'a [u8]) -> Option> { if cmd.len() == 3 && matches!((cmd[0], cmd[1], cmd[2]), (b'0'...b'9', b'0'...b'9', b'0'...b'9')) { None } else { Some(Self::new_unchecked(cmd)) } } #[inline] fn new_unchecked(cmd: &'a [u8]) -> Stringy<'a> { Stringy(cmd) } #[inline] pub fn get_bytes(&self) -> &'a [u8] { self.0 } } impl<'a> Numeric<'a> { /// Creates a new numeric from the given bytes. Returns None if the given bytes aren't a valid /// numeric. /// /// # Examples /// ``` /// use uirc::command::Numeric; /// /// match Numeric::new(b"005") { /// Some(numeric) => println!("Numeric: {}", numeric), /// None => println!("Not a numeric!"), /// } /// ``` #[inline] pub fn new(cmd: &'a [u8; 3]) -> Option> { match (cmd[0], cmd[1], cmd[2]) { (b'0'...b'9', b'0'...b'9', b'0'...b'9') => Some(Numeric::new_unchecked(cmd)), _ => None } } /// Creates a new numeric from the given bytes. Returns None if the given bytes aren't a valid /// numeric. /// /// # Examples /// ``` /// use uirc::command::Numeric; /// /// match Numeric::new_from_slice(b"005") { /// Some(numeric) => println!("Numeric: {}", numeric), /// None => println!("Not a numeric!"), /// } /// ``` #[inline] pub fn new_from_slice(cmd: &'a [u8]) -> Option> { if cmd.len() == 3 { // TODO switch to TryFrom/TryInto once those are stable. (rust-lang/rust#33417) Self::new(array_ref![cmd, 0, 3]) } else { None } } // not unsafe, but may produce unexpected results // keep this private (for now) #[inline] fn new_unchecked(cmd: &'a [u8; 3]) -> Numeric<'a> { Numeric(cmd.iter().map(|x| (x-b'0') as u16).fold(0, |x,y| x*10+y), cmd) } /// Returns the numeric as a number, e.g. for processing in code. /// /// # Examples /// ``` /// use uirc::command::Numeric; /// /// let numeric = Numeric::new(b"005").unwrap(); /// match numeric.get_numeric() { /// 005 => println!("got an ISUPPORT!"), /// _ => println!("got something else!"), /// } /// ``` #[inline] pub fn get_numeric(&self) -> u16 { self.0 } /// Returns the numeric as bytes, e.g. for writing to a stream. /// /// # Examples /// ```rust /// use uirc::command::Numeric; /// use std::io::Write; /// /// let mut client = Vec::new(); /// let numeric = Numeric::new(b"005").unwrap(); /// let bytes = numeric.get_bytes(); /// client.write_all(bytes).unwrap(); /// ``` #[inline] pub fn get_bytes(&self) -> &'a [u8; 3] { self.1 } } impl<'a> IrcCommand<'a> { pub fn new(cmd: &'a [u8]) -> IrcCommand<'a> { if let Some(numeric) = Numeric::new_from_slice(cmd) { IrcCommand::Numeric(numeric) } else { IrcCommand::Stringy(Stringy::new(cmd).unwrap()) } } } impl<'a> fmt::Display for Stringy<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut i = 0; let l = self.0.len(); let v = self.0; while i < l { let st = from_utf8(&v[i..]); if let Ok(s) = st { write!(f, "{}", s)?; break; } else { let err = st.err().unwrap(); write!(f, "{}", unsafe { from_utf8_unchecked(&v[i..][..err.valid_up_to()])})?; write!(f, "\u{FFFD}")?; match err.error_len() { None => i = l, Some(len) => i = i + err.valid_up_to() + len } } } Ok(()) } } impl<'a> fmt::Display for Numeric<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } }