devela/text/fmt/
buf.rs

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// devela::text::fmt::buf
//
//! Non-allocating formatting backed by a buffer.
//

use crate::{fmt_write, iif, FmtArguments, FmtError, FmtResult, TextWrite, _core::cmp::min};
#[allow(unused_imports, reason = "±unsafe")]
use ::core::str::{from_utf8, from_utf8_unchecked};

/// Returns a formatted [`str`] slice backed by a buffer, non-allocating.
///
/// It calls the [`format_buf_args`][format_buf_args()] function with the
/// [`format_args`] macro.
///
/// # Example
/// ```
/// # use devela::format_buf;
/// let mut buf = [0u8; 64];
/// let s = format_buf![&mut buf, "Test: {} {}", "foo", 42];
/// assert_eq!(Ok("Test: foo 42"), s);
/// ```
///
/// # Features
/// Makes use of the `unsafe_str` feature if enabled.
#[macro_export]
#[cfg_attr(cargo_primary_package, doc(hidden))]
macro_rules! format_buf {
    ($buf:expr, $($args:tt)*) => { $crate::format_buf_args($buf, format_args![$($args)*]) };
}
#[doc(inline)]
pub use format_buf;

/// Returns a formatted [`str`] slice backed by a buffer, non-allocating.
///
/// See also the slightly more convenient to use [`format_buf!`][crate::format_buf!] macro.
///
/// # Example
/// ```
/// # use devela::text::format_buf_args;
/// let mut buf = [0u8; 64];
/// let s = format_buf_args(&mut buf, format_args!["Test: {} {}", "foo", 42]);
/// assert_eq!(Ok("Test: foo 42"), s);
/// ```
///
/// # Features
/// Makes use of the `unsafe_str` feature if enabled.
pub fn format_buf_args<'a>(buf: &'a mut [u8], arg: FmtArguments) -> Result<&'a str, FmtError> {
    let mut w = WriteTo::new(buf);
    fmt_write(&mut w, arg)?;
    w.as_str()
}

#[derive(Debug)]
struct WriteTo<'a> {
    buf: &'a mut [u8],
    len: usize,
}
#[rustfmt::skip]
impl<'a> WriteTo<'a> {
    fn new(buf: &'a mut [u8]) -> Self { WriteTo { buf, len: 0 } }
    fn as_str(self) -> Result<&'a str, FmtError> {
        if self.len <= self.buf.len() {
            #[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
            { from_utf8(&self.buf[..self.len]).map_err(|_| FmtError) }
            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
            // SAFETY: the buffer is always filled from a previous &str
            { Ok(unsafe { from_utf8_unchecked(&self.buf[..self.len]) }) }
        } else {
            Err(FmtError)
        }
    }
}
impl TextWrite for WriteTo<'_> {
    fn write_str(&mut self, s: &str) -> FmtResult<()> {
        // IMPROVE?
        let rem = &mut self.buf[self.len..];
        let raw_s = s.as_bytes();
        let num = min(raw_s.len(), rem.len());

        rem[..num].copy_from_slice(&raw_s[..num]);
        self.len += num;

        iif![num < raw_s.len(); Err(FmtError); Ok(())]
    }
}