From 40cee89bab2255bc428265efbb47ab90d025005d Mon Sep 17 00:00:00 2001 From: Anton Hermann Date: Fri, 12 Nov 2021 19:38:07 +0100 Subject: [PATCH] Implement accessibility mode which reduces visual noise --- Cargo.toml | 4 ++-- README.md | 13 +++++++------ src/archive/zip.rs | 2 +- src/cli.rs | 7 +++++++ src/commands.rs | 13 +++++++------ src/error.rs | 6 +++++- src/list.rs | 7 ++++++- src/macros.rs | 24 +++++++++++++++++++++--- src/opts.rs | 4 ++++ src/utils/question.rs | 6 +++++- 10 files changed, 65 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb90828..06f52a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" readme = "README.md" repository = "https://github.com/ouch-org/ouch" license = "MIT" -keywords = ["decompression", "compression", "zip", "tar", "gzip"] -categories = ["command-line-utilities", "compression", "encoding"] +keywords = ["decompression", "compression", "zip", "tar", "gzip", "accessibility", "a11y"] +categories = ["command-line-utilities", "compression", "encoding", "accessibility"] description = "A command-line utility for easily compressing and decompressing files and directories." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index bad6981..2c9bae0 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,13 @@ # Features 1. Easy to use. -2. Automatic formats detection. -3. Same usage syntax for all formats. -4. Uses encoding and decoding streams to improve performance. -5. No runtime dependencies (for _Linux x86_64_). -6. Can list archive contents with pretty tree formatting. -7. Shell completions (soon!). +2. Accessibility (A11Y) mode via `--accessibility` or env var `ACCESSIBILITY` +3. Automatic formats detection. +4. Same usage syntax for all formats. +5. Uses encoding and decoding streams to improve performance. +6. No runtime dependencies (for _Linux x86_64_). +7. Can list archive contents with pretty tree formatting. +8. Shell completions (soon!). # Usage diff --git a/src/archive/zip.rs b/src/archive/zip.rs index c71be89..38924f0 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -48,7 +48,7 @@ where match (&*file.name()).ends_with('/') { _is_dir @ true => { - println!("File {} extracted to \"{}\"", idx, file_path.display()); + info!("File {} extracted to \"{}\"", idx, file_path.display()); fs::create_dir_all(&file_path)?; } _is_file @ false => { diff --git a/src/cli.rs b/src/cli.rs index 677471f..f80135c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,9 +8,14 @@ use std::{ use clap::Parser; use fs_err as fs; +use once_cell::sync::OnceCell; use crate::{Opts, QuestionPolicy, Subcommand}; +/// Whether to enable accessible output (removes info output and reduces other +/// output, removes visual markers like '[' and ']') +pub static ACCESSIBLE: OnceCell = OnceCell::new(); + impl Opts { /// A helper method that calls `clap::Parser::parse`. /// @@ -20,6 +25,8 @@ impl Opts { pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> { let mut opts = Self::parse(); + ACCESSIBLE.set(opts.accessible).unwrap(); + let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. } | Subcommand::List { archives: files, .. }) = &mut opts.cmd; diff --git a/src/commands.rs b/src/commands.rs index 5a2dcee..ad3ed41 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -139,6 +139,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { // Path::extension says: "if there is no file_name, then there is no extension". // Contrapositive statement: "if there is extension, then there is file_name". info!( + a11y_show, "Partial compression detected. Compressing {} into {}", to_utf(files[0].as_path().file_name().unwrap()), to_utf(&output_path) @@ -159,7 +160,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { eprintln!(" Error:{reset} {}{red}.{reset}\n", err, reset = *colors::RESET, red = *colors::RED); } } else { - info!("Successfully compressed '{}'.", to_utf(output_path)); + info!(a11y_show, "Successfully compressed '{}'.", to_utf(output_path)); } compress_result?; @@ -349,7 +350,7 @@ fn decompress_file( utils::create_dir_if_non_existent(output_dir)?; let zip_archive = zip::ZipArchive::new(reader)?; let _files = crate::archive::zip::unpack_archive(zip_archive, output_dir, question_policy)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_dir)); + info!(a11y_show, "Successfully decompressed archive in {}.", nice_directory_display(output_dir)); return Ok(()); } @@ -415,8 +416,8 @@ fn decompress_file( } } - info!("Successfully decompressed archive in {}.", nice_directory_display(output_dir)); - info!("Files unpacked: {}", files_unpacked.len()); + info!(a11y_show, "Successfully decompressed archive in {}.", nice_directory_display(output_dir)); + info!(a11y_show, "Files unpacked: {}", files_unpacked.len()); Ok(()) } @@ -497,7 +498,7 @@ fn check_mime_type( // File with no extension // Try to detect it automatically and prompt the user about it if let Some(detected_format) = try_infer_extension(path) { - info!("Detected file: `{}` extension as `{}`", path.display(), detected_format); + info!(a11y_show, "Detected file: `{}` extension as `{}`", path.display(), detected_format); if user_wants_to_continue_decompressing(path, question_policy)? { format.push(detected_format); } else { @@ -521,7 +522,7 @@ fn check_mime_type( } else { // NOTE: If this actually produces no false positives, we can upgrade it in the future // to a warning and ask the user if he wants to continue decompressing. - info!("Could not detect the extension of `{}`", path.display()); + info!(a11y_show, "Could not detect the extension of `{}`", path.display()); } } Ok(ControlFlow::Continue(())) diff --git a/src/error.rs b/src/error.rs index 289efbc..d68fdaf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,7 +49,11 @@ pub struct FinalError { impl Display for FinalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Title - write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?; + if *crate::cli::ACCESSIBLE.get().unwrap_or(&false) { + write!(f, "{}ERROR{}: {}", *RED, *RESET, self.title)?; + } else { + write!(f, "{}[ERROR]{} {}", *RED, *RESET, self.title)?; + } // Details for detail in &self.details { diff --git a/src/list.rs b/src/list.rs index fceb05e..ce75970 100644 --- a/src/list.rs +++ b/src/list.rs @@ -23,7 +23,7 @@ pub struct FileInArchive { /// Actually print the files pub fn list_files(archive: &Path, files: Vec, list_options: ListOptions) { - println!("{}:", archive.display()); + println!("Archiv: {}", archive.display()); if list_options.tree { let tree: Tree = files.into_iter().collect(); tree.print(); @@ -43,6 +43,11 @@ fn print_entry(name: impl std::fmt::Display, is_dir: bool) { // if colors are deactivated, print final / to mark directories if BLUE.is_empty() { println!("{}/", name); + // if in ACCESSIBLE mode, use colors but print final / in case colors + // aren't read out aloud with a screen reader or aren't printed on a + // braille reader + } else if *crate::cli::ACCESSIBLE.get().unwrap() { + println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET); } else { println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET); } diff --git a/src/macros.rs b/src/macros.rs index 49fd87c..755987d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,12 +1,26 @@ //! Macros used on ouch. /// Macro that prints \[INFO\] messages, wraps [`println`]. +/// +/// Normally, this prints nothing if ACCESSIBLE mode is turned on, +/// except when called as `info!(a11y_show, "..", ..)` #[macro_export] macro_rules! info { - ($($arg:tt)*) => { - $crate::macros::_info_helper(); + // Show info message even in ACCESSIBLE mode + (a11y_show, $($arg:tt)*) => { + // if in ACCESSIBLE mode, suppress the "[INFO]" and just print the message + if (!$crate::cli::ACCESSIBLE.get().unwrap()) { + $crate::macros::_info_helper(); + } println!($($arg)*); }; + // Print info message if ACCESSIBLE is not turned on + ($($arg:tt)*) => { + if (!$crate::cli::ACCESSIBLE.get().unwrap()) { + $crate::macros::_info_helper(); + println!($($arg)*); + } + }; } /// Helper to display "\[INFO\]", colored yellow @@ -29,5 +43,9 @@ macro_rules! warning { pub fn _warning_helper() { use crate::utils::colors::{ORANGE, RESET}; - print!("{}[WARNING]{} ", *ORANGE, *RESET); + if !crate::cli::ACCESSIBLE.get().unwrap() { + print!("{}Warning:{} ", *ORANGE, *RESET); + } else { + print!("{}[WARNING]{} ", *ORANGE, *RESET); + } } diff --git a/src/opts.rs b/src/opts.rs index def47d9..ad08251 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -19,6 +19,10 @@ pub struct Opts { #[clap(short, long)] pub no: bool, + /// Activate accessibility mode, reducing visual noise + #[clap(short = 'A', long, env = "ACCESSIBLE")] + pub accessible: bool, + /// Ouch and claps subcommands #[clap(subcommand)] pub cmd: Subcommand, diff --git a/src/utils/question.rs b/src/utils/question.rs index 01bff85..67094d5 100644 --- a/src/utils/question.rs +++ b/src/utils/question.rs @@ -106,7 +106,11 @@ impl<'a> Confirmation<'a> { // Ask the same question to end while no valid answers are given loop { - print!("{} [{}Y{}/{}n{}] ", message, *colors::GREEN, *colors::RESET, *colors::RED, *colors::RESET); + if *crate::cli::ACCESSIBLE.get().unwrap() { + print!("{} {}yes{}/{}no{}: ", message, *colors::GREEN, *colors::RESET, *colors::RED, *colors::RESET); + } else { + print!("{} [{}Y{}/{}n{}] ", message, *colors::GREEN, *colors::RESET, *colors::RED, *colors::RESET); + } io::stdout().flush()?; let mut answer = String::new();