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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Implements `write_str` to get `write_fmt`, which is used in the `format!()` and
//! `format_args!()` macros. For `no_std` formatting in a bare metal environment.
//!
//! This code is based on
//! https://stackoverflow.com/questions/50200268/how-can-i-use-the-format-macro-in-a-no-std-environment
//!
//! ``` rust
//! let mut buf = [0u8; 64];
//! let s = format_no_std::show(
//!     &mut buf,
//!     format_args!("Test String {}: {}", "foo", 42),
//! ).unwrap();
//!
//! assert_eq!("Test String foo: 42", s);
//! ```

#![no_std]

use core::cmp::min;
use core::fmt;
use core::str::from_utf8;

/// A struct representing a writer that appends formatted data to a byte buffer.
pub struct WriteTo<'a> {
    buf: &'a mut [u8],
    len: usize,
}

impl<'a> WriteTo<'a> {
    /// Constructs a new `WriteTo` instance wrapping the provided byte buffer.
    pub fn new(buf: &'a mut [u8]) -> Self {
        WriteTo { buf, len: 0 }
    }

    /// Converts the written portion of the buffer into a string slice, if possible.
    pub fn as_str(self) -> Option<&'a str> {
        if self.len <= self.buf.len() {
            from_utf8(&self.buf[..self.len]).ok()
        } else {
            None
        }
    }

    /// Get the number of bytes written to buffer, unless there where errors.
    pub fn len(&self) -> Option<usize> {
        if self.len <= self.buf.len() {
            Some(self.len)
        } else {
            None
        }
    }

    /// Returns true if self has a length of zero bytes, unless there where errors.
    pub fn is_empty(&self) -> Option<bool> {
        if self.len <= self.buf.len() {
            Some(self.len == 0)
        } else {
            None
        }
    }
}

impl<'a> fmt::Write for WriteTo<'a> {
    /// Writes a string slice into the buffer, updating the length accordingly.
    fn write_str(&mut self, s: &str) -> fmt::Result {
        if self.len > self.buf.len() {
            return Err(fmt::Error);
        }

        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 += raw_s.len();

        if num < raw_s.len() {
            Err(fmt::Error)
        } else {
            Ok(())
        }
    }
}

/// Formats data using `format_args!` (`arg` argument) and writes it to a byte buffer `buf`.
pub fn show<'a>(buf: &'a mut [u8], arg: fmt::Arguments) -> Result<&'a str, fmt::Error> {
    let mut w = WriteTo::new(buf);
    fmt::write(&mut w, arg)?;
    w.as_str().ok_or(fmt::Error)
}

#[test]
fn test() {
    let mut buf = [0u8; 64];
    let s = show(&mut buf, format_args!("Test String {}: {}", "foo", 42)).unwrap();

    assert_eq!("Test String foo: 42", s);
}

#[test]
fn test_to_long() {
    let mut buf = [0u8; 8];
    let ret = show(&mut buf, format_args!("Too long string"));

    assert_eq!(Err(fmt::Error), ret);
}

#[test]
fn test_len() {
    use fmt::Write;
    let mut buf = [0u8; 64];
    let mut w = WriteTo::new(&mut buf);
    write!(&mut w, "Test String {}: {}", "foo", 42).unwrap();

    assert_eq!(w.len(), Some(19));
    assert_eq!(w.is_empty(), Some(false));
}

#[test]
fn test_len_empty() {
    use fmt::Write;
    let mut buf = [0u8; 64];
    let mut w = WriteTo::new(&mut buf);
    write!(&mut w, "").unwrap();

    assert_eq!(w.len(), Some(0));
    assert_eq!(w.is_empty(), Some(true));
}

#[test]
fn test_len_to_long() {
    use fmt::Write;
    let mut buf = [0u8; 8];
    let mut w = WriteTo::new(&mut buf);
    let res = write!(&mut w, "Tooo long string");

    assert_eq!(res, Err(core::fmt::Error));
    assert_eq!(w.len(), None);
    assert_eq!(w.is_empty(), None);
}