format-bytes 0.4.0

A macro to format bytestrings
Documentation
use format_bytes::{
    ArgName, DisplayBytes, Message, Sink, Visit, message, write_bytes,
};
use std::io;

#[test]
fn writers_are_sinks() {
    let mut out = vec![];
    out.literal("a").unwrap();
    out.arg(ArgName::Index(0), &1).unwrap();
    let writer: &mut dyn io::Write = &mut out;
    writer.arg(ArgName::Name("b"), b"b").unwrap();
    assert_eq!(out, b"a1b");
}

/// Records what the macro sends
#[derive(Default)]
struct Recorder(Vec<(Option<ArgName>, Vec<u8>)>);

impl Sink for Recorder {
    fn literal(&mut self, text: &str) -> io::Result<()> {
        self.0.push((None, text.as_bytes().to_vec()));
        Ok(())
    }

    fn arg(
        &mut self,
        name: ArgName,
        value: &dyn DisplayBytes,
    ) -> io::Result<()> {
        let mut bytes = vec![];
        value.display_bytes(&mut bytes)?;
        self.0.push((Some(name), bytes));
        Ok(())
    }
}

#[test]
fn literals_and_args_are_apart() {
    let rev = 42;
    let mut sink = Recorder::default();
    write_bytes!(&mut sink, "{} {path} {rev}{}", b"a", 1, path = b"p")
        .unwrap();
    assert_eq!(
        sink.0,
        [
            (Some(ArgName::Index(0)), b"a".to_vec()),
            (None, b" ".to_vec()),
            (Some(ArgName::Name("path")), b"p".to_vec()),
            (None, b" ".to_vec()),
            (Some(ArgName::Name("rev")), b"42".to_vec()),
            (Some(ArgName::Index(1)), b"1".to_vec()),
        ]
    );
}

/// Brackets arguments, including those of nested messages
struct Brackets(Vec<u8>);

impl Sink for Brackets {
    fn literal(&mut self, text: &str) -> io::Result<()> {
        self.0.literal(text)
    }

    fn arg(
        &mut self,
        _name: ArgName,
        value: &dyn DisplayBytes,
    ) -> io::Result<()> {
        value.visit(&mut BracketsValue(self))
    }
}

struct BracketsValue<'a>(&'a mut Brackets);

impl Visit for BracketsValue<'_> {
    fn bytes(&mut self, value: &[u8]) -> io::Result<()> {
        write_bytes!(&mut self.0.0, "[{}]", value)
    }

    fn str(&mut self, value: &str) -> io::Result<()> {
        self.bytes(value.as_bytes())
    }

    fn message(&mut self, message: &Message) -> io::Result<()> {
        message.write_to(self.0)
    }
}

#[test]
fn nested_message() {
    let cause = message!("no such file {path}", path = b"p".to_vec());
    let msg = message!("cannot load {config}: {cause}", config = 1);
    let mut sink = Brackets(vec![]);
    msg.write_to(&mut sink).unwrap();
    assert_eq!(sink.0, b"cannot load [1]: no such file [p]");
}