Implement accessibility mode which reduces visual noise

This commit is contained in:
Anton Hermann 2021-11-12 19:38:07 +01:00
parent 9af1106144
commit 40cee89bab
10 changed files with 65 additions and 21 deletions

View File

@ -6,8 +6,8 @@ edition = "2021"
readme = "README.md" readme = "README.md"
repository = "https://github.com/ouch-org/ouch" repository = "https://github.com/ouch-org/ouch"
license = "MIT" license = "MIT"
keywords = ["decompression", "compression", "zip", "tar", "gzip"] keywords = ["decompression", "compression", "zip", "tar", "gzip", "accessibility", "a11y"]
categories = ["command-line-utilities", "compression", "encoding"] categories = ["command-line-utilities", "compression", "encoding", "accessibility"]
description = "A command-line utility for easily compressing and decompressing files and directories." 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -24,12 +24,13 @@
# Features # Features
1. Easy to use. 1. Easy to use.
2. Automatic formats detection. 2. Accessibility (A11Y) mode via `--accessibility` or env var `ACCESSIBILITY`
3. Same usage syntax for all formats. 3. Automatic formats detection.
4. Uses encoding and decoding streams to improve performance. 4. Same usage syntax for all formats.
5. No runtime dependencies (for _Linux x86_64_). 5. Uses encoding and decoding streams to improve performance.
6. Can list archive contents with pretty tree formatting. 6. No runtime dependencies (for _Linux x86_64_).
7. Shell completions (soon!). 7. Can list archive contents with pretty tree formatting.
8. Shell completions (soon!).
# Usage # Usage

View File

@ -48,7 +48,7 @@ where
match (&*file.name()).ends_with('/') { match (&*file.name()).ends_with('/') {
_is_dir @ true => { _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)?; fs::create_dir_all(&file_path)?;
} }
_is_file @ false => { _is_file @ false => {

View File

@ -8,9 +8,14 @@ use std::{
use clap::Parser; use clap::Parser;
use fs_err as fs; use fs_err as fs;
use once_cell::sync::OnceCell;
use crate::{Opts, QuestionPolicy, Subcommand}; 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<bool> = OnceCell::new();
impl Opts { impl Opts {
/// A helper method that calls `clap::Parser::parse`. /// A helper method that calls `clap::Parser::parse`.
/// ///
@ -20,6 +25,8 @@ impl Opts {
pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> { pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> {
let mut opts = Self::parse(); let mut opts = Self::parse();
ACCESSIBLE.set(opts.accessible).unwrap();
let (Subcommand::Compress { files, .. } let (Subcommand::Compress { files, .. }
| Subcommand::Decompress { files, .. } | Subcommand::Decompress { files, .. }
| Subcommand::List { archives: files, .. }) = &mut opts.cmd; | Subcommand::List { archives: files, .. }) = &mut opts.cmd;

View File

@ -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". // 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". // Contrapositive statement: "if there is extension, then there is file_name".
info!( info!(
a11y_show,
"Partial compression detected. Compressing {} into {}", "Partial compression detected. Compressing {} into {}",
to_utf(files[0].as_path().file_name().unwrap()), to_utf(files[0].as_path().file_name().unwrap()),
to_utf(&output_path) 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); eprintln!(" Error:{reset} {}{red}.{reset}\n", err, reset = *colors::RESET, red = *colors::RED);
} }
} else { } else {
info!("Successfully compressed '{}'.", to_utf(output_path)); info!(a11y_show, "Successfully compressed '{}'.", to_utf(output_path));
} }
compress_result?; compress_result?;
@ -349,7 +350,7 @@ fn decompress_file(
utils::create_dir_if_non_existent(output_dir)?; utils::create_dir_if_non_existent(output_dir)?;
let zip_archive = zip::ZipArchive::new(reader)?; let zip_archive = zip::ZipArchive::new(reader)?;
let _files = crate::archive::zip::unpack_archive(zip_archive, output_dir, question_policy)?; 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(()); return Ok(());
} }
@ -415,8 +416,8 @@ fn decompress_file(
} }
} }
info!("Successfully decompressed archive in {}.", nice_directory_display(output_dir)); info!(a11y_show, "Successfully decompressed archive in {}.", nice_directory_display(output_dir));
info!("Files unpacked: {}", files_unpacked.len()); info!(a11y_show, "Files unpacked: {}", files_unpacked.len());
Ok(()) Ok(())
} }
@ -497,7 +498,7 @@ fn check_mime_type(
// File with no extension // File with no extension
// Try to detect it automatically and prompt the user about it // Try to detect it automatically and prompt the user about it
if let Some(detected_format) = try_infer_extension(path) { 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)? { if user_wants_to_continue_decompressing(path, question_policy)? {
format.push(detected_format); format.push(detected_format);
} else { } else {
@ -521,7 +522,7 @@ fn check_mime_type(
} else { } else {
// NOTE: If this actually produces no false positives, we can upgrade it in the future // 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. // 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(())) Ok(ControlFlow::Continue(()))

View File

@ -49,7 +49,11 @@ pub struct FinalError {
impl Display for FinalError { impl Display for FinalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Title // 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 // Details
for detail in &self.details { for detail in &self.details {

View File

@ -23,7 +23,7 @@ pub struct FileInArchive {
/// Actually print the files /// Actually print the files
pub fn list_files(archive: &Path, files: Vec<FileInArchive>, list_options: ListOptions) { pub fn list_files(archive: &Path, files: Vec<FileInArchive>, list_options: ListOptions) {
println!("{}:", archive.display()); println!("Archiv: {}", archive.display());
if list_options.tree { if list_options.tree {
let tree: Tree = files.into_iter().collect(); let tree: Tree = files.into_iter().collect();
tree.print(); 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 colors are deactivated, print final / to mark directories
if BLUE.is_empty() { if BLUE.is_empty() {
println!("{}/", name); 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 { } else {
println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET); println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
} }

View File

@ -1,12 +1,26 @@
//! Macros used on ouch. //! Macros used on ouch.
/// Macro that prints \[INFO\] messages, wraps [`println`]. /// 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_export]
macro_rules! info { macro_rules! info {
($($arg:tt)*) => { // Show info message even in ACCESSIBLE mode
$crate::macros::_info_helper(); (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)*); 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 /// Helper to display "\[INFO\]", colored yellow
@ -29,5 +43,9 @@ macro_rules! warning {
pub fn _warning_helper() { pub fn _warning_helper() {
use crate::utils::colors::{ORANGE, RESET}; 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);
}
} }

View File

@ -19,6 +19,10 @@ pub struct Opts {
#[clap(short, long)] #[clap(short, long)]
pub no: bool, pub no: bool,
/// Activate accessibility mode, reducing visual noise
#[clap(short = 'A', long, env = "ACCESSIBLE")]
pub accessible: bool,
/// Ouch and claps subcommands /// Ouch and claps subcommands
#[clap(subcommand)] #[clap(subcommand)]
pub cmd: Subcommand, pub cmd: Subcommand,

View File

@ -106,7 +106,11 @@ impl<'a> Confirmation<'a> {
// Ask the same question to end while no valid answers are given // Ask the same question to end while no valid answers are given
loop { 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()?; io::stdout().flush()?;
let mut answer = String::new(); let mut answer = String::new();