anstream/
strip.rs
1use crate::adapter::StripBytes;
2use crate::stream::AsLockedWrite;
3use crate::stream::IsTerminal;
4
5#[derive(Debug)]
7pub struct StripStream<S>
8where
9 S: std::io::Write,
10{
11 raw: S,
12 state: StripBytes,
13}
14
15impl<S> StripStream<S>
16where
17 S: std::io::Write,
18{
19 #[inline]
21 pub fn new(raw: S) -> Self {
22 Self {
23 raw,
24 state: Default::default(),
25 }
26 }
27
28 #[inline]
30 pub fn into_inner(self) -> S {
31 self.raw
32 }
33}
34
35impl<S> StripStream<S>
36where
37 S: std::io::Write,
38 S: IsTerminal,
39{
40 #[inline]
42 pub fn is_terminal(&self) -> bool {
43 self.raw.is_terminal()
44 }
45}
46
47impl StripStream<std::io::Stdout> {
48 #[inline]
54 pub fn lock(self) -> StripStream<std::io::StdoutLock<'static>> {
55 StripStream {
56 raw: self.raw.lock(),
57 state: self.state,
58 }
59 }
60}
61
62impl StripStream<std::io::Stderr> {
63 #[inline]
69 pub fn lock(self) -> StripStream<std::io::StderrLock<'static>> {
70 StripStream {
71 raw: self.raw.lock(),
72 state: self.state,
73 }
74 }
75}
76
77impl<S> std::io::Write for StripStream<S>
78where
79 S: std::io::Write,
80 S: AsLockedWrite,
81{
82 #[inline]
84 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
85 write(&mut self.raw.as_locked_write(), &mut self.state, buf)
86 }
87 #[inline]
88 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
89 let buf = bufs
90 .iter()
91 .find(|b| !b.is_empty())
92 .map(|b| &**b)
93 .unwrap_or(&[][..]);
94 self.write(buf)
95 }
96 #[inline]
98 fn flush(&mut self) -> std::io::Result<()> {
99 self.raw.as_locked_write().flush()
100 }
101 #[inline]
102 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
103 write_all(&mut self.raw.as_locked_write(), &mut self.state, buf)
104 }
105 #[inline]
107 fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> {
108 write_fmt(&mut self.raw.as_locked_write(), &mut self.state, args)
109 }
110}
111
112fn write(
113 raw: &mut dyn std::io::Write,
114 state: &mut StripBytes,
115 buf: &[u8],
116) -> std::io::Result<usize> {
117 let initial_state = state.clone();
118
119 for printable in state.strip_next(buf) {
120 let possible = printable.len();
121 let written = raw.write(printable)?;
122 if possible != written {
123 let divergence = &printable[written..];
124 let offset = offset_to(buf, divergence);
125 let consumed = &buf[offset..];
126 *state = initial_state;
127 state.strip_next(consumed).last();
128 return Ok(offset);
129 }
130 }
131 Ok(buf.len())
132}
133
134fn write_all(
135 raw: &mut dyn std::io::Write,
136 state: &mut StripBytes,
137 buf: &[u8],
138) -> std::io::Result<()> {
139 for printable in state.strip_next(buf) {
140 raw.write_all(printable)?;
141 }
142 Ok(())
143}
144
145fn write_fmt(
146 raw: &mut dyn std::io::Write,
147 state: &mut StripBytes,
148 args: std::fmt::Arguments<'_>,
149) -> std::io::Result<()> {
150 let write_all = |buf: &[u8]| write_all(raw, state, buf);
151 crate::fmt::Adapter::new(write_all).write_fmt(args)
152}
153
154#[inline]
155fn offset_to(total: &[u8], subslice: &[u8]) -> usize {
156 let total = total.as_ptr();
157 let subslice = subslice.as_ptr();
158
159 debug_assert!(
160 total <= subslice,
161 "`Offset::offset_to` only accepts slices of `self`"
162 );
163 subslice as usize - total as usize
164}
165
166#[cfg(test)]
167mod test {
168 use super::*;
169 use proptest::prelude::*;
170 use std::io::Write as _;
171
172 proptest! {
173 #[test]
174 #[cfg_attr(miri, ignore)] fn write_all_no_escapes(s in "\\PC*") {
176 let buffer = Vec::new();
177 let mut stream = StripStream::new(buffer);
178 stream.write_all(s.as_bytes()).unwrap();
179 let buffer = stream.into_inner();
180 let actual = std::str::from_utf8(buffer.as_ref()).unwrap();
181 assert_eq!(s, actual);
182 }
183
184 #[test]
185 #[cfg_attr(miri, ignore)] fn write_byte_no_escapes(s in "\\PC*") {
187 let buffer = Vec::new();
188 let mut stream = StripStream::new(buffer);
189 for byte in s.as_bytes() {
190 stream.write_all(&[*byte]).unwrap();
191 }
192 let buffer = stream.into_inner();
193 let actual = std::str::from_utf8(buffer.as_ref()).unwrap();
194 assert_eq!(s, actual);
195 }
196
197 #[test]
198 #[cfg_attr(miri, ignore)] fn write_all_random(s in any::<Vec<u8>>()) {
200 let buffer = Vec::new();
201 let mut stream = StripStream::new(buffer);
202 stream.write_all(s.as_slice()).unwrap();
203 let buffer = stream.into_inner();
204 if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) {
205 for char in actual.chars() {
206 assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char);
207 }
208 }
209 }
210
211 #[test]
212 #[cfg_attr(miri, ignore)] fn write_byte_random(s in any::<Vec<u8>>()) {
214 let buffer = Vec::new();
215 let mut stream = StripStream::new(buffer);
216 for byte in s.as_slice() {
217 stream.write_all(&[*byte]).unwrap();
218 }
219 let buffer = stream.into_inner();
220 if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) {
221 for char in actual.chars() {
222 assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char);
223 }
224 }
225 }
226 }
227}