format-bytes 0.1.5

A macro to format bytestrings
Documentation
/*!
This crate exposes a procedural macro that allows you to format bytestrings.
For more background on why you would want to do that,
[read this article](https://octobus.net/blog/2020-06-05-not-everything-is-utf8.html).

## Usage

Add this to your `Cargo.toml`:

```toml
[dependencies]
format-bytes = "0.1"
```

then use the macro like so:

```rust
use format_bytes::format_bytes;

fn main() {
    assert_eq!(
        format_bytes!(b"look at those {} bytes", &[0u8, 1, 2]),
        b"look at those \x00\x01\x02 bytes"
    );
}
```

See more examples of how it works on the documentation of
[`format_bytes!` itself](https://docs.rs/format-bytes/\*\/format_bytes/macro.format_bytes.html).

## Missing features

* Named arguments, but they should be added in a future version
* Python-like "f-string" functionality is not planned because of its more
complex implementation and limited actual benefit
* ``format!``-like padding helpers: if the need manifests itself, they might
appear


## Why not 1.0?

Not until named arguments have landed and the macro gets a bit of mileage (it
will be used in [Mercurial](https://www.mercurial-scm.org)).
*/

use proc_macro_hack::proc_macro_hack;
use std::fmt;
use std::io;

/// Creates a `Vec<u8>` using interpolation of runtime expressions.
///
/// The first argument `format_bytes!` receives is a format bytestring.
/// This must be a bytestring literal. The power of the formatting string
/// is in the `{}`s contained.
///
/// Additional arguments passed to `format_bytes!` replace the `{}`s
/// within the formatting bytestring in the order given. It only supports
/// positional arguments for now, but a future version should add support
/// for named arguments.
///
/// These additional arguments may have any type that implements
/// the [`DisplayBytes`] trait.
///
/// # Examples
///
/// ```
/// use format_bytes::format_bytes;
///
/// assert_eq!(format_bytes!(b""), b"");
/// assert_eq!(format_bytes!(b"here"), b"here");
/// assert_eq!(format_bytes!(b"this {{ escapes {{"), b"this {{ escapes {{");
/// assert_eq!(format_bytes!(b"also this {{}}"), b"also this {{}}");
/// assert_eq!(format_bytes!(b"this works {{{}}}", b"a"), b"this works {{a}}");
/// assert_eq!(
///     format_bytes!(b"look at those {} bytes", &[0u8, 1, 2]),
///     b"look at those \x00\x01\x02 bytes"
/// );
///
/// let bytes = vec![0u8, 1, 2];
///
/// assert_eq!(
///     format_bytes!(b"look at those {} bytes", bytes),
///     b"look at those \x00\x01\x02 bytes"
/// );
/// assert_eq!(
///     format_bytes!(b"{}.{}.{}.{}", 1_i32, 2_u8, 3_f32, &4),
///     b"1.2.3.4"
/// );
/// assert_eq!(
///     format_bytes!(b"what about this very very long message {}?", "here".as_bytes()),
///     b"what about this very very long message here?".to_vec()
/// )
/// ```
#[macro_export]
macro_rules! format_bytes {
    ($($args: tt)*) => {{
        let mut vec = Vec::<u8>::new();
        $crate::write_bytes!(vec, $($args)*)
            // Never panics since `impl std::fmt::Write for Vec<u8>` never errors:
            .unwrap();
        vec
    }}
}

/// Like [`format_bytes!`], but writes to a stream given as an additional first argument.
///
/// The stream is an expression of any type that implements the [`WriteBytes`] trait.
/// The macro returns `Result<(), WriteBytes::Error>`.
///
/// # Examples
///
/// ```
/// use format_bytes::write_bytes;
///
/// const BUFFER_LEN: usize = 20;
/// let mut buffer = [0_u8; BUFFER_LEN];
/// let mut slice = &mut buffer[..];
///
/// write_bytes!(slice, b"{}", 3.14).unwrap();
///
/// let written = BUFFER_LEN - slice.len();
/// assert_eq!(&buffer[..written], b"3.14");
/// ```
#[proc_macro_hack]
pub use format_bytes_macros::write_bytes;

