print_bytes/
lib.rs

1//! This crate allows printing broken UTF-8 bytes to an output stream as
2//! losslessly as possible.
3//!
4//! Usually, paths are printed by calling [`Path::display`] or
5//! [`Path::to_string_lossy`] beforehand. However, both of these methods are
6//! always lossy; they misrepresent some valid paths in output. The same is
7//! true when using [`String::from_utf8_lossy`] to print any other
8//! UTF-8–like byte sequence.
9//!
10//! Instead, this crate only performs a lossy conversion when the output device
11//! is known to require Unicode, to make output as accurate as possible. When
12//! necessary, any character sequence that cannot be represented will be
13//! replaced with [`REPLACEMENT_CHARACTER`]. That convention is shared with the
14//! standard library, which uses the same character for its lossy conversion
15//! functions.
16//!
17//! ### Note: Windows Compatibility
18//!
19//! [`OsStr`] and related structs may be printed lossily on Windows. Paths are
20//! not represented using bytes on that platform, so it may be confusing to
21//! display them in that manner. Plus, the encoding most often used to account
22//! for the difference is [not permitted to be written to
23//! files][wtf8_audience], so it would not make sense for this crate to use it.
24//!
25//! Windows Console can display these paths, so this crate will output them
26//! losslessly when writing to that terminal.
27//!
28//! # Features
29//!
30//! These features are optional and can be enabled or disabled in a
31//! "Cargo.toml" file.
32//!
33//! ### Optional Features
34//!
35//! - **os\_str\_bytes** -
36//!   Provides implementations of [`ToBytes`] for:
37//!   - [`OsStr`]
38//!   - [`OsString`]
39//!   - [`Path`]
40//!   - [`PathBuf`]
41//!
42//! ### Nightly Features
43//!
44//! These features are unstable, since they rely on unstable Rust features.
45//!
46//! - **specialization** -
47//!   Provides an implementation of [`WriteLossy`] for all types.
48//!
49//! # Examples
50//!
51//! ```
52//! use std::env;
53//! # use std::io;
54//!
55//! use print_bytes::println_lossy;
56//!
57//! print!("exe: ");
58//! # #[cfg(feature = "os_str_bytes")]
59//! println_lossy(&env::current_exe()?);
60//! println!();
61//!
62//! println!("args:");
63//! for arg in env::args_os().skip(1) {
64//! #   #[cfg(feature = "os_str_bytes")]
65//!     println_lossy(&arg);
66//! }
67//! #
68//! # Ok::<_, io::Error>(())
69//! ```
70//!
71//! [`OsStr`]: ::std::ffi::OsStr
72//! [`OsString`]: ::std::ffi::OsString
73//! [`Path`]: ::std::path::Path
74//! [`Path::display`]: ::std::path::Path::display
75//! [`Path::to_string_lossy`]: ::std::path::Path::to_string_lossy
76//! [`PathBuf`]: ::std::path::PathBuf
77//! [`REPLACEMENT_CHARACTER`]: char::REPLACEMENT_CHARACTER
78//! [wtf8_audience]: https://simonsapin.github.io/wtf-8/#intended-audience
79
80#![cfg_attr(feature = "specialization", allow(incomplete_features))]
81// Only require a nightly compiler when building documentation for docs.rs.
82// This is a private option that should not be used.
83// https://github.com/rust-lang/docs.rs/issues/147#issuecomment-389544407
84#![cfg_attr(print_bytes_docs_rs, feature(doc_cfg))]
85#![cfg_attr(feature = "specialization", feature(specialization))]
86#![warn(unused_results)]
87
88use std::io;
89use std::io::Write;
90
91mod bytes;
92pub use bytes::ByteStr;
93use bytes::ByteStrInner;
94pub use bytes::ToBytes;
95#[cfg(any(doc, windows))]
96pub use bytes::WideStr;
97
98#[cfg(windows)]
99mod console;
100
101#[cfg_attr(test, macro_use)]
102mod writer;
103pub use writer::WriteLossy;
104
105#[cfg(test)]
106mod tests;
107
108/// Writes a value to a "writer".
109///
110/// This function is similar to [`write!`] but does not take a format
111/// parameter.
112///
113/// For more information, see [the module-level documentation][module].
114///
115/// # Errors
116///
117/// Returns an error if writing to the writer fails.
118///
119/// # Examples
120///
121/// ```
122/// use std::env;
123/// use std::ffi::OsStr;
124///
125/// use print_bytes::write_lossy;
126///
127/// let string = "foobar";
128/// let os_string = OsStr::new(string);
129///
130/// # #[cfg(feature = "os_str_bytes")]
131/// # {
132/// let mut lossy_string = Vec::new();
133/// write_lossy(&mut lossy_string, os_string)
134///     .expect("failed writing to vector");
135/// assert_eq!(string.as_bytes(), lossy_string);
136/// # }
137/// ```
138///
139/// [module]: self
140#[inline]
141pub fn write_lossy<T, W>(mut writer: W, value: &T) -> io::Result<()>
142where
143    T: ?Sized + ToBytes,
144    W: Write + WriteLossy,
145{
146    #[cfg(windows)]
147    let lossy = if let Some(mut console) = writer.__to_console() {
148        if let Some(string) = value.to_wide() {
149            return console.write_wide_all(&string.0);
150        }
151        true
152    } else {
153        false
154    };
155
156    #[cfg(windows)]
157    let buffer;
158    let string = value.to_bytes().0;
159    #[cfg_attr(not(windows), allow(clippy::infallible_destructuring_match))]
160    let string = match &string {
161        ByteStrInner::Bytes(string) => {
162            #[cfg(windows)]
163            if lossy {
164                buffer = String::from_utf8_lossy(string);
165                buffer.as_bytes()
166            } else {
167                string
168            }
169            #[cfg(not(windows))]
170            string
171        }
172        #[cfg(windows)]
173        ByteStrInner::Str(string) => string.as_bytes(),
174    };
175    writer.write_all(string)
176}
177
178macro_rules! expect_print {
179    ( $label:literal , $result:expr ) => {
180        $result
181            .unwrap_or_else(|x| panic!("failed writing to {}: {}", $label, x))
182    };
183}
184
185macro_rules! r#impl {
186    (
187        $writer:expr ,
188        $(#[ $print_fn_attr:meta ])* $print_fn:ident ,
189        $(#[ $println_fn_attr:meta ])* $println_fn:ident ,
190        $label:literal ,
191    ) => {
192        #[inline]
193        $(#[$print_fn_attr])*
194        pub fn $print_fn<T>(value: &T)
195        where
196            T: ?Sized + ToBytes,
197        {
198            expect_print!($label, write_lossy($writer, value));
199        }
200
201        #[inline]
202        $(#[$println_fn_attr])*
203        pub fn $println_fn<T>(value: &T)
204        where
205            T: ?Sized + ToBytes,
206        {
207            let mut writer = $writer.lock();
208            expect_print!($label, write_lossy(&mut writer, value));
209            expect_print!($label, writer.write_all(b"\n"));
210        }
211    };
212}
213r#impl!(
214    io::stderr(),
215    /// Prints a value to the standard error stream.
216    ///
217    /// This function is similar to [`eprint!`] but does not take a format
218    /// parameter.
219    ///
220    /// For more information, see [the module-level documentation][module].
221    ///
222    /// # Panics
223    ///
224    /// Panics if writing to the stream fails.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use std::env;
230    /// # use std::io;
231    ///
232    /// use print_bytes::eprint_lossy;
233    ///
234    /// # #[cfg(feature = "os_str_bytes")]
235    /// eprint_lossy(&env::current_exe()?);
236    /// #
237    /// # Ok::<_, io::Error>(())
238    /// ```
239    ///
240    /// [module]: self
241    eprint_lossy,
242    /// Prints a value to the standard error stream, followed by a newline.
243    ///
244    /// This function is similar to [`eprintln!`] but does not take a format
245    /// parameter.
246    ///
247    /// For more information, see [the module-level documentation][module].
248    ///
249    /// # Panics
250    ///
251    /// Panics if writing to the stream fails.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// use std::env;
257    /// # use std::io;
258    ///
259    /// use print_bytes::eprintln_lossy;
260    ///
261    /// # #[cfg(feature = "os_str_bytes")]
262    /// eprintln_lossy(&env::current_exe()?);
263    /// #
264    /// # Ok::<_, io::Error>(())
265    /// ```
266    ///
267    /// [module]: self
268    eprintln_lossy,
269    "stderr",
270);
271r#impl!(
272    io::stdout(),
273    /// Prints a value to the standard output stream.
274    ///
275    /// This function is similar to [`print!`] but does not take a format
276    /// parameter.
277    ///
278    /// For more information, see [the module-level documentation][module].
279    ///
280    /// # Panics
281    ///
282    /// Panics if writing to the stream fails.
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use std::env;
288    /// # use std::io;
289    ///
290    /// use print_bytes::print_lossy;
291    ///
292    /// # #[cfg(feature = "os_str_bytes")]
293    /// print_lossy(&env::current_exe()?);
294    /// #
295    /// # Ok::<_, io::Error>(())
296    /// ```
297    ///
298    /// [module]: self
299    print_lossy,
300    /// Prints a value to the standard output stream, followed by a newline.
301    ///
302    /// This function is similar to [`println!`] but does not take a format
303    /// parameter.
304    ///
305    /// For more information, see [the module-level documentation][module].
306    ///
307    /// # Panics
308    ///
309    /// Panics if writing to the stream fails.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use std::env;
315    /// # use std::io;
316    ///
317    /// use print_bytes::println_lossy;
318    ///
319    /// # #[cfg(feature = "os_str_bytes")]
320    /// println_lossy(&env::current_exe()?);
321    /// #
322    /// # Ok::<_, io::Error>(())
323    /// ```
324    ///
325    /// [module]: self
326    println_lossy,
327    "stdout",
328);