# Format-bytes
This crate exposes procedural macros that allow 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.4"
```
Requires Rust 1.85 or later. See the [changelog](https://foss.heptapod.net/octobus/rust/format-bytes/-/blob/branch/default/CHANGELOG.md).
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"
);
let path = b"caf\xe9";
assert_eq!(
format_bytes!("{{{path}}} is {len} bytes long", len = path.len()),
b"{caf\xe9} is 4 bytes long"
);
}
```
See more examples of how it works on the documentation of
[`format_bytes!` itself][`format_bytes!`].
Arguments can be of any type that implements [`DisplayBytes`]: bytestrings,
integers and floats, [`Utf8`] for [`std::fmt::Display`] types, [`join()`] for
sequences, and references and smart pointers to those.
### Writing to a stream: `write_bytes!`
[`write_bytes!`] takes a `&mut` to the stream to write to, followed by the same
arguments as [`format_bytes!`]:
```rust
use format_bytes::write_bytes;
use std::io;
fn write_entry(out: &mut dyn io::Write, path: &[u8]) -> io::Result<()> {
write_bytes!(out, b"{}: {} bytes\n", path, path.len())
}
fn main() {
let mut out = vec![];
write_entry(&mut out, b"caf\xe9").unwrap();
assert_eq!(out, b"caf\xe9: 4 bytes\n");
}
```
The stream can be any [`std::io::Write`], or any [`Sink`].
### Formatting later: `message!`
[`message!`] takes the same arguments as [`format_bytes!`], but returns a
[`Message`] that keeps the format string and the arguments, to be written
later, for example by an error handler. Arguments are moved into the message,
so they must be `'static`, [`Send`] and [`Sync`].
```rust
use format_bytes::{Message, format_bytes, message};
fn unknown_revision(rev: u32) -> Message {
message!("unknown revision {rev}")
}
fn main() {
let msg = unknown_revision(42);
assert_eq!(format_bytes!("abort: {msg}"), b"abort: unknown revision 42");
assert_eq!(msg.template().source, "unknown revision {rev}");
}
```
A [`Message`] is itself a [`DisplayBytes`] argument. [`Message::write_to`]
writes it to a [`Sink`], and [`Message::template`] gives the format string,
parsed at compile time.
### Custom output: `Sink`
A [`Sink`] receives the format string separately from each argument and its
name ([`ArgName`]). Every [`std::io::Write`] is a [`Sink`] that writes both
as-is. Other sinks can, for example, tell user data apart from the message
around it:
```rust
use format_bytes::{ArgName, DisplayBytes, Sink, write_bytes};
use std::io;
/// Quotes arguments
struct Quoting(Vec<u8>);
impl Sink for Quoting {
fn literal(&mut self, text: &str) -> io::Result<()> {
self.0.literal(text)
}
fn arg(&mut self, _: ArgName, value: &dyn DisplayBytes) -> io::Result<()> {
write_bytes!(&mut self.0, "'{}'", value)
}
}
fn main() {
let mut sink = Quoting(vec![]);
write_bytes!(&mut sink, "cannot open {path}", path = b"caf\xe9").unwrap();
assert_eq!(sink.0, b"cannot open 'caf\xe9'");
}
```
A sink that cares about types for structured output can call
[`DisplayBytes::visit`] with a [`Visit`], which implements methods for
integers, UTF-8 text, bytes, a [`join()`] sequence or a nested [`Message`].
## Missing features
* [`format!`]-like explicit indices (`{0}`) and format specs (`{:?}`, padding):
if the need manifests itself, they might appear
* ``#[no_std]`` support
## Why not 1.0?
Not until the macro gets a bit of mileage (it
will be used in [Mercurial](https://www.mercurial-scm.org)).
[`ArgName`]: https://docs.rs/format-bytes/latest/format_bytes/enum.ArgName.html
[`DisplayBytes`]: https://docs.rs/format-bytes/latest/format_bytes/trait.DisplayBytes.html
[`DisplayBytes::visit`]: https://docs.rs/format-bytes/latest/format_bytes/trait.DisplayBytes.html#method.visit
[`Message`]: https://docs.rs/format-bytes/latest/format_bytes/struct.Message.html
[`Message::template`]: https://docs.rs/format-bytes/latest/format_bytes/struct.Message.html#method.template
[`Message::write_to`]: https://docs.rs/format-bytes/latest/format_bytes/struct.Message.html#method.write_to
[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
[`Sink`]: https://docs.rs/format-bytes/latest/format_bytes/trait.Sink.html
[`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html
[`Utf8`]: https://docs.rs/format-bytes/latest/format_bytes/struct.Utf8.html
[`Visit`]: https://docs.rs/format-bytes/latest/format_bytes/trait.Visit.html
[`format!`]: https://doc.rust-lang.org/std/macro.format.html
[`format_bytes!`]: https://docs.rs/format-bytes/latest/format_bytes/macro.format_bytes.html
[`join()`]: https://docs.rs/format-bytes/latest/format_bytes/fn.join.html
[`message!`]: https://docs.rs/format-bytes/latest/format_bytes/macro.message.html
[`std::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
[`std::io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
[`write_bytes!`]: https://docs.rs/format-bytes/latest/format_bytes/macro.write_bytes.html