truncating_arraystring/
lib.rs

1pub use arrayvec::{ArrayString, CapacityError};
2use std::fmt;
3
4#[derive(Debug)]
5pub struct TruncatingArrayString<const CAP: usize>(pub ArrayString<CAP>);
6
7impl<const CAP: usize> TruncatingArrayString<CAP> {
8	pub fn new() -> Self {
9		Self(ArrayString::<CAP>::new())
10	}
11
12	pub fn try_push_str_truncate<'a>(&mut self, s: &'a str) -> Result<(), CapacityError<&'a str>> {
13		let remaining_capacity = self.0.capacity() - self.0.len();
14
15		if s.len() < remaining_capacity {
16			self.0.push_str(s);
17			Ok(())
18		} else {
19			let (fits, rest) = s.split_at(floor_char_boundary(s, remaining_capacity));
20			self.0.push_str(fits);
21			Err(CapacityError::new(rest))
22		}
23	}
24}
25
26impl<const CAP: usize> fmt::Display for TruncatingArrayString<CAP> {
27	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28		self.0.fmt(f)
29	}
30}
31
32impl<const CAP: usize> fmt::Write for TruncatingArrayString<CAP> {
33	fn write_char(&mut self, c: char) -> fmt::Result {
34		self.0.try_push(c).map_err(|_| fmt::Error)
35	}
36
37	fn write_str(&mut self, s: &str) -> fmt::Result {
38		self.try_push_str_truncate(s).map_err(|_| fmt::Error)
39	}
40}
41
42// https://doc.rust-lang.org/std/primitive.str.html#method.floor_char_boundary
43fn floor_char_boundary(s: &str, index: usize) -> usize {
44	if index >= s.len() {
45		s.len()
46	} else {
47		let lower_bound = index.saturating_sub(3);
48		let new_index = s.as_bytes()[lower_bound..=index]
49			.iter()
50			.rposition(|b| is_utf8_char_boundary(*b));
51
52		// SAFETY: we know that the character boundary will be within four bytes
53		unsafe { lower_bound + new_index.unwrap_unchecked() }
54	}
55}
56
57// https://doc.rust-lang.org/beta/src/core/num/mod.rs.html#883
58const fn is_utf8_char_boundary(b: u8) -> bool {
59	// This is bit magic equivalent to: b < 128 || b >= 192
60	(b as i8) >= -0x40
61}
62
63#[cfg(test)]
64mod tests {
65	use std::fmt::Write;
66	use super::*;
67
68	#[test]
69	fn it_truncates() {
70		let mut buf = TruncatingArrayString::<5>::new();
71		assert_eq!(write!(buf, "{}", "12"), Ok(()));
72		assert_eq!(write!(buf, "{}", "3456789"), Err(std::fmt::Error));
73		assert_eq!(&buf.0[..], "12345");
74	}
75
76	#[test]
77	fn it_truncates_at_char_boundary() {
78		let mut buf = TruncatingArrayString::<5>::new();
79		assert_eq!(write!(buf, "{}", "α"), Ok(()));
80		assert_eq!(write!(buf, "{}", "βγ"), Err(std::fmt::Error));
81		assert_eq!(&buf.0[..], "αβ");
82	}
83}