format_bytes/lib.rs
1/*!
2This crate exposes procedural macros that allow you to format bytestrings.
3For more background on why you would want to do that,
4[read this article](https://octobus.net/blog/2020-06-05-not-everything-is-utf8.html).
5
6## Usage
7
8Add this to your `Cargo.toml`:
9
10```toml
11[dependencies]
12format-bytes = "0.4"
13```
14
15Requires Rust 1.85 or later. See the [changelog](https://foss.heptapod.net/octobus/rust/format-bytes/-/blob/branch/default/CHANGELOG.md).
16
17Then use the macro like so:
18
19```rust
20use format_bytes::format_bytes;
21
22fn main() {
23 assert_eq!(
24 format_bytes!(b"look at those {} bytes", &[0u8, 1, 2]),
25 b"look at those \x00\x01\x02 bytes"
26 );
27
28 let path = b"caf\xe9";
29 assert_eq!(
30 format_bytes!("{{{path}}} is {len} bytes long", len = path.len()),
31 b"{caf\xe9} is 4 bytes long"
32 );
33}
34```
35
36See more examples of how it works on the documentation of
37[`format_bytes!` itself][`format_bytes!`].
38
39Arguments can be of any type that implements [`DisplayBytes`]: bytestrings,
40integers and floats, [`Utf8`] for [`std::fmt::Display`] types, [`join()`] for
41sequences, and references and smart pointers to those.
42
43### Writing to a stream: `write_bytes!`
44
45[`write_bytes!`] takes a `&mut` to the stream to write to, followed by the same
46arguments as [`format_bytes!`]:
47
48```rust
49use format_bytes::write_bytes;
50use std::io;
51
52fn write_entry(out: &mut dyn io::Write, path: &[u8]) -> io::Result<()> {
53 write_bytes!(out, b"{}: {} bytes\n", path, path.len())
54}
55
56fn main() {
57 let mut out = vec![];
58 write_entry(&mut out, b"caf\xe9").unwrap();
59 assert_eq!(out, b"caf\xe9: 4 bytes\n");
60}
61```
62
63The stream can be any [`std::io::Write`], or any [`Sink`].
64
65### Formatting later: `message!`
66
67[`message!`] takes the same arguments as [`format_bytes!`], but returns a
68[`Message`] that keeps the format string and the arguments, to be written
69later, for example by an error handler. Arguments are moved into the message,
70so they must be `'static`, [`Send`] and [`Sync`].
71
72```rust
73use format_bytes::{Message, format_bytes, message};
74
75fn unknown_revision(rev: u32) -> Message {
76 message!("unknown revision {rev}")
77}
78
79fn main() {
80 let msg = unknown_revision(42);
81 assert_eq!(format_bytes!("abort: {msg}"), b"abort: unknown revision 42");
82 assert_eq!(msg.template().source, "unknown revision {rev}");
83}
84```
85
86A [`Message`] is itself a [`DisplayBytes`] argument. [`Message::write_to`]
87writes it to a [`Sink`], and [`Message::template`] gives the format string,
88parsed at compile time.
89
90### Custom output: `Sink`
91
92A [`Sink`] receives the format string separately from each argument and its
93name ([`ArgName`]). Every [`std::io::Write`] is a [`Sink`] that writes both
94as-is. Other sinks can, for example, tell user data apart from the message
95around it:
96
97```rust
98use format_bytes::{ArgName, DisplayBytes, Sink, write_bytes};
99use std::io;
100
101/// Quotes arguments
102struct Quoting(Vec<u8>);
103
104impl Sink for Quoting {
105 fn literal(&mut self, text: &str) -> io::Result<()> {
106 self.0.literal(text)
107 }
108
109 fn arg(&mut self, _: ArgName, value: &dyn DisplayBytes) -> io::Result<()> {
110 write_bytes!(&mut self.0, "'{}'", value)
111 }
112}
113
114fn main() {
115 let mut sink = Quoting(vec![]);
116 write_bytes!(&mut sink, "cannot open {path}", path = b"caf\xe9").unwrap();
117 assert_eq!(sink.0, b"cannot open 'caf\xe9'");
118}
119```
120
121A sink that cares about types for structured output, can call
122[`DisplayBytes::visit`] with a [`Visit`], which implements methods for
123integers, UTF-8 text, bytes, a [`join()`] sequence or a nested [`Message`].
124
125## Missing features
126
127* [`format!`]-like explicit indices (`{0}`) and format specs (`{:?}`, padding):
128if the need manifests itself, they might appear
129* ``#[no_std]`` support
130
131
132## Why not 1.0?
133
134Not until the macro gets a bit of mileage (it
135will be used in [Mercurial](https://www.mercurial-scm.org)).
136*/
137
138use std::fmt;
139use std::io;
140
141/// Creates a `Vec<u8>` using interpolation of runtime expressions.
142///
143/// The first argument `format_bytes!` receives is a format string.
144/// This must be a string literal, or a bytestring literal holding valid UTF-8.
145/// The power of the formatting string
146/// is in the `{}`s contained.
147///
148/// Literal braces are written `{{` and `}}`.
149///
150/// Additional arguments passed to `format_bytes!` replace the `{}`s
151/// within the formatting string in the order given. Named arguments, given
152/// after positional ones as `name = value`, replace each `{name}`. A `{name}`
153/// with no such argument uses the variable `name` from the caller's scope.
154/// Each argument is evaluated once.
155///
156/// These additional arguments may have any type that implements
157/// the [`DisplayBytes`] trait.
158///
159/// # Examples
160///
161/// ```
162/// use format_bytes::format_bytes;
163///
164/// assert_eq!(format_bytes!(b""), b"");
165/// assert_eq!(format_bytes!(b"here"), b"here");
166/// assert_eq!(format_bytes!("é{}", b"\xe9"), b"\xc3\xa9\xe9");
167/// assert_eq!(format_bytes!(b"this {{ escapes }}"), b"this { escapes }");
168/// assert_eq!(format_bytes!(b"also this {{}}"), b"also this {}");
169/// assert_eq!(format_bytes!(b"this works {{{}}}", b"a"), b"this works {a}");
170/// assert_eq!(
171/// format_bytes!(b"look at those {} bytes", &[0u8, 1, 2]),
172/// b"look at those \x00\x01\x02 bytes"
173/// );
174///
175/// let bytes = vec![0u8, 1, 2];
176///
177/// assert_eq!(
178/// format_bytes!(b"look at those {} bytes", bytes),
179/// b"look at those \x00\x01\x02 bytes"
180/// );
181/// assert_eq!(
182/// format_bytes!(b"{}.{}.{}.{}", 1_i32, 2_u8, 3_f32, &4),
183/// b"1.2.3.4"
184/// );
185/// assert_eq!(
186/// format_bytes!(b"what about this very very long message {}?", "here".as_bytes()),
187/// b"what about this very very long message here?".to_vec()
188/// );
189/// assert_eq!(format_bytes!(b"{}", std::borrow::Cow::Borrowed("cow".as_bytes())), b"cow");
190/// ```
191#[macro_export]
192macro_rules! format_bytes {
193 ($($args: tt)*) => {{
194 let mut vec = Vec::<u8>::new();
195 $crate::write_bytes!(&mut vec, $($args)*)
196 // Never panics since `impl std::fmt::Write for Vec<u8>` never errors:
197 .unwrap();
198 vec
199 }}
200}
201
202#[doc(hidden)]
203pub use format_bytes_macros::{_message, _write_bytes};
204
205/// Like [`format_bytes!`], but writes to a stream given as an additional first argument.
206///
207/// The stream is a `&mut` reference to a [`Sink`], such as any [`std::io::Write`].
208/// The macro returns [`std::io::Result<()>`](std::io::Result).
209///
210/// # Examples
211///
212/// ```
213/// use format_bytes::write_bytes;
214///
215/// const BUFFER_LEN: usize = 20;
216/// let mut buffer = [0_u8; BUFFER_LEN];
217/// let mut slice = &mut buffer[..];
218///
219/// write_bytes!(&mut slice, b"{}", 3.14).unwrap();
220///
221/// // `impl std::io::Write for &mut [u8]` reassigns the slice to the unwritten remainder:
222/// let written = BUFFER_LEN - slice.len();
223/// assert_eq!(&buffer[..written], b"3.14");
224/// ```
225#[macro_export]
226macro_rules! write_bytes {
227 ($($args: tt)*) => {
228 $crate::_write_bytes!([$crate] $($args)*)
229 };
230}
231
232/// Let types decide how to format themselves for presentation to users in a byte-stream output.
233///
234/// Similar to `std::fmt::Display`, but the output stream is bytes instead of Unicode.
235///
236/// When output is presented to users, it is decoded with an unspecified character encoding
237/// that is presumed to be ASCII-compatible.
238///
239/// Implementers should return any error from `output` (e.g. with the `?` operator),
240/// and not emit other errors.
241///
242/// # Example
243///
244/// A typical `impl` for a struct with multiple fields might use the `write_bytes` macro:
245///
246/// ```
247/// use format_bytes::{DisplayBytes, write_bytes};
248///
249/// struct Point2D { x: f32, y: f32 }
250///
251/// impl DisplayBytes for Point2D {
252/// fn display_bytes(
253/// &self,
254/// out: &mut dyn std::io::Write,
255/// ) -> std::io::Result<()> {
256/// write_bytes!(out, b"x = {}, y = {}", self.x, self.y)
257/// }
258/// }
259/// ```
260pub trait DisplayBytes {
261 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()>;
262
263 /// Describes the value with its type, for sinks that care about types,
264 /// such as structured output.
265 ///
266 /// Defaults to the output of `display_bytes`, as bytes.
267 fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
268 let mut bytes = vec![];
269 self.display_bytes(&mut bytes)?;
270 visitor.bytes(&bytes)
271 }
272}
273
274/// Receives a value described by [`DisplayBytes::visit`].
275///
276/// Only `bytes` and `str` are required: other methods default to them, the
277/// way `display_bytes` would write the value.
278pub trait Visit {
279 /// Bytes in an unknown encoding
280 fn bytes(&mut self, value: &[u8]) -> io::Result<()>;
281
282 /// Text known to be valid UTF-8
283 fn str(&mut self, value: &str) -> io::Result<()>;
284
285 fn int(&mut self, value: i128) -> io::Result<()> {
286 self.str(&value.to_string())
287 }
288
289 fn uint(&mut self, value: u128) -> io::Result<()> {
290 self.str(&value.to_string())
291 }
292
293 /// Items of [`join`]. Defaults to them joined, as bytes.
294 fn seq(
295 &mut self,
296 items: &[&dyn DisplayBytes],
297 separator: &dyn DisplayBytes,
298 ) -> io::Result<()> {
299 let mut bytes = vec![];
300 join(items, separator).display_bytes(&mut bytes)?;
301 self.bytes(&bytes)
302 }
303
304 /// A message within another, e.g. a cause within its context. Defaults to
305 /// it written, as bytes: a [`Sink`] would rather write it through itself,
306 /// with [`Message::write_to`].
307 fn message(&mut self, message: &Message) -> io::Result<()> {
308 let mut bytes = vec![];
309 message.display_bytes(&mut bytes)?;
310 self.bytes(&bytes)
311 }
312}
313
314/// How a format string refers to an argument.
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
316pub enum ArgName {
317 /// The n-th positional argument
318 Index(usize),
319 /// A named argument, or a variable captured from scope
320 Name(&'static str),
321}
322
323/// Destination of formatting, which keeps the format string's text apart from
324/// its arguments.
325///
326/// Every [`std::io::Write`] is a `Sink` that writes both as-is.
327pub trait Sink {
328 /// Text from the format string: written by the programmer, valid UTF-8
329 fn literal(&mut self, text: &str) -> io::Result<()>;
330
331 /// A value interpolated into the format string
332 fn arg(
333 &mut self,
334 name: ArgName,
335 value: &dyn DisplayBytes,
336 ) -> io::Result<()>;
337}
338
339impl<W: io::Write + ?Sized> Sink for W {
340 fn literal(&mut self, text: &str) -> io::Result<()> {
341 self.write_all(text.as_bytes())
342 }
343
344 fn arg(&mut self, _: ArgName, value: &dyn DisplayBytes) -> io::Result<()> {
345 // `&mut Self` is `Sized`, unlike `Self`
346 value.display_bytes(&mut &mut *self)
347 }
348}
349
350/// Creates a [`Message`]: like [`format_bytes!`], but the arguments are kept,
351/// for a [`Sink`] to write later.
352///
353/// Arguments are moved into the message, so they must be `'static`, `Send`
354/// and `Sync`.
355///
356/// # Example
357///
358/// ```
359/// use format_bytes::message;
360///
361/// let rev = 42;
362/// let msg = message!("unknown revision {rev} in {path}", path = b"caf\xe9".to_vec());
363///
364/// let mut out = vec![];
365/// msg.write_to(&mut out).unwrap();
366/// assert_eq!(out, b"unknown revision 42 in caf\xe9");
367/// assert_eq!(msg.template().source, "unknown revision {rev} in {path}");
368/// ```
369#[macro_export]
370macro_rules! message {
371 ($($args: tt)*) => {
372 $crate::_message!([$crate] $($args)*)
373 };
374}
375
376/// A format string, parsed at compile time.
377#[derive(Debug)]
378pub struct Template {
379 /// The format string as written
380 pub source: &'static str,
381 pub pieces: &'static [Piece],
382 /// Names of the arguments, in order
383 pub args: &'static [ArgName],
384}
385
386/// A part of a [`Template`].
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
388pub enum Piece {
389 /// Text, with `{{` and `}}` unescaped
390 Literal(&'static str),
391 /// Index of an argument
392 Arg(usize),
393}
394
395/// A format string with its arguments, made by [`message!`].
396pub struct Message {
397 template: &'static Template,
398 args: Vec<Box<dyn DisplayBytes + Send + Sync>>,
399}
400
401impl Message {
402 #[doc(hidden)]
403 pub fn new(
404 template: &'static Template,
405 args: Vec<Box<dyn DisplayBytes + Send + Sync>>,
406 ) -> Self {
407 Self { template, args }
408 }
409
410 pub fn template(&self) -> &'static Template {
411 self.template
412 }
413
414 pub fn arg(&self, index: usize) -> &dyn DisplayBytes {
415 &*self.args[index]
416 }
417
418 /// Writes the message as [`write_bytes!`] would.
419 pub fn write_to(&self, sink: &mut dyn Sink) -> io::Result<()> {
420 for piece in self.template.pieces {
421 match *piece {
422 Piece::Literal(text) => sink.literal(text)?,
423 Piece::Arg(index) => {
424 sink.arg(self.template.args[index], self.arg(index))?
425 }
426 }
427 }
428 Ok(())
429 }
430}
431
432impl DisplayBytes for Message {
433 fn display_bytes(&self, mut output: &mut dyn io::Write) -> io::Result<()> {
434 self.write_to(&mut output)
435 }
436
437 fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
438 visitor.message(self)
439 }
440}
441
442impl fmt::Debug for Message {
443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444 let mut bytes = vec![];
445 self.write_to(&mut bytes).map_err(|_| fmt::Error)?;
446 f.debug_tuple("Message")
447 .field(&String::from_utf8_lossy(&bytes))
448 .finish()
449 }
450}
451
452macro_rules! impl_through_deref {
453 // Macro hygiene requires the `$Inner` ident to be an input
454 // so it matches corresponding idents in `$Wrapper` types:
455 ($Inner: ident => $( $Wrapper: ty, )*) => {
456 $(
457 /// Forward to the inner type.
458 impl<$Inner: ?Sized + DisplayBytes> DisplayBytes for $Wrapper {
459 #[inline]
460 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
461 (**self).display_bytes(output)
462 }
463
464 #[inline]
465 fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
466 (**self).visit(visitor)
467 }
468 }
469 )*
470 };
471}
472
473impl_through_deref! {
474 Inner =>
475 &'_ Inner,
476 &'_ mut Inner,
477 Box<Inner>,
478 std::rc::Rc<Inner>,
479 std::sync::Arc<Inner>,
480}
481
482/// Forward to the inner type.
483impl<Inner> DisplayBytes for std::borrow::Cow<'_, Inner>
484where
485 Inner: ?Sized + ToOwned + DisplayBytes,
486{
487 #[inline]
488 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
489 (**self).display_bytes(output)
490 }
491
492 #[inline]
493 fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
494 (**self).visit(visitor)
495 }
496}
497
498macro_rules! impl_for_byte_string {
499 ($($Ty: ty),+) => {
500 $(
501 /// Byte strings are "formatted" as-is.
502 impl DisplayBytes for $Ty {
503 #[inline]
504 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
505 output.write_all(self)
506 }
507 }
508 )+
509 };
510}
511
512impl_for_byte_string!([u8], Vec<u8>);
513
514macro_rules! impl_for_arrays {
515 ($( $LEN: expr )+) => {
516 impl_for_byte_string! {
517 $( [u8; $LEN] ),+
518 }
519 };
520}
521
522impl_for_arrays! {
523 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
524 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
525 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
526 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
527}
528
529/// Adaptor for types that implement `std::fmt::Display`. The Unicode output is encoded as UTF-8.
530///
531/// # Example
532///
533/// ```rust
534/// use format_bytes::{format_bytes, Utf8};
535///
536/// assert_eq!(format_bytes!(b"{}", Utf8("è_é")), b"\xc3\xa8_\xc3\xa9");
537/// ```
538pub struct Utf8<Inner>(pub Inner);
539
540impl<Inner: fmt::Display> DisplayBytes for Utf8<Inner> {
541 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
542 // Calling `Display::fmt` requires a `Formatter` which we can’t create directly,
543 // so let’s go through `write!` with an adaptor type.
544 struct Adapter<'a> {
545 output: &'a mut dyn io::Write,
546 result: io::Result<()>,
547 }
548
549 impl fmt::Write for Adapter<'_> {
550 fn write_str(&mut self, s: &str) -> fmt::Result {
551 if self.result.is_err() {
552 return Err(fmt::Error);
553 }
554 let utf8 = s.as_bytes();
555 match self.output.write_all(utf8) {
556 Ok(()) => Ok(()),
557 Err(error) => {
558 // `fmt::Error` cannot carry any data so stash the error
559 self.result = Err(error);
560 Err(fmt::Error)
561 }
562 }
563 }
564 }
565
566 let mut adapter = Adapter {
567 output,
568 result: Ok(()),
569 };
570 {
571 // `write!` requires this import: https://github.com/rust-lang/rust/issues/21826
572 use fmt::Write;
573 write!(adapter, "{}", self.0)
574 // Recover stashed error
575 .map_err(|fmt::Error| adapter.result.unwrap_err())
576 }
577 }
578
579 fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
580 visitor.str(&self.0.to_string())
581 }
582}
583
584macro_rules! impl_ascii_only {
585 ($( $Ty: ident )* => |$value: ident, $visitor: ident| $visit: expr) => {
586 $(
587 /// Format to ASCII bytes with `std::fmt::Display`.
588 ///
589 /// The `Display` impl for this type only emits ASCII characters,
590 /// so it’s less useful than in the general case
591 /// to make users explicitly opt-in to UTF-8 encoding.
592 impl DisplayBytes for $Ty {
593 #[inline]
594 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
595 Utf8(self).display_bytes(output)
596 }
597
598 #[inline]
599 fn visit(&self, $visitor: &mut dyn Visit) -> io::Result<()> {
600 let $value = *self;
601 $visit
602 }
603 }
604 )*
605 };
606}
607
608impl_ascii_only! {
609 u8 u16 u32 u64 u128 usize => |value, visitor| visitor.uint(value as u128)
610}
611
612impl_ascii_only! {
613 i8 i16 i32 i64 i128 isize => |value, visitor| visitor.int(value as i128)
614}
615
616impl_ascii_only! {
617 f32 f64 => |value, visitor| visitor.str(&value.to_string())
618}
619
620/// Format a sequence of values with the given separator repeated
621/// between any two consecutive values, but not at the start or end of the sequence.
622///
623/// It is visited with [`Visit::seq`].
624///
625/// # Example
626///
627/// ```
628/// use format_bytes::{format_bytes, join};
629///
630/// let formatted = format_bytes!(b"Got {}.", join(&[4, 3, 2], b" and "));
631/// assert_eq!(formatted, b"Got 4 and 3 and 2.");
632/// ```
633pub fn join(
634 iterable: impl IntoIterator<Item = impl DisplayBytes>,
635 separator: impl DisplayBytes,
636) -> impl DisplayBytes {
637 Join {
638 items: iterable.into_iter().collect::<Vec<_>>(),
639 separator,
640 }
641}
642
643struct Join<T, S> {
644 items: Vec<T>,
645 separator: S,
646}
647
648impl<T: DisplayBytes, S: DisplayBytes> DisplayBytes for Join<T, S> {
649 fn display_bytes(&self, output: &mut dyn io::Write) -> io::Result<()> {
650 for (i, item) in self.items.iter().enumerate() {
651 if i > 0 {
652 self.separator.display_bytes(output)?;
653 }
654 item.display_bytes(output)?;
655 }
656 Ok(())
657 }
658
659 fn visit(&self, visitor: &mut dyn Visit) -> io::Result<()> {
660 let items: Vec<&dyn DisplayBytes> =
661 self.items.iter().map(|item| item as _).collect();
662 visitor.seq(&items, &self.separator)
663 }
664}