From 3934cc86059b06aab4d5cc298f175c9d817143d9 Mon Sep 17 00:00:00 2001 From: dylni <46035563+dylni@users.noreply.github.com> Date: Sat, 14 Jan 2023 10:34:02 -0500 Subject: [PATCH] Use the semver trick --- .github/workflows/build.yml | 2 +- Cargo.toml | 8 +- src/bytes.rs | 218 -------------------------------- src/console.rs | 90 -------------- src/lib.rs | 240 ++---------------------------------- src/tests.rs | 65 ---------- src/writer.rs | 135 -------------------- 7 files changed, 15 insertions(+), 743 deletions(-) delete mode 100644 src/bytes.rs delete mode 100644 src/console.rs delete mode 100644 src/tests.rs delete mode 100644 src/writer.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2adb25..3505895 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: pull_request: push: branches: - - master + - release-* schedule: - cron: 0 0 * * FRI diff --git a/Cargo.toml b/Cargo.toml index 42126de..c7d5ef4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "print_bytes" -version = "0.7.0" +version = "0.7.1" authors = ["dylni"] edition = "2021" rust-version = "1.57.0" @@ -19,11 +19,11 @@ all-features = true rustc-args = ["--cfg", "print_bytes_docs_rs"] rustdoc-args = ["--cfg", "print_bytes_docs_rs"] -[target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.42", features = ["Win32_Foundation", "Win32_System_Console"] } +[dependencies] +print_bytes2 = { package = "print_bytes", version = "1.0" } [dev-dependencies] os_str_bytes = "6.3" [features] -specialization = [] +specialization = ["print_bytes2/specialization"] diff --git a/src/bytes.rs b/src/bytes.rs deleted file mode 100644 index 6df1ea5..0000000 --- a/src/bytes.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::borrow::Cow; -use std::ffi::CStr; -use std::ffi::CString; -use std::ops::Deref; - -#[derive(Debug)] -pub(super) enum ByteStrInner<'a> { - Bytes(&'a [u8]), - #[cfg(windows)] - Str(Cow<'a, str>), -} - -/// A value returned by [`ToBytes::to_bytes`]. -/// -/// This struct is usually initialized by calling the above method for -/// [`[u8]`][slice]. -#[derive(Debug)] -pub struct ByteStr<'a>(pub(super) ByteStrInner<'a>); - -#[cfg(any(doc, windows))] -#[cfg_attr(print_bytes_docs_rs, doc(cfg(windows)))] -impl<'a> ByteStr<'a> { - /// Wraps a byte string lossily. - /// - /// This method can be used to implement [`ToBytes::to_bytes`] when - /// [`ToBytes::to_wide`] is the better way to represent the string. - #[inline] - #[must_use] - pub fn from_utf8_lossy(string: &'a [u8]) -> Self { - Self(ByteStrInner::Str(String::from_utf8_lossy(string))) - } -} - -/// A value returned by [`ToBytes::to_wide`]. -#[cfg(any(doc, windows))] -#[cfg_attr(print_bytes_docs_rs, doc(cfg(windows)))] -#[derive(Debug)] -pub struct WideStr(pub(super) Vec); - -#[cfg(any(doc, windows))] -impl WideStr { - /// Wraps a wide character string. - /// - /// This method can be used to implement [`ToBytes::to_wide`]. - #[inline] - #[must_use] - pub fn new(string: Vec) -> Self { - Self(string) - } -} - -/// Represents a type similarly to [`Display`]. -/// -/// Implement this trait to allow printing a type that cannot guarantee UTF-8 -/// output. It is used to bound values accepted by functions in this crate. -/// -/// # Examples -/// -/// ``` -/// use print_bytes::println_lossy; -/// use print_bytes::ByteStr; -/// use print_bytes::ToBytes; -/// #[cfg(windows)] -/// use print_bytes::WideStr; -/// -/// struct ByteSlice<'a>(&'a [u8]); -/// -/// impl ToBytes for ByteSlice<'_> { -/// fn to_bytes(&self) -> ByteStr<'_> { -/// self.0.to_bytes() -/// } -/// -/// #[cfg(windows)] -/// fn to_wide(&self) -> Option { -/// self.0.to_wide() -/// } -/// } -/// -/// println_lossy(&ByteSlice(b"Hello, world!")); -/// ``` -/// -/// [`Display`]: ::std::fmt::Display -/// [`to_bytes`]: Self::to_bytes -/// [`ToString`]: ::std::string::ToString -pub trait ToBytes { - /// Returns a byte string that will be used to represent the instance. - #[must_use] - fn to_bytes(&self) -> ByteStr<'_>; - - /// Returns a wide character string that will be used to represent the - /// instance. - /// - /// The Windows API frequently uses wide character strings. This method - /// allows them to be printed losslessly in some cases, even when they - /// cannot be converted to UTF-8. - /// - /// Returning [`None`] causes [`to_bytes`] to be used instead. - /// - /// [`to_bytes`]: Self::to_bytes - #[cfg(any(doc, windows))] - #[cfg_attr(print_bytes_docs_rs, doc(cfg(windows)))] - #[must_use] - fn to_wide(&self) -> Option; -} - -impl ToBytes for [u8] { - #[inline] - fn to_bytes(&self) -> ByteStr<'_> { - ByteStr(ByteStrInner::Bytes(self)) - } - - #[cfg(any(doc, windows))] - #[inline] - fn to_wide(&self) -> Option { - None - } -} - -macro_rules! defer_methods { - ( $convert_method:ident ) => { - #[inline] - fn to_bytes(&self) -> ByteStr<'_> { - ToBytes::to_bytes(self.$convert_method()) - } - - #[cfg(any(doc, windows))] - #[inline] - fn to_wide(&self) -> Option { - self.$convert_method().to_wide() - } - }; -} - -impl ToBytes for [u8; N] { - defer_methods!(as_slice); -} - -impl ToBytes for Cow<'_, T> -where - T: ?Sized + ToBytes + ToOwned, - T::Owned: ToBytes, -{ - defer_methods!(deref); -} - -macro_rules! defer_impl { - ( $type:ty , $convert_method:ident ) => { - impl ToBytes for $type { - defer_methods!($convert_method); - } - }; -} -defer_impl!(CStr, to_bytes); -defer_impl!(CString, as_c_str); -defer_impl!(Vec, as_slice); - -#[cfg(any( - all(target_vendor = "fortanix", target_env = "sgx"), - target_os = "hermit", - target_os = "solid_asp3", - target_os = "wasi", - unix, - windows, -))] -mod os_str { - use std::ffi::OsStr; - use std::ffi::OsString; - use std::path::Path; - use std::path::PathBuf; - - use super::ByteStr; - use super::ToBytes; - #[cfg(any(doc, windows))] - use super::WideStr; - - impl ToBytes for OsStr { - #[inline] - fn to_bytes(&self) -> ByteStr<'_> { - #[cfg(windows)] - { - use super::ByteStrInner; - - ByteStr(ByteStrInner::Str(self.to_string_lossy())) - } - #[cfg(not(windows))] - { - #[cfg(all( - target_vendor = "fortanix", - target_env = "sgx", - ))] - use std::os::fortanix_sgx as os; - #[cfg(target_os = "solid_asp3")] - use std::os::solid as os; - #[cfg(any(target_os = "hermit", unix))] - use std::os::unix as os; - #[cfg(target_os = "wasi")] - use std::os::wasi as os; - - use os::ffi::OsStrExt; - - self.as_bytes().to_bytes() - } - } - - #[cfg(any(doc, windows))] - #[inline] - fn to_wide(&self) -> Option { - #[cfg(windows)] - use std::os::windows::ffi::OsStrExt; - - Some(WideStr(self.encode_wide().collect())) - } - } - - defer_impl!(OsString, as_os_str); - defer_impl!(Path, as_os_str); - defer_impl!(PathBuf, as_path); -} diff --git a/src/console.rs b/src/console.rs deleted file mode 100644 index 3cd658f..0000000 --- a/src/console.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::convert::TryInto; -use std::io; -use std::marker::PhantomData; -use std::os::windows::io::AsRawHandle; -use std::ptr; - -use windows_sys::Win32::Foundation::BOOL; -use windows_sys::Win32::Foundation::HANDLE; -use windows_sys::Win32::System::Console::GetConsoleMode; -use windows_sys::Win32::System::Console::WriteConsoleW; - -const TRUE: BOOL = 1; - -#[derive(Clone, Copy)] -pub struct Console<'a> { - handle: HANDLE, - _marker: PhantomData<&'a ()>, -} - -impl<'a> Console<'a> { - pub(super) fn from_handle(handle: &'a T) -> Option - where - T: AsRawHandle + ?Sized, - { - let handle = handle.as_raw_handle(); - if handle.is_null() { - return None; - } - let handle = handle as _; - - // The mode is not important, since this call only succeeds for Windows - // Console. Other streams usually do not require Unicode writes. - let mut mode = 0; - (unsafe { GetConsoleMode(handle, &mut mode) } == TRUE).then(|| Self { - handle, - _marker: PhantomData, - }) - } - - // Writing to the returned instance causes undefined behavior. - #[cfg(test)] - pub(super) const unsafe fn null() -> Self { - Self { - handle: 0, - _marker: PhantomData, - } - } - - fn write_wide(&mut self, string: &[u16]) -> io::Result { - let length = string.len().try_into().unwrap_or(u32::MAX); - let mut written_length = 0; - let result = unsafe { - WriteConsoleW( - self.handle, - string.as_ptr().cast(), - length, - &mut written_length, - ptr::null_mut(), - ) - }; - (result == TRUE) - .then(|| written_length as usize) - .ok_or_else(io::Error::last_os_error) - } - - pub(super) fn write_wide_all( - &mut self, - mut string: &[u16], - ) -> io::Result<()> { - while !string.is_empty() { - match self.write_wide(string) { - Ok(written_length) => { - if written_length == 0 { - return Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to write whole buffer", - )); - } - string = &string[written_length..]; - } - Err(error) => { - if error.kind() != io::ErrorKind::Interrupted { - return Err(error); - } - } - } - } - Ok(()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 46d142e..cdecf9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,236 +72,16 @@ all(target_vendor = "fortanix", target_env = "sgx"), feature(sgx_platform) )] -#![cfg_attr(feature = "specialization", feature(specialization))] #![warn(unused_results)] -use std::io; -use std::io::Write; - -mod bytes; -pub use bytes::ByteStr; -use bytes::ByteStrInner; -pub use bytes::ToBytes; -#[cfg(any(doc, windows))] -pub use bytes::WideStr; - +pub use print_bytes2::eprint_lossy; +pub use print_bytes2::eprintln_lossy; +pub use print_bytes2::print_lossy; +pub use print_bytes2::println_lossy; +pub use print_bytes2::write_lossy; +pub use print_bytes2::ByteStr; +pub use print_bytes2::ToBytes; #[cfg(windows)] -mod console; - -#[cfg_attr(test, macro_use)] -mod writer; -pub use writer::WriteLossy; - -#[cfg(test)] -mod tests; - -/// Writes a value to a "writer". -/// -/// This function is similar to [`write!`] but does not take a format -/// parameter. -/// -/// For more information, see [the module-level documentation][module]. -/// -/// # Errors -/// -/// Returns an error if writing to the writer fails. -/// -/// # Examples -/// -/// ``` -/// use std::env; -/// use std::ffi::OsStr; -/// -/// use print_bytes::write_lossy; -/// -/// let string = "foobar"; -/// let os_string = OsStr::new(string); -/// -/// let mut lossy_string = Vec::new(); -/// write_lossy(&mut lossy_string, os_string) -/// .expect("failed writing to vector"); -/// assert_eq!(string.as_bytes(), lossy_string); -/// ``` -/// -/// [module]: self -#[inline] -pub fn write_lossy(mut writer: W, value: &T) -> io::Result<()> -where - T: ?Sized + ToBytes, - W: Write + WriteLossy, -{ - #[cfg_attr(not(windows), allow(unused_mut))] - let mut lossy = false; - #[cfg(windows)] - if let Some(mut console) = writer.__to_console() { - if let Some(string) = value.to_wide() { - return console.write_wide_all(&string.0); - } - lossy = true; - } - - let buffer; - let string = value.to_bytes(); - let string = match &string.0 { - ByteStrInner::Bytes(string) => { - if lossy { - buffer = String::from_utf8_lossy(string); - buffer.as_bytes() - } else { - string - } - } - #[cfg(windows)] - ByteStrInner::Str(string) => string.as_bytes(), - }; - writer.write_all(string) -} - -macro_rules! expect_print { - ( $label:literal , $result:expr ) => { - $result - .unwrap_or_else(|x| panic!("failed writing to {}: {}", $label, x)) - }; -} - -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(value: &T) - where - T: ?Sized + ToBytes, - { - expect_print!($label, write_lossy($writer, value)); - } - - #[inline] - $(#[$println_fn_attr])* - pub fn $println_fn(value: &T) - where - T: ?Sized + ToBytes, - { - let writer = $writer; - let mut writer = writer.lock(); - expect_print!($label, write_lossy(&mut writer, value)); - expect_print!($label, writer.write_all(b"\n")); - } - }; -} -r#impl!( - io::stderr(), - /// Prints a value to the standard error stream. - /// - /// This function is similar to [`eprint!`] but does not take a format - /// parameter. - /// - /// For more information, see [the module-level documentation][module]. - /// - /// # Panics - /// - /// Panics if writing to the stream fails. - /// - /// # Examples - /// - /// ``` - /// use std::env; - /// # use std::io; - /// - /// use print_bytes::eprint_lossy; - /// - /// eprint_lossy(&env::current_exe()?); - /// # - /// # Ok::<_, io::Error>(()) - /// ``` - /// - /// [module]: self - eprint_lossy, - /// Prints a value to the standard error stream, followed by a newline. - /// - /// This function is similar to [`eprintln!`] but does not take a format - /// parameter. - /// - /// For more information, see [the module-level documentation][module]. - /// - /// # Panics - /// - /// Panics if writing to the stream fails. - /// - /// # Examples - /// - /// ``` - /// use std::env; - /// # use std::io; - /// - /// use print_bytes::eprintln_lossy; - /// - /// eprintln_lossy(&env::current_exe()?); - /// # - /// # Ok::<_, io::Error>(()) - /// ``` - /// - /// [module]: self - eprintln_lossy, - "stderr", -); -r#impl!( - io::stdout(), - /// Prints a value to the standard output stream. - /// - /// This function is similar to [`print!`] but does not take a format - /// parameter. - /// - /// For more information, see [the module-level documentation][module]. - /// - /// # Panics - /// - /// Panics if writing to the stream fails. - /// - /// # Examples - /// - /// ``` - /// use std::env; - /// # use std::io; - /// - /// use print_bytes::print_lossy; - /// - /// print_lossy(&env::current_exe()?); - /// # - /// # Ok::<_, io::Error>(()) - /// ``` - /// - /// [module]: self - print_lossy, - /// Prints a value to the standard output stream, followed by a newline. - /// - /// This function is similar to [`println!`] but does not take a format - /// parameter. - /// - /// For more information, see [the module-level documentation][module]. - /// - /// # Panics - /// - /// Panics if writing to the stream fails. - /// - /// # Examples - /// - /// ``` - /// use std::env; - /// # use std::io; - /// - /// use print_bytes::println_lossy; - /// - /// println_lossy(&env::current_exe()?); - /// # - /// # Ok::<_, io::Error>(()) - /// ``` - /// - /// [module]: self - println_lossy, - "stdout", -); +#[cfg_attr(print_bytes_docs_rs, doc(cfg(windows)))] +pub use print_bytes2::WideStr; +pub use print_bytes2::WriteLossy; diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 7a06e12..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![cfg(windows)] - -use std::io; -use std::io::Write; - -use super::console::Console; - -const INVALID_STRING: &[u8] = b"\xF1foo\xF1\x80bar\xF1\x80\x80"; - -struct Writer { - buffer: Vec, - is_console: bool, -} - -impl Writer { - const fn new(is_console: bool) -> Self { - Self { - buffer: Vec::new(), - is_console, - } - } -} - -impl Write for Writer { - fn write(&mut self, bytes: &[u8]) -> io::Result { - self.buffer.write(bytes) - } - - fn flush(&mut self) -> io::Result<()> { - self.buffer.flush() - } -} - -impl_to_console! { - Writer, - // SAFETY: Since no platform strings are being written, no test should ever - // write to this console. - |x| x.is_console.then(|| unsafe { Console::null() }), -} - -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); - } -} - -#[test] -fn test_write_lossy() -> io::Result<()> { - let mut writer = Writer::new(false); - super::write_lossy(&mut writer, INVALID_STRING)?; - assert_invalid_string(&writer, false); - - writer = Writer::new(true); - super::write_lossy(&mut writer, INVALID_STRING)?; - assert_invalid_string(&writer, true); - - Ok(()) -} diff --git a/src/writer.rs b/src/writer.rs deleted file mode 100644 index a417eb4..0000000 --- a/src/writer.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::io::BufWriter; -use std::io::LineWriter; -#[cfg(any(doc, not(feature = "specialization")))] -use std::io::Stderr; -#[cfg(any(doc, not(feature = "specialization")))] -use std::io::StderrLock; -#[cfg(any(doc, not(feature = "specialization")))] -use std::io::Stdout; -#[cfg(any(doc, not(feature = "specialization")))] -use std::io::StdoutLock; -use std::io::Write; -#[cfg(all(feature = "specialization", windows))] -use std::os::windows::io::AsRawHandle; - -#[cfg(windows)] -use super::console::Console; - -pub(super) trait ToConsole { - #[cfg(windows)] - fn to_console(&self) -> Option>; -} - -#[cfg(feature = "specialization")] -impl ToConsole for T -where - T: ?Sized, -{ - #[cfg(windows)] - default fn to_console(&self) -> Option> { - None - } -} - -#[cfg(all(feature = "specialization", windows))] -impl ToConsole for T -where - T: AsRawHandle + ?Sized + Write, -{ - fn to_console(&self) -> Option> { - Console::from_handle(self) - } -} - -/// A bound for [`write_lossy`] that allows it to be used for some types -/// without specialization. -/// -/// When the "specialization" feature is enabled, this trait is implemented for -/// all types. -/// -/// [`write_lossy`]: super::write_lossy -pub trait WriteLossy { - #[cfg(windows)] - #[doc(hidden)] - fn __to_console(&self) -> Option>; -} - -#[cfg(feature = "specialization")] -#[cfg_attr(print_bytes_docs_rs, doc(cfg(feature = "specialization")))] -impl WriteLossy for T -where - T: ?Sized, -{ - #[cfg(windows)] - default fn __to_console(&self) -> Option> { - self.to_console() - } -} - -macro_rules! r#impl { - ( $generic:ident , $type:ty ) => { - impl<$generic> WriteLossy for $type - where - $generic: ?Sized + WriteLossy, - { - #[cfg(windows)] - fn __to_console(&self) -> Option> { - (**self).__to_console() - } - } - }; -} -r#impl!(T, &mut T); -r#impl!(T, Box); - -macro_rules! r#impl { - ( $generic:ident , $type:ty ) => { - impl<$generic> WriteLossy for $type - where - $generic: Write + WriteLossy, - { - #[cfg(windows)] - fn __to_console(&self) -> Option> { - self.get_ref().__to_console() - } - } - }; -} -r#impl!(T, BufWriter); -r#impl!(T, LineWriter); - -macro_rules! impl_to_console { - ( $(#[ $attr:meta ])* $type:ty , $to_console_fn:expr , ) => { - #[cfg(any(doc, not(feature = "specialization")))] - impl $crate::WriteLossy for $type { - #[cfg(windows)] - fn __to_console(&self) -> Option> { - $crate::writer::ToConsole::to_console(self) - } - } - - $(#[$attr])* - impl $crate::writer::ToConsole for $type { - #[cfg(windows)] - fn to_console<'a>(&'a self) -> Option> { - let to_console_fn: fn(&'a Self) -> _ = $to_console_fn; - to_console_fn(self) - } - } - }; -} - -macro_rules! r#impl { - ( $($type:ty),+ ) => { - $(impl_to_console! { - #[cfg(not(feature = "specialization"))] - $type, Console::from_handle, - })+ - }; -} -r#impl!(Stderr, StderrLock<'_>, Stdout, StdoutLock<'_>); - -impl_to_console! { - #[cfg(not(feature = "specialization"))] - Vec, |_| None, -}