Add --format flag to specify formats when compressing

This commit is contained in:
João M. Bezerra 2021-12-08 02:28:50 -03:00
parent fddc79628a
commit ea1d3bf6bb
4 changed files with 74 additions and 23 deletions

View File

@ -10,7 +10,7 @@ use clap::Parser;
use fs_err as fs;
use once_cell::sync::OnceCell;
use crate::{Opts, QuestionPolicy, Subcommand};
use crate::{error::FinalError, Opts, QuestionPolicy, Subcommand};
/// Whether to enable accessible output (removes info output and reduces other
/// output, removes visual markers like '[' and ']').
@ -26,6 +26,13 @@ impl Opts {
pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> {
let mut opts = Self::parse();
if opts.format.as_ref().map(String::is_empty).unwrap_or(false) {
let error = FinalError::with_title("Given --format flag is empty")
.hint("Try passing a supported extension, like --format tar.gz");
return Err(error.into());
}
ACCESSIBLE.set(opts.accessible).unwrap();
let (Subcommand::Compress { files, .. }

View File

@ -46,7 +46,8 @@ fn represents_several_files(files: &[PathBuf]) -> bool {
pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
match args.cmd {
Subcommand::Compress { mut files, output: output_path } => {
// If the output_path file exists and is the same as some of the input files, warn the user and skip those inputs (in order to avoid compression recursion)
// If the output_path file exists and is the same as some of the input files, warn the
// user and skip those inputs (in order to avoid compression recursion)
if output_path.exists() {
clean_input_files_if_needed(&mut files, &fs::canonicalize(&output_path)?);
}
@ -56,7 +57,15 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
}
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
let mut formats = extension::extensions_from_path(&output_path);
let mut formats = if let Some(format) = args.format {
extension::from_format_text(&format).ok_or_else(|| {
FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path)))
.detail("Supplied compression format is not valid")
.hint("Check --help for all supported formats")
})
} else {
Ok(extension::extensions_from_path(&output_path))
}?;
if formats.is_empty() {
let error = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path)))

View File

@ -102,8 +102,54 @@ impl fmt::Display for CompressionFormat {
}
}
// use crate::extension::CompressionFormat::*;
//
/// Returns the list of compression formats that correspond to the given extension text.
///
/// # Examples:
/// - `"tar" => Some(&[Tar])`
/// - `"tgz" => Some(&[Tar, Gzip])`
///
/// Note that the text given as input should not contain any dots, otherwise, None will be returned.
pub fn compression_formats_from_text(extension: &str) -> Option<&'static [CompressionFormat]> {
match extension {
"tar" => Some(&[Tar]),
"tgz" => Some(&[Tar, Gzip]),
"tbz" | "tbz2" => Some(&[Tar, Bzip]),
"tlz4" => Some(&[Tar, Lz4]),
"txz" | "tlzma" => Some(&[Tar, Lzma]),
"tsz" => Some(&[Tar, Snappy]),
"tzst" => Some(&[Tar, Zstd]),
"zip" => Some(&[Zip]),
"bz" | "bz2" => Some(&[Bzip]),
"gz" => Some(&[Gzip]),
"lz4" => Some(&[Lz4]),
"xz" | "lzma" => Some(&[Lzma]),
"sz" => Some(&[Snappy]),
"zst" => Some(&[Zstd]),
_ => None,
}
}
/// Grab a user given format, and parse it.
///
/// # Examples:
/// - `"tar.gz" => Extension { ... [Tar, Gz] }`
/// - `".tar.gz" => Extension { ... [Tar, Gz] }`
pub fn from_format_text(format: &str) -> Option<Vec<Extension>> {
let mut extensions = vec![];
// Ignore all dots, use filter to ignore dots at first and last char leaving empty pieces
let extension_pieces = format.split('.').filter(|piece| !piece.is_empty());
for piece in extension_pieces {
let extension = match compression_formats_from_text(piece) {
Some(formats) => Extension::new(formats, piece),
None => return None,
};
extensions.push(extension);
}
extensions.reverse();
Some(extensions)
}
/// Extracts extensions from a path,
/// return both the remaining path and the list of extension objects
@ -120,25 +166,10 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Exten
// While there is known extensions at the tail, grab them
while let Some(extension) = path.extension().and_then(OsStr::to_str) {
let formats: &[CompressionFormat] = match extension {
"tar" => &[Tar],
"tgz" => &[Tar, Gzip],
"tbz" | "tbz2" => &[Tar, Bzip],
"tlz4" => &[Tar, Lz4],
"txz" | "tlzma" => &[Tar, Lzma],
"tsz" => &[Tar, Snappy],
"tzst" => &[Tar, Zstd],
"zip" => &[Zip],
"bz" | "bz2" => &[Bzip],
"gz" => &[Gzip],
"lz4" => &[Lz4],
"xz" | "lzma" => &[Lzma],
"sz" => &[Snappy],
"zst" => &[Zstd],
_ => break,
let extension = match compression_formats_from_text(extension) {
Some(formats) => Extension::new(formats, extension),
None => break,
};
let extension = Extension::new(formats, extension);
extensions.push(extension);
// Update for the next iteration

View File

@ -20,6 +20,10 @@ pub struct Opts {
pub no: bool,
/// Activate accessibility mode, reducing visual noise
#[clap(short = 'f', long)]
pub format: Option<String>,
/// Ignore autodetection, specifying
#[clap(short = 'A', long, env = "ACCESSIBLE")]
pub accessible: bool,