|
|
@@ -0,0 +1,175 @@ |
|
|
|
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<Stringy<'a>> { |
|
|
|
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<Numeric<'a>> { |
|
|
|
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<Numeric<'a>> { |
|
|
|
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) |
|
|
|
} |
|
|
|
} |