1use crate::adapter::StripBytes;
2use crate::Lockable;
3use crate::RawStream;
4
5#[derive(Debug)]
7pub struct StripStream<S> {
8 raw: S,
9 state: StripBytes,
10}
11
12impl<S> StripStream<S>
13where
14 S: RawStream,
15{
16 #[inline]
18 pub fn new(raw: S) -> Self {
19 Self {
20 raw,
21 state: Default::default(),
22 }
23 }
24
25 #[inline]
27 pub fn into_inner(self) -> S {
28 self.raw
29 }
30
31 #[inline]
32 #[cfg(feature = "auto")]
33 pub fn is_terminal(&self) -> bool {
34 self.raw.is_terminal()
35 }
36}
37
38#[cfg(feature = "auto")]
39impl<S> is_terminal::IsTerminal for StripStream<S>
40where
41 S: RawStream,
42{
43 #[inline]
44 fn is_terminal(&self) -> bool {
45 self.is_terminal()
46 }
47}
48
49impl<S> std::io::Write for StripStream<S>
50where
51 S: RawStream,
52{
53 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
54 let initial_state = self.state.clone();
55
56 for printable in self.state.strip_next(buf) {
57 let possible = printable.len();
58 let written = self.raw.write(printable)?;
59 if possible != written {
60 let divergence = &printable[written..];
61 let offset = offset_to(buf, divergence);
62 let consumed = &buf[offset..];
63 self.state = initial_state;
64 self.state.strip_next(consumed).last();
65 return Ok(offset);
66 }
67 }
68 Ok(buf.len())
69 }
70 #[inline]
71 fn flush(&mut self) -> std::io::Result<()> {
72 self.raw.flush()
73 }
74
75 #[inline]
80 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
81 for printable in self.state.strip_next(buf) {
82 self.raw.write_all(printable)?;
83 }
84 Ok(())
85 }
86
87 }
89
90#[inline]
91fn offset_to(total: &[u8], subslice: &[u8]) -> usize {
92 let total = total.as_ptr();
93 let subslice = subslice.as_ptr();
94
95 debug_assert!(
96 total <= subslice,
97 "`Offset::offset_to` only accepts slices of `self`"
98 );
99 subslice as usize - total as usize
100}
101
102impl<S> Lockable for StripStream<S>
103where
104 S: Lockable,
105{
106 type Locked = StripStream<<S as Lockable>::Locked>;
107
108 #[inline]
109 fn lock(self) -> Self::Locked {
110 Self::Locked {
111 raw: self.raw.lock(),
112 state: self.state,
113 }
114 }
115}
116
117#[cfg(test)]
118mod test {
119 use super::*;
120 use proptest::prelude::*;
121 use std::io::Write as _;
122
123 proptest! {
124 #[test]
125 #[cfg_attr(miri, ignore)] fn write_all_no_escapes(s in "\\PC*") {
127 let buffer = crate::Buffer::new();
128 let mut stream = StripStream::new(buffer);
129 stream.write_all(s.as_bytes()).unwrap();
130 let buffer = stream.into_inner();
131 let actual = std::str::from_utf8(buffer.as_ref()).unwrap();
132 assert_eq!(s, actual);
133 }
134
135 #[test]
136 #[cfg_attr(miri, ignore)] fn write_byte_no_escapes(s in "\\PC*") {
138 let buffer = crate::Buffer::new();
139 let mut stream = StripStream::new(buffer);
140 for byte in s.as_bytes() {
141 stream.write_all(&[*byte]).unwrap();
142 }
143 let buffer = stream.into_inner();
144 let actual = std::str::from_utf8(buffer.as_ref()).unwrap();
145 assert_eq!(s, actual);
146 }
147
148 #[test]
149 #[cfg_attr(miri, ignore)] fn write_all_random(s in any::<Vec<u8>>()) {
151 let buffer = crate::Buffer::new();
152 let mut stream = StripStream::new(buffer);
153 stream.write_all(s.as_slice()).unwrap();
154 let buffer = stream.into_inner();
155 if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) {
156 for char in actual.chars() {
157 assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char);
158 }
159 }
160 }
161
162 #[test]
163 #[cfg_attr(miri, ignore)] fn write_byte_random(s in any::<Vec<u8>>()) {
165 let buffer = crate::Buffer::new();
166 let mut stream = StripStream::new(buffer);
167 for byte in s.as_slice() {
168 stream.write_all(&[*byte]).unwrap();
169 }
170 let buffer = stream.into_inner();
171 if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) {
172 for char in actual.chars() {
173 assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char);
174 }
175 }
176 }
177 }
178}