use core::fmt;
use std::io;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ByteParser {
Decimal = 10,
Hexadecimal = 16,
}
macro_rules! unwrap {
($expr:expr) => {
match $expr {
Some(value) => value,
None => return None,
}
};
}
impl ByteParser {
const fn digit(&self, byte: u8) -> Option<u8> {
let value = match byte {
0x30..=0x39 => byte - 0x30,
0x41..=0x46 => byte - 0x41 + 10,
0x61..=0x66 => byte - 0x61 + 10,
_ => return None,
};
if (*self as u8) <= value {
return None;
}
Some(value)
}
pub const fn to_u16(&self, bytes: &[u8]) -> Option<u16> {
let value = unwrap!(self.to_u32(bytes));
if value <= 0xffff {
Some(value as u16)
} else {
None
}
}
pub const fn to_u32(&self, bytes: &[u8]) -> Option<u32> {
let mut value: u32 = 0;
let mut index = 0;
while index < bytes.len() {
let digit = unwrap!(self.digit(bytes[index]));
value = unwrap!(value.checked_mul(*self as u32));
value = unwrap!(value.checked_add(digit as u32));
index += 1;
}
Some(value)
}
}
#[derive(Debug)]
pub enum ByteFormat<'a> {
Concise(&'a [u8]),
Nicely(&'a [u8]),
Hexdump(&'a [u8]),
}
const C0: [&str; 32] = [
"‹NUL›",
"‹SOH›",
"‹STX›",
"‹ETX›",
"‹EOT›",
"‹ENQ›",
"‹ACK›",
"‹BEL›",
"‹BS›",
"‹HT›",
"‹LF›",
"‹VT›",
"‹FF›",
"‹CR›",
"‹SO›",
"‹SI›",
"‹DLE›",
"‹DC1›",
"‹DC2›",
"‹DC3›",
"‹DC4›",
"‹NAK›",
"‹SYN›",
"‹ETB›",
"‹CAN›",
"‹EM›",
"‹SUB›",
"‹ESC›",
"‹FS›",
"‹GS›",
"‹RS›",
"‹US›",
];
const C1: [&str; 5] = ["‹CSI›", "‹ST›", "‹OSC›", "‹PM›", "‹APC›"];
impl ByteFormat<'_> {
pub fn render<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, fmt::Error> {
match *self {
ByteFormat::Concise(bytes) => ByteFormat::render_concise(bytes, writer),
ByteFormat::Nicely(bytes) => ByteFormat::render_nicely(bytes, writer),
ByteFormat::Hexdump(bytes) => ByteFormat::render_hexdump(bytes, writer),
}
}
fn render_concise<W>(bytes: &[u8], writer: &mut W) -> Result<usize, fmt::Error>
where
W: fmt::Write + ?Sized,
{
for byte in bytes {
let display = match *byte {
0x00..=0x1f => {
char::from_u32(0x2400_u32 + *byte as u32).expect("known good Unicode character")
}
0x20..=0x7e => *byte as char,
0x7f => char::from_u32(0x2421).expect("known good Unicode character"),
_ => '.',
};
writer.write_char(display)?;
}
Ok(bytes.len())
}
fn render_nicely<W>(bytes: &[u8], writer: &mut W) -> Result<usize, fmt::Error>
where
W: fmt::Write + ?Sized,
{
let mut ascii = [0; 1];
let mut characters = 0;
for &byte in bytes {
let display = match byte {
0x00..=0x1f => C0[byte as usize],
0x20..=0x7e => {
ascii[0] = byte;
core::str::from_utf8(&ascii).expect("ASCII characters are valid UTF-8, too")
}
0x7f => "‹DEL›",
0x90 => "‹DCS›",
0x98 => "‹SOS›",
0x9b..=0x9f => C1[(byte - 0x9b) as usize],
_ => "",
};
if display.is_empty() {
writer.write_fmt(format_args!("「{:02X}」", byte))?;
characters += 4;
} else {
writer.write_str(display)?;
characters += match display.len() {
n @ (1 | 2) => n,
n => n - 6 + 2,
};
}
}
Ok(characters)
}
#[allow(clippy::missing_asserts_for_indexing)]
fn render_hexdump<W>(bytes: &[u8], writer: &mut W) -> Result<usize, fmt::Error>
where
W: fmt::Write + ?Sized,
{
const CHUNK_SIZE: usize = 16;
let compact = bytes.len() < CHUNK_SIZE;
let mut chunk_index = 0;
let mut characters = 0;
for chunk in bytes.chunks(CHUNK_SIZE) {
if 0 < chunk_index {
writer.write_char('\n')?;
}
write!(writer, "{:04x}: ", chunk_index)?;
characters = 7;
for pair in chunk.chunks(2) {
write!(writer, "{:02x}", pair[0])?;
if pair.len() == 1 {
write!(writer, " ")?;
} else {
write!(writer, "{:02x} ", pair[1])?;
}
characters += 5;
}
if !compact {
for _ in 0..(CHUNK_SIZE - chunk.len()) / 2 {
writer.write_str(" ")?;
characters += 5;
}
}
writer.write_str(" ")?;
characters += 1;
ByteFormat::render_concise(chunk, writer)?;
chunk_index += chunk.len();
characters += chunk.len();
}
Ok(characters)
}
}
impl fmt::Display for ByteFormat<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.render(f)?;
Ok(())
}
}
pub struct Rewriter<'a, W: ?Sized + 'a> {
writer: &'a mut W,
result: io::Result<()>,
}
impl<'a, W: ?Sized + 'a> Rewriter<'a, W> {
pub fn new(writer: &'a mut W) -> Self {
Self {
writer,
result: Ok(()),
}
}
pub fn is_err(&self) -> bool {
self.result.is_err()
}
pub fn into_err(self) -> io::Error {
match self.result {
Err(err) => err,
Ok(_) => panic!("display trait returned error without underlying I/O error"),
}
}
}
impl<W: io::Write + ?Sized> fmt::Write for Rewriter<'_, W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.writer.write_all(s.as_bytes()).map_err(|err| {
self.result = Err(err);
fmt::Error
})
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{Cursor, Error, Write};
#[test]
fn test_radix_parse() {
assert_eq!(ByteParser::Decimal.to_u16(b"665"), Some(665));
assert_eq!(ByteParser::Decimal.to_u16(b"65536"), None);
assert_eq!(ByteParser::Decimal.to_u16(b"665A"), None);
assert_eq!(ByteParser::Hexadecimal.to_u16(b"665"), Some(1_637));
assert_eq!(ByteParser::Hexadecimal.to_u16(b"665A"), Some(26_202));
assert_eq!(ByteParser::Hexadecimal.to_u16(b"fFfF"), Some(0xffff));
assert_eq!(ByteParser::Hexadecimal.to_u16(b"10000"), None);
assert_eq!(ByteParser::Decimal.to_u32(b"665"), Some(665));
assert_eq!(ByteParser::Decimal.to_u32(b"65536"), Some(65_536));
assert_eq!(ByteParser::Decimal.to_u32(b"665A"), None);
assert_eq!(ByteParser::Hexadecimal.to_u32(b"665"), Some(1_637));
assert_eq!(ByteParser::Hexadecimal.to_u32(b"665A"), Some(26_202));
assert_eq!(
ByteParser::Hexadecimal.to_u32(b"fFfFfFfF"),
Some(0xffff_ffff)
);
assert_eq!(ByteParser::Hexadecimal.to_u32(b"100000000"), None);
}
#[test]
fn test_format() -> std::io::Result<()> {
let mut buffer = Cursor::new(vec![0; 500]);
write!(
buffer,
"{}",
ByteFormat::Hexdump(b"\x1bP>|Terminal\x07\x1bP>|Name\x1b\\")
)?;
assert_eq!(
&buffer.get_ref()[0..buffer.position() as usize],
b"0000: 1b50 3e7c 5465 726d 696e 616c 071b 503e \xe2\x90\x9bP>|Terminal\
\xe2\x90\x87\
\xe2\x90\x9bP>\n\
0010: 7c4e 616d 651b 5c |Name\xe2\x90\x9b\\"
);
Ok(())
}
#[test]
fn test_nicely() -> std::io::Result<()> {
let mut buffer = Cursor::new(vec![0; 100]);
let mut writer = Rewriter::new(&mut buffer);
assert_eq!(ByteFormat::Nicely(b"R").render(&mut writer), Ok(1));
assert_eq!(ByteFormat::Nicely(b"\x1b").render(&mut writer), Ok(5));
assert_eq!(ByteFormat::Nicely(b"#").render(&mut writer), Ok(1));
assert_eq!(ByteFormat::Nicely(b"\xaf").render(&mut writer), Ok(4));
assert_eq!(ByteFormat::Nicely(b"\\").render(&mut writer), Ok(1));
assert_eq!(ByteFormat::Nicely(b"\"").render(&mut writer), Ok(1));
assert_eq!(
&buffer.get_ref()[0..buffer.position() as usize],
"R‹ESC›#「AF」\\\"".as_bytes()
);
assert_eq!(buffer.position(), 21);
Ok::<(), Error>(())
}
}