diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 5aadb46..1130598 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -71,7 +71,6 @@ where FinalError::with_title("Could not create archive") .detail("Unexpected error while trying to read file") .detail(format!("Error: {}.", err)) - .into_owned() })?; } } diff --git a/src/cli.rs b/src/cli.rs index cd853de..17c6c71 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,4 @@ -//! CLI configuration step, uses definitions from `opts.rs`. -//! -//! Also used to treat some inputs. +//! CLI related functions, uses the clap argparsing definitions from `opts.rs`. use std::{ path::{Path, PathBuf}, @@ -13,8 +11,11 @@ use fs_err as fs; use crate::{Error, Opts, QuestionPolicy, Subcommand}; impl Opts { - /// A helper method that calls `clap::Parser::parse` and then translates relative paths to absolute. - /// Also determines if the user wants to skip questions or not + /// A helper method that calls `clap::Parser::parse`. + /// + /// And: + /// 1. Make paths absolute. + /// 2. Checks the QuestionPolicy. pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> { let mut opts: Self = Self::parse(); diff --git a/src/commands.rs b/src/commands.rs index 7467772..6cfd877 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -37,7 +37,7 @@ fn represents_several_files(files: &[PathBuf]) -> bool { } /// Entrypoint of ouch, receives cli options and matches Subcommand -/// to decide current operation +/// to decide what to do pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { match args.cmd { Subcommand::Compress { files, output: output_path } => { diff --git a/src/dialogs.rs b/src/dialogs.rs index ce18102..cbfe754 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -1,7 +1,7 @@ //! Pretty (and colored) dialog for asking [Y/n] for the end user. //! //! Example: -//! "Do you want to overwrite 'archive.targz'? [Y/n]" +//! "Do you want to overwrite 'archive.tar.gz'? [Y/n]" use std::{ borrow::Cow, @@ -10,19 +10,21 @@ use std::{ use crate::utils::colors; -/// Represents a confirmation dialog +/// 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> { - /// Represents the message to the displayed + /// The message to be displayed with the placeholder text in it. /// e.g.: "Do you want to overwrite 'FILE'?" pub prompt: &'a str, - /// Represents a placeholder to be changed at runtime + /// The placeholder text that will be replaced in the `ask` function: /// e.g.: Some("FILE") pub placeholder: Option<&'a str>, } impl<'a> Confirmation<'a> { - /// New Confirmation + /// Creates a new Confirmation. pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self { Self { prompt, placeholder: pattern } } @@ -35,20 +37,17 @@ impl<'a> Confirmation<'a> { (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)?; - let trimmed_answer = answer.trim(); - if trimmed_answer.is_empty() { - return Ok(true); - } - - match trimmed_answer.to_ascii_lowercase().as_ref() { - "y" | "yes" => return Ok(true), + answer.make_ascii_lowercase(); + match answer.trim() { + "" | "y" | "yes" => return Ok(true), "n" | "no" => return Ok(false), _ => continue, // Try again } diff --git a/src/error.rs b/src/error.rs index f395e7a..77a6751 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,6 @@ -//! Error type definitions. +//! Error types definitions. //! -//! All the unexpected user-side errors should be treated in this file, that does not include -//! errors made by devs in our implementation. -//! -//! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function -//! calls inside of this module. - -#![allow(missing_docs)] +//! All usage errors will pass throught the Error enum, a lot of them in the Error::Custom. use std::{ fmt::{self, Display}, @@ -15,32 +9,64 @@ use std::{ use crate::utils::colors::*; -/// Custom Ouch Errors +/// All errors that can be generated by `ouch` #[derive(Debug, PartialEq)] pub enum Error { + /// Extension found is not supported and known to ouch UnknownExtensionError(String), + /// TO BE REMOVED MissingExtensionError(PathBuf), - IoError { reason: String }, + /// Not every IoError, some of them get filtered by `From` into other variants + IoError { + /// TODO + reason: String, + }, + /// Detected from io::Error if .kind() is io::ErrorKind::NotFound FileNotFound(PathBuf), + /// TO BE REMOVED AlreadyExists, + /// TO BE REMOVED InvalidZipArchive(&'static str), - PermissionDenied { error_title: String }, + /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied + PermissionDenied { + /// TODO + error_title: String, + }, + /// TO BE REMOVED UnsupportedZipArchive(&'static str), + /// TO BE REMOVED InternalError, + /// TO BE REMOVED CompressingRootFolder, + /// TO BE REMOVED MissingArgumentsForCompression, + /// TO BE REMOVED MissingArgumentsForDecompression, + /// TO BE REMOVED CompressionTypo, - WalkdirError { reason: String }, - Custom { reason: FinalError }, + /// Specialized walkdir's io::Error wrapper with additional information on the error + WalkdirError { + /// TODO + reason: String, + }, + /// Custom and unique errors are reported in this variant + Custom { + /// TODO + reason: FinalError, + }, } +/// Alias to std's Result with ouch's Error pub type Result = std::result::Result; +/// Pretty final error message for end users, crashing the program after display. #[derive(Clone, Debug, Default, PartialEq)] pub struct FinalError { + /// Should be made of just one line, appears after the "[ERROR]" part title: String, + /// Shown as a unnumbered list in yellow details: Vec, + /// Shown as green at the end to give hints on how to work around this error, if it's fixable hints: Vec, } @@ -68,23 +94,22 @@ impl Display for FinalError { } impl FinalError { + /// Only constructor pub fn with_title(title: impl ToString) -> Self { Self { title: title.to_string(), details: vec![], hints: vec![] } } + /// Add one detail line, can have multiple pub fn detail(mut self, detail: impl ToString) -> Self { self.details.push(detail.to_string()); self } + /// Add one hint line, can have multiple pub fn hint(mut self, hint: impl ToString) -> Self { self.hints.push(hint.to_string()); self } - - pub fn into_owned(&mut self) -> Self { - std::mem::take(self) - } } impl fmt::Display for Error { @@ -151,6 +176,7 @@ impl fmt::Display for Error { } impl Error { + /// TO BE REMOVED pub fn with_reason(reason: FinalError) -> Self { Self::Custom { reason } } diff --git a/src/extension.rs b/src/extension.rs index 07beb03..c9dc055 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -7,7 +7,9 @@ use self::CompressionFormat::*; /// A wrapper around `CompressionFormat` that allows combinations like `tgz` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Extension { + /// One extension like "tgz" can be made of multiple CompressionFormats ([Tar, Gz]) pub compression_formats: Vec, + /// The input text for this extension, like "tgz", "tar" or "xz" pub display_text: String, } @@ -26,6 +28,7 @@ impl Extension { self.compression_formats[0].is_archive_format() } + /// Iteration to inner compression formats, useful for flat_mapping pub fn iter(&self) -> impl Iterator { self.compression_formats.iter() } @@ -37,16 +40,21 @@ impl fmt::Display for Extension { } } -#[allow(missing_docs)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { - Gzip, // .gz - Bzip, // .bz - Lzma, // .lzma - Tar, // .tar (technically not a compression extension, but will do for now) - Zstd, // .zst - Zip, // .zip + /// .gz + Gzip, + /// .bz .bz2 + Bzip, + /// .xz .lzma .lz + Lzma, + /// tar, tgz, tbz, tbz2, txz, tlz, tlzma, tzst + Tar, + /// .zst + Zstd, + /// .zip + Zip, } impl CompressionFormat { diff --git a/src/lib.rs b/src/lib.rs index 40384a6..71c51fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,4 @@ -//! This library is meant to be published, just used internally by our binary crate at `main.rs`. -//! -//! A module shall be public only if: -//! 1. It's required by `main.rs`, or -//! 2. It's required by some integration tests at tests/ folder. +//! This library isn't meant to be published, but used internally by our binary crate `main.rs`. #![warn(missing_docs)] @@ -17,12 +13,12 @@ pub mod error; pub mod extension; pub mod utils; -/// CLI configuration step, uses definitions from `opts.rs`, also used to treat some inputs. +/// CLI argparsing definitions, using `clap`. pub mod opts; pub use error::{Error, Result}; pub use opts::{Opts, Subcommand}; pub use utils::QuestionPolicy; -/// The status code ouch has when an error is encountered +/// The status code returned from `ouch` on error pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE; diff --git a/src/macros.rs b/src/macros.rs index 5bf7205..1057120 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,6 +1,6 @@ //! Macros used on ouch. -/// Macro that prints message in INFO mode +/// Macro that prints [INFO] messages, wraps [`println`]. #[macro_export] macro_rules! info { ($($arg:tt)*) => { @@ -9,7 +9,7 @@ macro_rules! info { }; } -/// Prints the `[Info]` tag +/// Helper to display "[INFO]", colored yellow pub fn _info_helper() { use crate::utils::colors::{RESET, YELLOW}; diff --git a/src/opts.rs b/src/opts.rs index 243a5a0..14d6997 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -6,41 +6,51 @@ use std::path::PathBuf; #[derive(Parser, Debug)] #[clap(version, about)] pub struct Opts { - /// Skip overwrite questions positively. + /// Skip [Y/n] questions positively. #[clap(short, long, conflicts_with = "no")] pub yes: bool, - /// Skip overwrite questions negatively. + /// Skip [Y/n] questions negatively. #[clap(short, long)] pub no: bool, - /// Action to take + /// Ouch and claps subcommands #[clap(subcommand)] pub cmd: Subcommand, } -/// Actions to take +// CAREFUL: this docs can accidentally become part of the --help message if they get too long +// this was tested in clap 3.0.0-beta5. +/// Repository: https://github.com/ouch-org/ouch +// +// Ouch commands: +// - `compress` +// - `decompress` +// - `list` +// +// Clap commands: +// - `help` #[derive(Parser, PartialEq, Eq, Debug)] pub enum Subcommand { - /// Compress files. Alias: c + /// Compress one or more files into one output file. #[clap(alias = "c")] Compress { - /// Files to be compressed + /// Files to be compressed. #[clap(required = true, min_values = 1)] files: Vec, - /// The resulting file. Its extensions specify how the files will be compressed and they need to be supported + /// The resulting file. It's extensions can be used to specify the compression formats. #[clap(required = true, value_hint = ValueHint::FilePath)] output: PathBuf, }, - /// Compress files. Alias: d + /// Decompresses one or more files, optionally into another folder. #[clap(alias = "d")] Decompress { - /// Files to be decompressed + /// Files to be decompressed. #[clap(required = true, min_values = 1)] files: Vec, - /// Decompress files in a directory other than the current + /// Choose to files in a directory other than the current #[clap(short, long = "dir", value_hint = ValueHint::DirPath)] output_dir: Option, }, diff --git a/src/utils.rs b/src/utils.rs index e8aaf52..7d19964 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -//! Utils used on ouch. +//! Random stuff used on ouch. use std::{ cmp, env, @@ -11,7 +11,7 @@ use fs_err as fs; use crate::{dialogs::Confirmation, info}; -/// Checks if the given path represents an empty directory. +/// Checks given path points to an empty directory. pub fn dir_is_empty(dir_path: &Path) -> bool { let is_empty = |mut rd: std::fs::ReadDir| rd.next().is_none(); @@ -37,21 +37,18 @@ pub fn strip_cur_dir(source_path: &Path) -> PathBuf { .unwrap_or_else(|_| source_path.to_path_buf()) } -/// Changes the process' current directory to the directory that contains the -/// file pointed to by `filename` and returns the directory that the process -/// was in before this function was called. +/// Returns current directory, but before change the process' directory to the +/// one that contains the file pointed to by `filename`. pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result { let previous_location = env::current_dir()?; let parent = filename.parent().ok_or(crate::Error::CompressingRootFolder)?; - env::set_current_dir(parent)?; Ok(previous_location) } -/// Centralizes the decision of overwriting a file or not, -/// whether the user has already passed a question_policy or not. +/// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite. pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result { match question_policy { QuestionPolicy::AlwaysYes => Ok(true), @@ -65,13 +62,17 @@ pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> } } -/// Converts an OsStr to utf8. +/// 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) -> String { let text = format!("{:?}", os_str.as_ref()); text.trim_matches('"').to_string() } -/// Treats weird paths for better user messages. +/// Display the directory name, but change to "current directory" when necessary. pub fn nice_directory_display(os_str: impl AsRef) -> String { let text = to_utf(os_str); if text == "." { @@ -81,11 +82,6 @@ pub fn nice_directory_display(os_str: impl AsRef) -> String { } } -/// Struct used to overload functionality onto Byte presentation. -pub struct Bytes { - bytes: f64, -} - /// Module with a list of bright colors. #[allow(dead_code)] pub mod colors { @@ -116,10 +112,15 @@ pub mod colors { color!(YELLOW = "\u{1b}[38;5;11m"); } +/// Struct useful to printing bytes as kB, MB, GB, etc. +pub struct Bytes { + bytes: f64, +} + impl Bytes { const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"]; - /// New Byte structure + /// Create a new Bytes. pub fn new(bytes: u64) -> Self { Self { bytes: bytes as f64 } } @@ -141,13 +142,13 @@ impl std::fmt::Display for Bytes { } #[derive(Debug, PartialEq, Clone, Copy)] -/// How overwrite questions should be handled +/// Determines if overwrite questions should be skipped or asked to the user pub enum QuestionPolicy { - /// Ask everytime + /// Ask the user every time Ask, - /// Skip overwrite questions positively + /// Set by `--yes`, will say 'Y' to all overwrite questions AlwaysYes, - /// Skip overwrite questions negatively + /// Set by `--no`, will say 'N' to all overwrite questions AlwaysNo, }