#![cfg_attr(
any(feature = "const_generics", feature = "specialization"),
allow(incomplete_features)
)]
#![doc(html_root_url = "https://docs.rs/print_bytes/*")]
#![cfg_attr(feature = "const_generics", feature(const_generics))]
#![cfg_attr(feature = "min_const_generics", feature(min_const_generics))]
#![cfg_attr(print_bytes_docs_rs, feature(doc_cfg))]
#![cfg_attr(feature = "specialization", feature(specialization))]
#![warn(unused_results)]
use std::io;
use std::io::Stderr;
use std::io::StderrLock;
use std::io::Stdout;
use std::io::StdoutLock;
use std::io::Write;
mod bytes;
pub use bytes::Bytes;
use bytes::BytesInner;
pub use bytes::ToBytes;
#[cfg_attr(windows, path = "windows.rs")]
#[cfg_attr(not(windows), path = "common.rs")]
mod imp;
trait WriteBytes: Write {
fn to_console(&self) -> Option<imp::Console<'_>>;
#[inline]
fn write_bytes<TValue>(&mut self, value: &TValue) -> io::Result<()>
where
TValue: ?Sized + ToBytes,
{
let value = value.to_bytes().0;
match value {
BytesInner::Bytes(value) => {
let buffer;
let value = if self.to_console().is_some() {
buffer = String::from_utf8_lossy(value);
buffer.as_bytes()
} else {
&value
};
self.write_all(value)
}
BytesInner::OsStr(value) => imp::write_os(self, value),
}
}
}
#[cfg(feature = "specialization")]
impl<T> WriteBytes for T
where
T: ?Sized + Write,
{
default fn to_console(&self) -> Option<imp::Console<'_>> {
None
}
}
#[cfg(feature = "specialization")]
impl<'a, T> WriteBytes for &'a mut T
where
T: ?Sized + WriteBytes,
&'a mut T: Write,
{
default fn to_console(&self) -> Option<imp::Console<'_>> {
(**self).to_console()
}
}
macro_rules! r#impl {
( $($type:ty),+ ) => {
$(
impl WriteBytes for $type {
fn to_console(&self) -> Option<imp::Console<'_>> {
imp::Console::from_handle(self)
}
}
)+
};
}
r#impl!(Stderr, StderrLock<'_>, Stdout, StdoutLock<'_>);
#[cfg_attr(print_bytes_docs_rs, doc(cfg(feature = "specialization")))]
#[cfg(feature = "specialization")]
#[inline]
pub fn write_bytes<TValue, TWriter>(
mut writer: TWriter,
value: &TValue,
) -> io::Result<()>
where
TValue: ?Sized + ToBytes,
TWriter: Write,
{
writer.write_bytes(value)
}
macro_rules! r#impl {
(
$writer:expr ,
$(#[ $print_fn_attr:meta ])* $print_fn:ident ,
$(#[ $println_fn_attr:meta ])* $println_fn:ident ,
$label:literal $(,)?
) => {
#[inline]
$(#[$print_fn_attr])*
pub fn $print_fn<TValue>(value: &TValue)
where
TValue: ?Sized + ToBytes,
{
if let Err(error) = $writer.write_bytes(value) {
panic!("failed writing to {}: {}", $label, error);
}
}
#[inline]
$(#[$println_fn_attr])*
pub fn $println_fn<TValue>(value: &TValue)
where
TValue: ?Sized + ToBytes,
{
let _ = $writer.lock();
$print_fn(value);
$print_fn(&b"\n"[..]);
}
};
}
r#impl!(
io::stdout(),
print_bytes,
println_bytes,
"stdout",
);
r#impl!(
io::stderr(),
eprint_bytes,
eprintln_bytes,
"stderr",
);
#[cfg(test)]
mod tests {
use std::io;
use std::io::Write;
use super::imp;
use super::WriteBytes;
const INVALID_STRING: &[u8] = b"\xF1foo\xF1\x80bar\xF1\x80\x80";
struct Writer {
buffer: Vec<u8>,
is_console: bool,
}
impl Writer {
fn new(is_console: bool) -> Self {
Self {
buffer: Vec::new(),
is_console,
}
}
}
impl Write for Writer {
fn flush(&mut self) -> io::Result<()> {
self.buffer.flush()
}
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.buffer.write(bytes)
}
}
impl WriteBytes for Writer {
fn to_console(&self) -> Option<imp::Console<'_>> {
if self.is_console {
Some(unsafe { imp::Console::null() })
} else {
None
}
}
}
fn assert_invalid_string(writer: &Writer, lossy: bool) {
let lossy_string = String::from_utf8_lossy(INVALID_STRING);
let lossy_string = lossy_string.as_bytes();
assert_ne!(INVALID_STRING, lossy_string);
let string = &*writer.buffer;
if lossy {
assert_eq!(lossy_string, string);
} else {
assert_eq!(INVALID_STRING, string);
}
}
fn test<TWriteFn>(mut write_fn: TWriteFn) -> io::Result<()>
where
TWriteFn: FnMut(&mut Writer, &[u8]) -> io::Result<()>,
{
let mut writer = Writer::new(false);
write_fn(&mut writer, INVALID_STRING)?;
assert_invalid_string(&writer, false);
writer = Writer::new(true);
write_fn(&mut writer, INVALID_STRING)?;
assert_invalid_string(&writer, true);
Ok(())
}
#[test]
fn test_write() -> io::Result<()> {
test(WriteBytes::write_bytes)
}
#[cfg(feature = "specialization")]
#[test]
fn test_write_bytes() -> io::Result<()> {
test(|writer, bytes| super::write_bytes(writer, bytes))
}
}