use core::fmt;
use core::marker::PhantomData;
pub trait Unsigned {
const N: usize;
}
#[macro_export]
#[doc(hidden)]
macro_rules! typeint {
($name:ident, $n:expr) => {
pub struct $name;
impl $crate::hex::Unsigned for $name {
const N: usize = $n;
}
};
}
pub struct U1;
impl Unsigned for U1 {
const N: usize = 1;
}
pub trait Separator {
const SEPARATOR: &'static str;
}
#[macro_export]
#[doc(hidden)]
macro_rules! typesep {
($name:ident, $s:expr) => {
pub struct $name;
impl $crate::hex::Separator for $name {
const SEPARATOR: &'static str = $s;
}
};
}
pub struct HexStr<'a, T: ?Sized, U, S>
where
U: Unsigned,
S: Separator,
{
pub value: &'a T,
_bytes_per_block: PhantomData<U>,
_separator: PhantomData<S>,
}
#[macro_export]
macro_rules! hex_str {
($array:expr) => { $crate::hex_str!($array, 1, sep: " ") };
($array:expr, sep: $separator:expr) => { $crate::hex_str!($array, 1, sep: $separator) };
($array:expr, $n:tt) => { $crate::hex_str!($array, $n, sep: " ") };
($array:expr, $n:tt, sep: $separator:expr) => {{
$crate::typesep!(Separator, $separator);
$crate::typeint!(Number, $n);
$crate::hex::HexStr::<_, Number, Separator>($array)
}};
}
#[macro_export]
macro_rules! hexstr {
($array:expr) => {
$crate::hex_str!($array, sep: "")
}
}
#[allow(non_snake_case)]
pub fn HexStr<T: ?Sized, U: Unsigned, S: Separator>(value: &T) -> HexStr<'_, T, U, S> {
HexStr {
value,
_bytes_per_block: PhantomData,
_separator: PhantomData,
}
}
impl<T: ?Sized, S, U> fmt::Debug for HexStr<'_, T, U, S>
where
T: AsRef<[u8]>,
U: Unsigned,
S: Separator,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Display::fmt(self, f)
}
}
impl<T: ?Sized, U, S> fmt::Display for HexStr<'_, T, U, S>
where
T: AsRef<[u8]>,
U: Unsigned,
S: Separator,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::UpperHex::fmt(self, f)
}
}
macro_rules! implement {
($Trait:ident, $padded_formatter:expr) => {
impl<'a, T: ?Sized, U, S> fmt::$Trait for HexStr<'a, T, U, S>
where
T: AsRef<[u8]>,
U: Unsigned,
S: Separator,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
use core::fmt::{self, Alignment::*};
let max_bytes = f.width().unwrap_or(usize::MAX);
let bytes = self.value.as_ref();
#[inline]
fn nontruncated_fmt(
bytes: &[u8],
f: &mut fmt::Formatter<'_>,
chunk_size: usize,
separator: &str,
) -> Result<(), fmt::Error> {
let mut first = true;
for entry in bytes.chunks(chunk_size) {
if first {
first = false;
} else {
f.write_str(separator)?;
}
for byte in entry.iter() {
write!(f, $padded_formatter, byte)?;
}
}
Ok(())
}
const ELLIPSIS: &str = "..";
let chunk_size = U::N;
let separator = S::SEPARATOR;
if bytes.len() <= max_bytes {
nontruncated_fmt(bytes, f, chunk_size, separator)
} else {
let align = f.align().unwrap_or(Center);
let (left, right) = match align {
Left => (max_bytes, 0),
Center => (max_bytes - max_bytes / 2, max_bytes / 2),
Right => (0, max_bytes),
};
nontruncated_fmt(&bytes[..left], f, chunk_size, separator)?;
f.write_str(ELLIPSIS)?;
nontruncated_fmt(&bytes[bytes.len() - right..], f, chunk_size, separator)?;
Ok(())
}
}
}
};
}
implement!(LowerHex, "{:02x}");
implement!(UpperHex, "{:02X}");
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_hex_str() {
let buf = [1u8, 2, 3, 0xA1, 0xB7, 0xFF, 0x3];
insta::assert_debug_snapshot!(format_args!("'{}'", hex_str!(&buf)));
insta::assert_debug_snapshot!(format_args!("'{}'", hex_str!(&buf, 2)));
insta::assert_debug_snapshot!(format_args!("'{:x}'", hex_str!(&buf, 2)));
insta::assert_debug_snapshot!(format_args!("'{}'", hex_str!(&buf, 4)));
insta::assert_debug_snapshot!(format_args!("'{}'", hex_str!(&buf[..], 4)));
insta::assert_debug_snapshot!(format_args!("'{}'", hex_str!(&buf, 4)));
insta::assert_debug_snapshot!(format_args!("'{}'", hex_str!(&buf, 4)));
}
#[test]
fn test_custom_hex_str() {
let buf = [1u8, 2, 3, 0xA1, 0xB7, 0xFF, 0x3];
typeint!(U3, 3);
typesep!(Space, " ");
insta::assert_debug_snapshot!(format_args!("'{:X}'", HexStr::<_, U3, Space>(&buf),));
}
}