anstyle_stream/
strip.rs

1use crate::adapter::StripBytes;
2use crate::Lockable;
3use crate::RawStream;
4
5/// Only pass printable data to the inner `Write`
6#[derive(Debug)]
7pub struct StripStream<S> {
8    raw: S,
9    state: StripBytes,
10}
11
12impl<S> StripStream<S>
13where
14    S: RawStream,
15{
16    /// Only pass printable data to the inner `Write`
17    #[inline]
18    pub fn new(raw: S) -> Self {
19        Self {
20            raw,
21            state: Default::default(),
22        }
23    }
24
25    /// Get the wrapped [`RawStream`]
26    #[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    // Provide explicit implementations of trait methods
76    // - To reduce bookkeeping
77    // - Avoid acquiring / releasing locks in a loop
78
79    #[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    // Not bothering with `write_fmt` as it just calls `write_all`
88}
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)]  // See https://github.com/AltSysrq/proptest/issues/253
126        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)]  // See https://github.com/AltSysrq/proptest/issues/253
137        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)]  // See https://github.com/AltSysrq/proptest/issues/253
150        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)]  // See https://github.com/AltSysrq/proptest/issues/253
164        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}