/// Let types decide how to format themselves for presentation to users in a byte-stream output.
///
/// Similar to `std::fmt::Display`, but the output stream is bytes instead of Unicode.
///
/// When output is presented to users, it is decoded with an unspecified character encoding
/// that is presumed to be ASCII-compatible.
///
/// Implementers should return any error from `output` (e.g. with the `?` operator),
/// and not emit other errors.
pub trait DisplayBytes {
    fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()>;
}

macro_rules! impl_through_deref {
    // Macro hygiene requires the `$Inner` ident to be an input
    // so it matches corresponding idents in `$Wrapper` types:
    ($Inner: ident => $( $Wrapper: ty, )*) => {
        $(
            /// Forward to the inner type.
            impl<$Inner: ?Sized + DisplayBytes> DisplayBytes for $Wrapper {
                #[inline]
                fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
                    (**self).display_bytes(output)
                }
            }
        )*
    };
}

impl_through_deref! {
    Inner =>
    &'_ Inner,
    &'_ mut Inner,
    Box<Inner>,
    std::rc::Rc<Inner>,
    std::sync::Arc<Inner>,
}

macro_rules! impl_for_byte_string {
    ($($Ty: ty),+) => {
        $(
            /// Byte strings are "formatted" as-is.
            impl DisplayBytes for $Ty {
                #[inline]
                fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
                    output.write_all(self)
                }
            }
        )+
    };
}

impl_for_byte_string!([u8], Vec<u8>);

macro_rules! impl_for_arrays {
    ($( $LEN: expr )+) => {
        impl_for_byte_string! {
            $( [u8; $LEN] ),+
        }
    };
}

impl_for_arrays! {
    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
    33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
    49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
}

/// Adaptor for types that implement `std::fmt::Display`. The Unicode output is encoded as UTF-8.
///
/// # Example
///
/// ```rust
/// use format_bytes::{format_bytes, Utf8};
///
/// assert_eq!(format_bytes!(b"{}", Utf8("è_é")), b"\xc3\xa8_\xc3\xa9");
/// ```
pub struct Utf8<Inner>(pub Inner);

impl<Inner: fmt::Display> DisplayBytes for Utf8<Inner> {
    fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
        // Calling `Display::fmt` requires a `Formatter` which we can’t create directly,
        // so let’s go through `write!` with an adaptor type.
        struct Adapter<'a> {
            output: &'a mut dyn io::Write,
            result: io::Result<()>,
        }

        impl fmt::Write for Adapter<'_> {
            fn write_str(&mut self, s: &str) -> fmt::Result {
                if self.result.is_err() {
                    return Err(fmt::Error);
                }
                let utf8 = s.as_bytes();
                match self.output.write_all(utf8) {
                    Ok(()) => Ok(()),
                    Err(error) => {
                        // `fmt::Error` cannot carry any data so stash the error
                        self.result = Err(error);
                        Err(fmt::Error)
                    }
                }
            }
        }

        let mut adapter = Adapter {
            output,
            result: Ok(()),
        };
        {
            // `write!` requires this import: https://github.com/rust-lang/rust/issues/21826
            use fmt::Write;
            write!(adapter, "{}", self.0)
                // Recover stashed error
                .map_err(|fmt::Error| adapter.result.unwrap_err())
        }
    }
}

macro_rules! impl_ascii_only {
    ($( $Ty: ident )*) => {
        $(
            /// Format to ASCII bytes with `std::fmt::Display`.
            ///
            /// The `Display` impl for this type only emits ASCII characters,
            /// so it’s less useful than in the general case
            /// to make users explicitly opt-in to UTF-8 encoding.
            impl DisplayBytes for $Ty {
                #[inline]
                fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
                    Utf8(self).display_bytes(output)
                }
            }
        )*
    };
}

impl_ascii_only! {
    u8 u16 u32 u64 u128 usize
    i8 i16 i32 i64 i128 isize
    f32 f64
}