use std::{
borrow::Cow,
error::Error,
fmt,
str::{self, FromStr, Utf8Error},
};
#[derive(Clone, Eq, PartialEq, Debug)]
#[must_use]
pub struct Skip(pub bool);
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Nag(pub u8);
impl Nag {
pub fn from_ascii(s: &[u8]) -> Result<Nag, InvalidNag> {
if s == b"?!" {
Ok(Nag::DUBIOUS_MOVE)
} else if s == b"?" {
Ok(Nag::MISTAKE)
} else if s == b"??" {
Ok(Nag::BLUNDER)
} else if s == b"!" {
Ok(Nag::GOOD_MOVE)
} else if s == b"!!" {
Ok(Nag::BRILLIANT_MOVE)
} else if s == b"!?" {
Ok(Nag::SPECULATIVE_MOVE)
} else if s.len() > 1 && s[0] == b'$' {
btoi::btou(&s[1..])
.ok()
.map(Nag)
.ok_or(InvalidNag { _priv: () })
} else {
Err(InvalidNag { _priv: () })
}
}
pub const GOOD_MOVE: Nag = Nag(1);
pub const MISTAKE: Nag = Nag(2);
pub const BRILLIANT_MOVE: Nag = Nag(3);
pub const BLUNDER: Nag = Nag(4);
pub const SPECULATIVE_MOVE: Nag = Nag(5);
pub const DUBIOUS_MOVE: Nag = Nag(6);
}
impl fmt::Display for Nag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "${}", self.0)
}
}
impl From<u8> for Nag {
fn from(nag: u8) -> Nag {
Nag(nag)
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct InvalidNag {
_priv: (),
}
impl fmt::Debug for InvalidNag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("InvalidNag").finish()
}
}
impl fmt::Display for InvalidNag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
"invalid nag".fmt(f)
}
}
impl Error for InvalidNag {
fn description(&self) -> &str {
"invalid nag"
}
}
impl FromStr for Nag {
type Err = InvalidNag;
fn from_str(s: &str) -> Result<Nag, InvalidNag> {
Nag::from_ascii(s.as_bytes())
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct RawHeader<'a>(pub &'a [u8]);
impl<'a> RawHeader<'a> {
pub fn as_bytes(&self) -> &[u8] {
self.0
}
pub fn decode(&self) -> Cow<'a, [u8]> {
let mut head = 0;
let mut decoded: Vec<u8> = Vec::new();
for escape in memchr::memchr_iter(b'\\', self.0) {
match self.0.get(escape + 1).cloned() {
Some(ch) if ch == b'\\' || ch == b'"' => {
decoded.extend_from_slice(&self.0[head..escape]);
head = escape + 1;
}
_ => (),
}
}
if head == 0 {
Cow::Borrowed(self.0)
} else {
decoded.extend_from_slice(&self.0[head..]);
Cow::Owned(decoded)
}
}
pub fn decode_utf8(&self) -> Result<Cow<'a, str>, Utf8Error> {
Ok(match self.decode() {
Cow::Borrowed(borrowed) => Cow::Borrowed(str::from_utf8(borrowed)?),
Cow::Owned(owned) => Cow::Owned(String::from_utf8(owned).map_err(|e| e.utf8_error())?),
})
}
pub fn decode_utf8_lossy(&self) -> Cow<'a, str> {
match self.decode() {
Cow::Borrowed(borrowed) => String::from_utf8_lossy(borrowed),
Cow::Owned(owned) => Cow::Owned(String::from_utf8_lossy(&owned).into_owned()),
}
}
}
impl<'a> fmt::Debug for RawHeader<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.decode_utf8_lossy())
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct RawComment<'a>(pub &'a [u8]);
impl<'a> RawComment<'a> {
pub fn as_bytes(&self) -> &[u8] {
self.0
}
}
impl<'a> fmt::Debug for RawComment<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", String::from_utf8_lossy(self.as_bytes()).as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nag() {
assert_eq!(Nag::from_ascii(b"$33"), Ok(Nag(33)));
}
#[test]
fn test_raw_header() {
let header = RawHeader(b"Hello world");
assert_eq!(header.decode().as_ref(), b"Hello world");
let header = RawHeader(b"Hello \\world\\");
assert_eq!(header.decode().as_ref(), b"Hello \\world\\");
let header = RawHeader(b"\\Hello \\\"world\\\\");
assert_eq!(header.decode().as_ref(), b"\\Hello \"world\\");
}
}