mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-08 20:45:29 +00:00
Merge pull request #179 from ouch-org/organizing-utils
Organizing utils
This commit is contained in:
commit
fe6913118d
@ -22,7 +22,7 @@ use crate::{
|
|||||||
info,
|
info,
|
||||||
list::{self, ListOptions},
|
list::{self, ListOptions},
|
||||||
utils::{
|
utils::{
|
||||||
self, concatenate_list_of_os_str, dir_is_empty, nice_directory_display, to_utf, try_infer_extension,
|
self, concatenate_os_str_list, dir_is_empty, nice_directory_display, to_utf, try_infer_extension,
|
||||||
user_wants_to_continue_decompressing,
|
user_wants_to_continue_decompressing,
|
||||||
},
|
},
|
||||||
warning, Opts, QuestionPolicy, Subcommand,
|
warning, Opts, QuestionPolicy, Subcommand,
|
||||||
@ -189,7 +189,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
|||||||
let error = FinalError::with_title("Cannot decompress files without extensions")
|
let error = FinalError::with_title("Cannot decompress files without extensions")
|
||||||
.detail(format!(
|
.detail(format!(
|
||||||
"Files without supported extensions: {}",
|
"Files without supported extensions: {}",
|
||||||
concatenate_list_of_os_str(&files_missing_format)
|
concatenate_os_str_list(&files_missing_format)
|
||||||
))
|
))
|
||||||
.detail("Decompression formats are detected automatically by the file extension")
|
.detail("Decompression formats are detected automatically by the file extension")
|
||||||
.hint("Provide a file with a supported extension:")
|
.hint("Provide a file with a supported extension:")
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
//! Pretty (and colored) dialog for asking [Y/n] for the end user.
|
|
||||||
//!
|
|
||||||
//! Example:
|
|
||||||
//! "Do you want to overwrite 'archive.tar.gz'? [Y/n]"
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
borrow::Cow,
|
|
||||||
io::{self, Write},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::utils::colors;
|
|
||||||
|
|
||||||
/// Confirmation dialog for end user with [Y/n] question.
|
|
||||||
///
|
|
||||||
/// If the placeholder is found in the prompt text, it will be replaced to form the final message.
|
|
||||||
pub struct Confirmation<'a> {
|
|
||||||
/// The message to be displayed with the placeholder text in it.
|
|
||||||
/// e.g.: "Do you want to overwrite 'FILE'?"
|
|
||||||
pub prompt: &'a str,
|
|
||||||
|
|
||||||
/// The placeholder text that will be replaced in the `ask` function:
|
|
||||||
/// e.g.: Some("FILE")
|
|
||||||
pub placeholder: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Confirmation<'a> {
|
|
||||||
/// Creates a new Confirmation.
|
|
||||||
pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
|
||||||
Self { prompt, placeholder: pattern }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates user message and receives a boolean input to be used on the program
|
|
||||||
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
|
||||||
let message = match (self.placeholder, substitute) {
|
|
||||||
(None, _) => Cow::Borrowed(self.prompt),
|
|
||||||
(Some(_), None) => unreachable!("dev error, should be reported, we checked this won't happen"),
|
|
||||||
(Some(placeholder), Some(subs)) => Cow::Owned(self.prompt.replace(placeholder, subs)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
io::stdout().flush()?;
|
|
||||||
|
|
||||||
let mut answer = String::new();
|
|
||||||
io::stdin().read_line(&mut answer)?;
|
|
||||||
|
|
||||||
answer.make_ascii_lowercase();
|
|
||||||
match answer.trim() {
|
|
||||||
"" | "y" | "yes" => return Ok(true),
|
|
||||||
"n" | "no" => return Ok(false),
|
|
||||||
_ => continue, // Try again
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
//! This library isn't meant to be published, but used internally by our binary crate `main.rs`.
|
//! This library is just meant to supply needs for the `ouch` binary crate.
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
@ -8,7 +8,6 @@ pub mod macros;
|
|||||||
pub mod archive;
|
pub mod archive;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod dialogs;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod extension;
|
pub mod extension;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
36
src/utils/colors.rs
Normal file
36
src/utils/colors.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//! Colored output in ouch with bright colors.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
static DISABLE_COLORED_TEXT: Lazy<bool> = Lazy::new(|| {
|
||||||
|
env::var_os("NO_COLOR").is_some() || atty::isnt(atty::Stream::Stdout) || atty::isnt(atty::Stream::Stderr)
|
||||||
|
});
|
||||||
|
|
||||||
|
macro_rules! color {
|
||||||
|
($name:ident = $value:literal) => {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
/// Inserts color onto text based on configuration
|
||||||
|
pub static $name: Lazy<&str> = Lazy::new(|| if *DISABLE_COLORED_TEXT { "" } else { $value });
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
pub static $name: &&str = &"";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
color!(RESET = "\u{1b}[39m");
|
||||||
|
color!(BLACK = "\u{1b}[38;5;8m");
|
||||||
|
color!(BLUE = "\u{1b}[38;5;12m");
|
||||||
|
color!(CYAN = "\u{1b}[38;5;14m");
|
||||||
|
color!(GREEN = "\u{1b}[38;5;10m");
|
||||||
|
color!(MAGENTA = "\u{1b}[38;5;13m");
|
||||||
|
color!(RED = "\u{1b}[38;5;9m");
|
||||||
|
color!(WHITE = "\u{1b}[38;5;15m");
|
||||||
|
color!(YELLOW = "\u{1b}[38;5;11m");
|
||||||
|
// Requires true color support
|
||||||
|
color!(ORANGE = "\u{1b}[38;2;255;165;0m");
|
||||||
|
color!(STYLE_BOLD = "\u{1b}[1m");
|
||||||
|
color!(STYLE_RESET = "\u{1b}[0m");
|
||||||
|
color!(ALL_RESET = "\u{1b}[0;39m");
|
@ -1,4 +1,51 @@
|
|||||||
use std::cmp;
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
cmp,
|
||||||
|
ffi::OsStr,
|
||||||
|
path::{Component, Path},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Converts an OsStr to utf8 with custom formatting.
|
||||||
|
///
|
||||||
|
/// This is different from [`Path::display`].
|
||||||
|
///
|
||||||
|
/// See https://gist.github.com/marcospb19/ebce5572be26397cf08bbd0fd3b65ac1 for a comparison.
|
||||||
|
pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
|
||||||
|
let text = format!("{:?}", os_str.as_ref());
|
||||||
|
text.trim_matches('"').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the current dir from the beginning of a path
|
||||||
|
/// normally used for presentation sake.
|
||||||
|
/// If this function fails, it will return source path as a PathBuf.
|
||||||
|
pub fn strip_cur_dir(source_path: &Path) -> &Path {
|
||||||
|
source_path.strip_prefix(Component::CurDir).unwrap_or(source_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a slice of AsRef<OsStr> to comma separated String
|
||||||
|
///
|
||||||
|
/// Panics if the slice is empty.
|
||||||
|
pub fn concatenate_os_str_list(os_strs: &[impl AsRef<OsStr>]) -> String {
|
||||||
|
let mut iter = os_strs.iter().map(AsRef::as_ref);
|
||||||
|
|
||||||
|
let mut string = to_utf(iter.next().unwrap()); // May panic
|
||||||
|
|
||||||
|
for os_str in iter {
|
||||||
|
string += ", ";
|
||||||
|
string += &to_utf(os_str);
|
||||||
|
}
|
||||||
|
string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display the directory name, but change to "current directory" when necessary.
|
||||||
|
pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> Cow<'static, str> {
|
||||||
|
if os_str.as_ref() == "." {
|
||||||
|
Cow::Borrowed("current directory")
|
||||||
|
} else {
|
||||||
|
let text = to_utf(os_str);
|
||||||
|
Cow::Owned(format!("'{}'", text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Struct useful to printing bytes as kB, MB, GB, etc.
|
/// Struct useful to printing bytes as kB, MB, GB, etc.
|
||||||
pub struct Bytes {
|
pub struct Bytes {
|
@ -1,26 +1,25 @@
|
|||||||
//! Random filesystem-related stuff used on ouch.
|
//! Filesystem utility functions.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
env,
|
env,
|
||||||
ffi::OsStr,
|
|
||||||
fs::ReadDir,
|
fs::ReadDir,
|
||||||
io::Read,
|
io::Read,
|
||||||
path::{Component, Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
|
||||||
|
use super::to_utf;
|
||||||
use crate::{extension::Extension, info};
|
use crate::{extension::Extension, info};
|
||||||
|
|
||||||
/// Checks given path points to an empty directory.
|
/// Checks if given path points to an empty directory.
|
||||||
pub fn dir_is_empty(dir_path: &Path) -> bool {
|
pub fn dir_is_empty(dir_path: &Path) -> bool {
|
||||||
let is_empty = |mut rd: ReadDir| rd.next().is_none();
|
let is_empty = |mut rd: ReadDir| rd.next().is_none();
|
||||||
|
|
||||||
dir_path.read_dir().map(is_empty).unwrap_or_default()
|
dir_path.read_dir().map(is_empty).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the dir if non existent.
|
/// Creates a directory at the path, if there is nothing there.
|
||||||
pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
fs::create_dir_all(path)?;
|
fs::create_dir_all(path)?;
|
||||||
@ -29,13 +28,6 @@ pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the current dir from the beginning of a path
|
|
||||||
/// normally used for presentation sake.
|
|
||||||
/// If this function fails, it will return source path as a PathBuf.
|
|
||||||
pub fn strip_cur_dir(source_path: &Path) -> &Path {
|
|
||||||
source_path.strip_prefix(Component::CurDir).unwrap_or(source_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns current directory, but before change the process' directory to the
|
/// Returns current directory, but before change the process' directory to the
|
||||||
/// one that contains the file pointed to by `filename`.
|
/// one that contains the file pointed to by `filename`.
|
||||||
pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
|
pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
|
||||||
@ -47,41 +39,6 @@ pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
|
|||||||
Ok(previous_location)
|
Ok(previous_location)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an OsStr to utf8 with custom formatting.
|
|
||||||
///
|
|
||||||
/// This is different from [`Path::display`].
|
|
||||||
///
|
|
||||||
/// See for a comparison.
|
|
||||||
pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
|
|
||||||
let text = format!("{:?}", os_str.as_ref());
|
|
||||||
text.trim_matches('"').to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a slice of AsRef<OsStr> to comma separated String
|
|
||||||
///
|
|
||||||
/// Panics if the slice is empty.
|
|
||||||
pub fn concatenate_list_of_os_str(os_strs: &[impl AsRef<OsStr>]) -> String {
|
|
||||||
let mut iter = os_strs.iter().map(AsRef::as_ref);
|
|
||||||
|
|
||||||
let mut string = to_utf(iter.next().unwrap()); // May panic
|
|
||||||
|
|
||||||
for os_str in iter {
|
|
||||||
string += ", ";
|
|
||||||
string += &to_utf(os_str);
|
|
||||||
}
|
|
||||||
string
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display the directory name, but change to "current directory" when necessary.
|
|
||||||
pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> Cow<'static, str> {
|
|
||||||
if os_str.as_ref() == "." {
|
|
||||||
Cow::Borrowed("current directory")
|
|
||||||
} else {
|
|
||||||
let text = to_utf(os_str);
|
|
||||||
Cow::Owned(format!("'{}'", text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to detect the file extension by looking for known magic strings
|
/// Try to detect the file extension by looking for known magic strings
|
||||||
/// Source: https://en.wikipedia.org/wiki/List_of_file_signatures
|
/// Source: https://en.wikipedia.org/wiki/List_of_file_signatures
|
||||||
pub fn try_infer_extension(path: &Path) -> Option<Extension> {
|
pub fn try_infer_extension(path: &Path) -> Option<Extension> {
|
||||||
@ -146,40 +103,3 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Module with a list of bright colors.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub mod colors {
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
static DISABLE_COLORED_TEXT: Lazy<bool> = Lazy::new(|| {
|
|
||||||
env::var_os("NO_COLOR").is_some() || atty::isnt(atty::Stream::Stdout) || atty::isnt(atty::Stream::Stderr)
|
|
||||||
});
|
|
||||||
|
|
||||||
macro_rules! color {
|
|
||||||
($name:ident = $value:literal) => {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
/// Inserts color onto text based on configuration
|
|
||||||
pub static $name: Lazy<&str> = Lazy::new(|| if *DISABLE_COLORED_TEXT { "" } else { $value });
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
|
||||||
pub static $name: &&str = &"";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
color!(RESET = "\u{1b}[39m");
|
|
||||||
color!(BLACK = "\u{1b}[38;5;8m");
|
|
||||||
color!(BLUE = "\u{1b}[38;5;12m");
|
|
||||||
color!(CYAN = "\u{1b}[38;5;14m");
|
|
||||||
color!(GREEN = "\u{1b}[38;5;10m");
|
|
||||||
color!(MAGENTA = "\u{1b}[38;5;13m");
|
|
||||||
color!(RED = "\u{1b}[38;5;9m");
|
|
||||||
color!(WHITE = "\u{1b}[38;5;15m");
|
|
||||||
color!(YELLOW = "\u{1b}[38;5;11m");
|
|
||||||
// Requires true color support
|
|
||||||
color!(ORANGE = "\u{1b}[38;2;255;165;0m");
|
|
||||||
color!(STYLE_BOLD = "\u{1b}[1m");
|
|
||||||
color!(STYLE_RESET = "\u{1b}[0m");
|
|
||||||
color!(ALL_RESET = "\u{1b}[0;39m");
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
//! Random filesystem-related stuff used on ouch.
|
//! Random and miscellaneous utils used in ouch.
|
||||||
|
//!
|
||||||
|
//! In here we have the logic for custom formatting, some file and directory utils, and user
|
||||||
|
//! stdin interaction helpers.
|
||||||
|
|
||||||
mod bytes;
|
pub mod colors;
|
||||||
|
mod formatting;
|
||||||
mod fs;
|
mod fs;
|
||||||
mod question_policy;
|
mod question;
|
||||||
|
|
||||||
pub use bytes::Bytes;
|
pub use formatting::{concatenate_os_str_list, nice_directory_display, strip_cur_dir, to_utf, Bytes};
|
||||||
pub use fs::*;
|
pub use fs::{cd_into_same_dir_as, create_dir_if_non_existent, dir_is_empty, try_infer_extension};
|
||||||
pub use question_policy::*;
|
pub use question::{
|
||||||
|
create_or_ask_overwrite, user_wants_to_continue_decompressing, user_wants_to_overwrite, QuestionPolicy,
|
||||||
|
};
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
use std::path::Path;
|
//! Utils related to asking [Y/n] questions to the user.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! "Do you want to overwrite 'archive.tar.gz'? [Y/n]"
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
io::{self, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
|
||||||
use super::{strip_cur_dir, to_utf};
|
use super::{strip_cur_dir, to_utf};
|
||||||
use crate::{
|
use crate::{
|
||||||
dialogs::Confirmation,
|
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
|
utils::colors,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
@ -67,3 +76,48 @@ pub fn user_wants_to_continue_decompressing(path: &Path, question_policy: Questi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Confirmation dialog for end user with [Y/n] question.
|
||||||
|
///
|
||||||
|
/// If the placeholder is found in the prompt text, it will be replaced to form the final message.
|
||||||
|
pub struct Confirmation<'a> {
|
||||||
|
/// The message to be displayed with the placeholder text in it.
|
||||||
|
/// e.g.: "Do you want to overwrite 'FILE'?"
|
||||||
|
pub prompt: &'a str,
|
||||||
|
|
||||||
|
/// The placeholder text that will be replaced in the `ask` function:
|
||||||
|
/// e.g.: Some("FILE")
|
||||||
|
pub placeholder: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Confirmation<'a> {
|
||||||
|
/// Creates a new Confirmation.
|
||||||
|
pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
||||||
|
Self { prompt, placeholder: pattern }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates user message and receives a boolean input to be used on the program
|
||||||
|
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
||||||
|
let message = match (self.placeholder, substitute) {
|
||||||
|
(None, _) => Cow::Borrowed(self.prompt),
|
||||||
|
(Some(_), None) => unreachable!("dev error, should be reported, we checked this won't happen"),
|
||||||
|
(Some(placeholder), Some(subs)) => Cow::Owned(self.prompt.replace(placeholder, subs)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
io::stdout().flush()?;
|
||||||
|
|
||||||
|
let mut answer = String::new();
|
||||||
|
io::stdin().read_line(&mut answer)?;
|
||||||
|
|
||||||
|
answer.make_ascii_lowercase();
|
||||||
|
match answer.trim() {
|
||||||
|
"" | "y" | "yes" => return Ok(true),
|
||||||
|
"n" | "no" => return Ok(false),
|
||||||
|
_ => continue, // Try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user