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);