use crate::adapter::StripBytes;
use crate::IsTerminal;
use crate::Lockable;
use crate::RawStream;
#[derive(Debug)]
pub struct StripStream<S> {
raw: S,
state: StripBytes,
}
impl<S> StripStream<S>
where
S: RawStream,
{
#[inline]
pub fn new(raw: S) -> Self {
Self {
raw,
state: Default::default(),
}
}
#[inline]
pub fn into_inner(self) -> S {
self.raw
}
#[inline]
pub fn is_terminal(&self) -> bool {
self.raw.is_terminal()
}
}
impl<S> IsTerminal for StripStream<S>
where
S: RawStream,
{
#[inline]
fn is_terminal(&self) -> bool {
self.is_terminal()
}
}
impl<S> std::io::Write for StripStream<S>
where
S: RawStream,
{
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
write(&mut self.raw, &mut self.state, buf)
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
self.raw.flush()
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
write_all(&mut self.raw, &mut self.state, buf)
}
}
fn write(
raw: &mut dyn std::io::Write,
state: &mut StripBytes,
buf: &[u8],
) -> std::io::Result<usize> {
let initial_state = state.clone();
for printable in state.strip_next(buf) {
let possible = printable.len();
let written = raw.write(printable)?;
if possible != written {
let divergence = &printable[written..];
let offset = offset_to(buf, divergence);
let consumed = &buf[offset..];
*state = initial_state;
state.strip_next(consumed).last();
return Ok(offset);
}
}
Ok(buf.len())
}
fn write_all(
raw: &mut dyn std::io::Write,
state: &mut StripBytes,
buf: &[u8],
) -> std::io::Result<()> {
for printable in state.strip_next(buf) {
raw.write_all(printable)?;
}
Ok(())
}
#[inline]
fn offset_to(total: &[u8], subslice: &[u8]) -> usize {
let total = total.as_ptr();
let subslice = subslice.as_ptr();
debug_assert!(
total <= subslice,
"`Offset::offset_to` only accepts slices of `self`"
);
subslice as usize - total as usize
}
impl<S> Lockable for StripStream<S>
where
S: Lockable,
{
type Locked = StripStream<<S as Lockable>::Locked>;
#[inline]
fn lock(self) -> Self::Locked {
Self::Locked {
raw: self.raw.lock(),
state: self.state,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use proptest::prelude::*;
use std::io::Write as _;
proptest! {
#[test]
#[cfg_attr(miri, ignore)] fn write_all_no_escapes(s in "\\PC*") {
let buffer = crate::Buffer::new();
let mut stream = StripStream::new(buffer);
stream.write_all(s.as_bytes()).unwrap();
let buffer = stream.into_inner();
let actual = std::str::from_utf8(buffer.as_ref()).unwrap();
assert_eq!(s, actual);
}
#[test]
#[cfg_attr(miri, ignore)] fn write_byte_no_escapes(s in "\\PC*") {
let buffer = crate::Buffer::new();
let mut stream = StripStream::new(buffer);
for byte in s.as_bytes() {
stream.write_all(&[*byte]).unwrap();
}
let buffer = stream.into_inner();
let actual = std::str::from_utf8(buffer.as_ref()).unwrap();
assert_eq!(s, actual);
}
#[test]
#[cfg_attr(miri, ignore)] fn write_all_random(s in any::<Vec<u8>>()) {
let buffer = crate::Buffer::new();
let mut stream = StripStream::new(buffer);
stream.write_all(s.as_slice()).unwrap();
let buffer = stream.into_inner();
if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) {
for char in actual.chars() {
assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char);
}
}
}
#[test]
#[cfg_attr(miri, ignore)] fn write_byte_random(s in any::<Vec<u8>>()) {
let buffer = crate::Buffer::new();
let mut stream = StripStream::new(buffer);
for byte in s.as_slice() {
stream.write_all(&[*byte]).unwrap();
}
let buffer = stream.into_inner();
if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) {
for char in actual.chars() {
assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char);
}
}
}
}
}