Skip to main content

uucore/mods/
display.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5//! Utilities for printing paths, with special attention paid to special
6//! characters and invalid unicode.
7//!
8//! For displaying paths in informational messages use `Quotable::quote`. This
9//! will wrap quotes around the filename and add the necessary escapes to make
10//! it copy/paste-able into a shell.
11//!
12//! For writing raw paths to stdout when the output should not be quoted or escaped,
13//! use `println_verbatim`. This will preserve invalid unicode.
14//!
15//! # Examples
16//! ```rust
17//! use std::path::Path;
18//! use uucore::display::{Quotable, println_verbatim};
19//!
20//! let path = Path::new("foo/bar.baz");
21//!
22//! println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'"
23//! println_verbatim(path)?; // Prints "foo/bar.baz"
24//! # Ok::<(), std::io::Error>(())
25//! ```
26
27use std::env;
28use std::ffi::OsStr;
29use std::fmt;
30use std::fs::File;
31use std::io::{self, BufWriter, Stdout, StdoutLock, Write as IoWrite};
32
33#[cfg(unix)]
34use std::os::unix::ffi::OsStrExt;
35#[cfg(target_os = "wasi")]
36use std::os::wasi::ffi::OsStrExt;
37
38// These used to be defined here, but they live in their own crate now.
39pub use os_display::{Quotable, Quoted};
40
41/// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline,
42/// without losing any information if its encoding is invalid.
43///
44/// This function is appropriate for commands where printing paths is the point and the
45/// output is likely to be captured, like `pwd` and `basename`. For informational output
46/// use `Quotable::quote`.
47///
48/// FIXME: Invalid Unicode will produce an error on Windows. That could be fixed by
49/// using low-level library calls and bypassing `io::Write`. This is not a big priority
50/// because broken filenames are much rarer on Windows than on Unix.
51pub fn println_verbatim<S: AsRef<OsStr>>(text: S) -> io::Result<()> {
52    let mut stdout = io::stdout().lock();
53    stdout.write_all_os(text.as_ref())?;
54    stdout.write_all(b"\n")?;
55    Ok(())
56}
57
58/// Like `println_verbatim`, without the trailing newline.
59pub fn print_verbatim<S: AsRef<OsStr>>(text: S) -> io::Result<()> {
60    io::stdout().write_all_os(text.as_ref())
61}
62
63/// [`io::Write`], but for OS strings.
64///
65/// On Unix this works straightforwardly.
66///
67/// On Windows this currently returns an error if the OS string is not valid Unicode.
68/// This may in the future change to allow those strings to be written to consoles.
69pub trait OsWrite: io::Write {
70    /// Write the entire OS string into this writer.
71    ///
72    /// # Errors
73    ///
74    /// An error is returned if the underlying I/O operation fails.
75    ///
76    /// On Windows, if the OS string is not valid Unicode, an error of kind
77    /// [`io::ErrorKind::InvalidData`] is returned.
78    fn write_all_os(&mut self, buf: &OsStr) -> io::Result<()> {
79        #[cfg(any(unix, target_os = "wasi"))]
80        {
81            self.write_all(buf.as_bytes())
82        }
83
84        #[cfg(not(any(unix, target_os = "wasi")))]
85        {
86            // It's possible to write a better OsWrite impl for Windows consoles (e.g. Stdout)
87            // as those are fundamentally 16-bit. If the OS string is invalid then it can be
88            // encoded to 16-bit and written using raw windows_sys calls. But this is quite involved
89            // (see `sys/pal/windows/stdio.rs` in the stdlib) and the value-add is small.
90            //
91            // There's no way to write invalid OS strings to Windows files, as those are 8-bit.
92
93            match buf.to_str() {
94                Some(text) => self.write_all(text.as_bytes()),
95                // We could output replacement characters instead, but the
96                // stdlib errors when sending invalid UTF-8 to the console,
97                // so let's follow that.
98                None => Err(io::Error::new(
99                    io::ErrorKind::InvalidData,
100                    "OS string cannot be converted to bytes",
101                )),
102            }
103        }
104    }
105}
106
107// We do not have a blanket impl for all Write because a smarter Windows impl should
108// be able to make use of AsRawHandle. Please keep this in mind when adding new impls.
109impl OsWrite for File {}
110impl OsWrite for Stdout {}
111impl OsWrite for StdoutLock<'_> {}
112// A future smarter Windows implementation can first flush the BufWriter before
113// doing a raw write.
114impl<W: OsWrite> OsWrite for BufWriter<W> {}
115
116impl OsWrite for Box<dyn OsWrite> {
117    fn write_all_os(&mut self, buf: &OsStr) -> io::Result<()> {
118        let this: &mut dyn OsWrite = self;
119        this.write_all_os(buf)
120    }
121}
122
123/// Print all environment variables in the format `name=value` with the specified line ending.
124///
125/// This function handles non-UTF-8 environment variable names and values correctly by using
126/// raw bytes on Unix systems.
127pub fn print_all_env_vars<T: fmt::Display>(line_ending: T) -> io::Result<()> {
128    let mut stdout = io::stdout().lock();
129    for (name, value) in env::vars_os() {
130        stdout.write_all_os(&name)?;
131        stdout.write_all(b"=")?;
132        stdout.write_all_os(&value)?;
133        write!(stdout, "{line_ending}")?;
134    }
135    Ok(())
136}