From 0deb18289a50a5e666f3be7577708824dc4aea95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20R=2E=20Miguel?= Date: Thu, 5 Jan 2023 12:46:29 -0300 Subject: [PATCH] refac: use BStr to display possibly non-UTF8 byte sequences --- Cargo.toml | 2 +- src/archive/tar.rs | 4 ++-- src/archive/zip.rs | 6 +++--- src/commands/mod.rs | 45 ++++++++++++++++++++++++----------------- src/utils/formatting.rs | 25 ++++++++++++++++++++++- src/utils/fs.rs | 6 +++--- src/utils/mod.rs | 2 +- 7 files changed, 60 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e7f533..4e7f4dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ description = "A command-line utility for easily compressing and decompressing f [dependencies] atty = "0.2.14" -bstr = "1.1.0" +bstr = { version = "1.1.0", default-features = false, features = ["std"] } bzip2 = "0.4.3" clap = { version = "4.0.32", features = ["derive", "env"] } filetime = "0.2.19" diff --git a/src/archive/tar.rs b/src/archive/tar.rs index b48d221..2adffd8 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -16,7 +16,7 @@ use crate::{ error::FinalError, info, list::FileInArchive, - utils::{self, FileVisibilityPolicy}, + utils::{self, EscapedUtf8Display, FileVisibilityPolicy}, warning, }; @@ -121,7 +121,7 @@ where // spoken text for users using screen readers, braille displays // and so on if !quiet { - info!(inaccessible, "Compressing '{}'.", utils::to_utf(path)); + info!(inaccessible, "Compressing '{}'.", EscapedUtf8Display::new(path)); } if path.is_dir() { diff --git a/src/archive/zip.rs b/src/archive/zip.rs index e48df7b..64b94ad 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -21,8 +21,8 @@ use crate::{ info, list::FileInArchive, utils::{ - self, cd_into_same_dir_as, get_invalid_utf8_paths, pretty_format_list_of_paths, strip_cur_dir, to_utf, - FileVisibilityPolicy, + self, cd_into_same_dir_as, get_invalid_utf8_paths, pretty_format_list_of_paths, strip_cur_dir, + EscapedUtf8Display, FileVisibilityPolicy, }, warning, }; @@ -191,7 +191,7 @@ where // spoken text for users using screen readers, braille displays // and so on if !quiet { - info!(inaccessible, "Compressing '{}'.", to_utf(path)); + info!(inaccessible, "Compressing '{}'.", EscapedUtf8Display::new(path)); } let metadata = match path.metadata() { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c67cebc..79e31a4 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -18,7 +18,8 @@ use crate::{ info, list::ListOptions, utils::{ - self, pretty_format_list_of_paths, to_utf, try_infer_extension, user_wants_to_continue, FileVisibilityPolicy, + self, pretty_format_list_of_paths, to_utf, try_infer_extension, user_wants_to_continue, EscapedUtf8Display, + FileVisibilityPolicy, }, warning, Opts, QuestionAction, QuestionPolicy, Subcommand, }; @@ -115,7 +116,7 @@ pub fn run( let formats = extension::extensions_from_path(&output_path); let first_format = formats.first().ok_or_else(|| { - let output_path = to_utf(&output_path); + let output_path = EscapedUtf8Display::new(&output_path); FinalError::with_title(format!("Cannot compress to '{output_path}'.")) .detail("You shall supply the compression format") .hint("Try adding supported extensions (see --help):") @@ -138,7 +139,7 @@ pub fn run( // To file.tar.bz.xz let suggested_output_path = build_archive_file_suggestion(&output_path, ".tar") .expect("output path should contain a compression format"); - let output_path = to_utf(&output_path); + let output_path = EscapedUtf8Display::new(&output_path); let first_detail_message = if is_multiple_inputs { "You are trying to compress multiple files." } else { @@ -160,21 +161,24 @@ pub fn run( } if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive()) { - let error = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) - .detail(format!("Found the format '{}' in an incorrect position.", format)) - .detail(format!( - "'{}' can only be used at the start of the file extension.", - format - )) - .hint(format!( - "If you wish to compress multiple files, start the extension with '{}'.", - format - )) - .hint(format!( - "Otherwise, remove the last '{}' from '{}'.", - format, - to_utf(&output_path) - )); + let error = FinalError::with_title(format!( + "Cannot compress to '{}'.", + EscapedUtf8Display::new(&output_path) + )) + .detail(format!("Found the format '{}' in an incorrect position.", format)) + .detail(format!( + "'{}' can only be used at the start of the file extension.", + format + )) + .hint(format!( + "If you wish to compress multiple files, start the extension with '{}'.", + format + )) + .hint(format!( + "Otherwise, remove the last '{}' from '{}'.", + format, + EscapedUtf8Display::new(&output_path) + )); return Err(error.into()); } @@ -207,7 +211,10 @@ pub fn run( // out that we left a possibly CORRUPTED file at `output_path` if utils::remove_file_or_dir(&output_path).is_err() { eprintln!("{red}FATAL ERROR:\n", red = *colors::RED); - eprintln!(" Ouch failed to delete the file '{}'.", to_utf(&output_path)); + eprintln!( + " Ouch failed to delete the file '{}'.", + EscapedUtf8Display::new(&output_path) + ); eprintln!(" Please delete it manually."); eprintln!(" This file is corrupted if compression didn't finished."); diff --git a/src/utils/formatting.rs b/src/utils/formatting.rs index 57c9bcd..2a6a13a 100644 --- a/src/utils/formatting.rs +++ b/src/utils/formatting.rs @@ -1,7 +1,30 @@ -use std::{borrow::Cow, path::Path}; +use std::{borrow::Cow, fmt::Display, path::Path}; + +use bstr::BStr; use crate::CURRENT_DIRECTORY; +/// Converts invalid UTF-8 bytes to the Unicode replacement codepoint (�) in its Display implementation. +pub struct EscapedUtf8Display<'a> { + bstr: &'a BStr, +} + +impl<'a> EscapedUtf8Display<'a> { + pub fn new(path: &'a Path) -> Self { + use std::os::unix::prelude::OsStrExt; + + let bytes = path.as_os_str().as_bytes(); + + Self { bstr: BStr::new(bytes) } + } +} + +impl Display for EscapedUtf8Display<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.bstr) + } +} + /// Converts an OsStr to utf8 with custom formatting. /// /// This is different from [`Path::display`]. diff --git a/src/utils/fs.rs b/src/utils/fs.rs index d9f577c..2141701 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -8,8 +8,8 @@ use std::{ use fs_err as fs; -use super::{to_utf, user_wants_to_overwrite}; -use crate::{extension::Extension, info, QuestionPolicy}; +use super::user_wants_to_overwrite; +use crate::{extension::Extension, info, utils::EscapedUtf8Display, QuestionPolicy}; /// Remove `path` asking the user to overwrite if necessary. /// @@ -41,7 +41,7 @@ pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> { fs::create_dir_all(path)?; // creating a directory is an important change to the file system we // should always inform the user about - info!(accessible, "directory {} created.", to_utf(path)); + info!(accessible, "directory {} created.", EscapedUtf8Display::new(path)); } Ok(()) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 61a65bd..67646db 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -10,7 +10,7 @@ mod fs; mod question; pub use file_visibility::FileVisibilityPolicy; -pub use formatting::{nice_directory_display, pretty_format_list_of_paths, strip_cur_dir, to_utf}; +pub use formatting::{nice_directory_display, pretty_format_list_of_paths, strip_cur_dir, to_utf, EscapedUtf8Display}; pub use fs::{ cd_into_same_dir_as, clear_path, create_dir_if_non_existent, is_symlink, remove_file_or_dir, try_infer_extension, };