string_builder/
lib.rs

1use std::iter;
2use std::io::Write;
3use std::string::FromUtf8Error;
4
5const DEFAULT_CAPACITY: usize = 1024;
6const MAX_UNICODE_WIDTH: usize = 4;
7
8/// This is a growable string builder.
9#[derive(Debug)]
10pub struct Builder(Vec<u8>);
11
12impl Default for Builder {
13    fn default() -> Builder {
14        let inner = Vec::with_capacity(DEFAULT_CAPACITY);
15        Builder(inner)
16    }
17}
18
19impl Builder {
20    /// Return a new `Builder` with an initial capacity.
21    pub fn new(size: usize) -> Builder {
22        let inner = Vec::with_capacity(size);
23        Builder(inner)
24    }
25
26    /// Add a type that can be viewed as a slice of bytes.
27    ///
28    /// # Example
29    ///
30    /// ```rust
31    /// use string_builder::Builder;
32    ///
33    /// let mut builder = Builder::default();
34    /// builder.append("some string");
35    /// ```
36    pub fn append<T: ToBytes>(&mut self, buf: T) {
37        self.0.write_all(&buf.to_bytes()).unwrap()
38    }
39
40    /// Return the current length in bytes of the underlying buffer.
41    ///
42    /// # Example
43    ///
44    /// ```rust
45    /// use string_builder::Builder;
46    ///
47    /// let mut builder = Builder::default();
48    /// builder.append("four");
49    /// assert_eq!(builder.len(), 4);
50    /// ```
51    pub fn len(&self) -> usize {
52        self.0.len()
53    }
54
55    /// Return a `String` of our buffer once we are done appending to it. This method consumes
56    /// the underlying buffer.
57    ///
58    /// # Example
59    ///
60    /// ```rust
61    /// use string_builder::Builder;
62    ///
63    /// let mut builder = Builder::default();
64    /// builder.append("i am building");
65    /// builder.append(' ');
66    /// builder.append("a string");
67    /// assert_eq!(builder.string().unwrap(), "i am building a string");
68    /// ```
69    pub fn string(self) -> Result<String, FromUtf8Error> {
70        String::from_utf8(self.0)
71    }
72}
73
74/// A trait to convert a value into a byte slice that can be appended to a `Builder`.
75pub trait ToBytes {
76    fn to_bytes(&self) -> Vec<u8>;
77}
78
79// Generate a buffer with the same length as the given argument in order to use `copy_from_slice`.
80fn make_copyable_buf(len: usize) -> Vec<u8> {
81    iter::repeat(0).take(len).collect::<Vec<u8>>()
82}
83
84// Copy the slice into a `Vec` with the same capacity.
85fn slice_to_vec(s: &[u8]) -> Vec<u8> {
86    let mut res = make_copyable_buf(s.len());
87    res.copy_from_slice(s);
88    res
89}
90
91impl ToBytes for String {
92    fn to_bytes(&self) -> Vec<u8> {
93        slice_to_vec(self.as_bytes())
94    }
95}
96
97impl<'a> ToBytes for &'a str {
98    fn to_bytes(&self) -> Vec<u8> {
99        slice_to_vec(self.as_bytes())
100    }
101}
102
103impl ToBytes for u8 {
104    fn to_bytes(&self) -> Vec<u8> {
105        vec![*self]
106    }
107}
108
109impl ToBytes for char {
110    fn to_bytes(&self) -> Vec<u8> {
111        // The maximum length of a unicode character is 4 bytes.
112        let mut buf = [0; MAX_UNICODE_WIDTH];
113        slice_to_vec(self.encode_utf8(&mut buf).as_bytes())
114    }
115}
116
117impl<'a> ToBytes for &'a [u8] {
118    fn to_bytes(&self) -> Vec<u8> {
119        slice_to_vec(self)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::Builder;
126
127    #[test]
128    fn test_all_supported_types() {
129        let mut b = Builder::default();
130        b.append(String::from("hello"));
131        b.append(',');
132        b.append(b' ');
133        b.append("world");
134        b.append(" it works".as_bytes());
135
136        assert_eq!(b.string().unwrap(), "hello, world it works");
137    }
138
139    #[test]
140    fn test_individual_unicode_characters() {
141        let mut b = Builder::default();
142        b.append('‘');
143        b.append("starts with and ends with");
144        b.append('‗');
145
146        assert_eq!(b.string().unwrap(), "‘starts with and ends with‗");
147    }
148
149    #[test]
150    fn test_tool_album() {
151        let mut b = Builder::default();
152        b.append('\u{00C6}');
153        b.append("nima");
154
155        assert_eq!(b.string().unwrap(), "\u{00C6}nima");
156    }
157}