Merge pull request #341 from figsoda/format

add --format option
This commit is contained in:
João Marcos Bezerra 2023-01-08 23:57:13 -03:00 committed by GitHub
commit 38e19536af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 27 deletions

View File

@ -14,8 +14,8 @@ use utils::colors;
use crate::{
commands::{compress::compress_files, decompress::decompress_file, list::list_archive_contents},
error::FinalError,
extension::{self, flatten_compression_formats, Extension, SUPPORTED_EXTENSIONS},
error::{Error, FinalError},
extension::{self, flatten_compression_formats, parse_format, Extension, SUPPORTED_EXTENSIONS},
info,
list::ListOptions,
utils::{
@ -114,7 +114,13 @@ pub fn run(
}
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
let formats = extension::extensions_from_path(&output_path);
let (formats_from_flag, formats) = match args.format {
Some(formats) => {
let parsed_formats = parse_format(&formats)?;
(Some(formats), parsed_formats)
}
None => (None, extension::extensions_from_path(&output_path)),
};
let first_format = formats.first().ok_or_else(|| {
let output_path = EscapedPathDisplay::new(&output_path);
@ -134,28 +140,42 @@ pub fn run(
// If first format is not archive, can't compress folder, or multiple files
// Index safety: empty formats should be checked above.
if !first_format.is_archive() && (is_some_input_a_folder || is_multiple_inputs) {
// This piece of code creates a suggestion for compressing multiple files
// It says:
// Change from file.bz.xz
// To file.tar.bz.xz
let suggested_output_path = build_archive_file_suggestion(&output_path, ".tar")
.expect("output path should contain a compression format");
let output_path = EscapedPathDisplay::new(&output_path);
let first_detail_message = if is_multiple_inputs {
"You are trying to compress multiple files."
} else {
"You are trying to compress a folder."
};
let (from_hint, to_hint) = if let Some(formats) = formats_from_flag {
let formats = formats.to_string_lossy();
(
format!("From: --format {formats}"),
format!("To: --format tar.{formats}"),
)
} else {
// This piece of code creates a suggestion for compressing multiple files
// It says:
// Change from file.bz.xz
// To file.tar.bz.xz
let suggested_output_path = build_archive_file_suggestion(&output_path, ".tar")
.expect("output path should contain a compression format");
(
format!("From: {}", EscapedPathDisplay::new(&output_path)),
format!("To: {suggested_output_path}"),
)
};
let output_path = EscapedPathDisplay::new(&output_path);
let error = FinalError::with_title(format!("Cannot compress to '{output_path}'."))
.detail(first_detail_message)
.detail(format!(
"The compression format '{first_format}' does not accept multiple files.",
))
.detail("Formats that bundle files into an archive are .tar and .zip.")
.hint(format!("Try inserting '.tar' or '.zip' before '{first_format}'."))
.hint(format!("From: {output_path}"))
.hint(format!("To: {suggested_output_path}"));
.detail("Formats that bundle files into an archive are tar and zip.")
.hint(format!("Try inserting 'tar.' or 'zip.' before '{first_format}'."))
.hint(from_hint)
.hint(to_hint);
return Err(error.into());
}
@ -228,11 +248,22 @@ pub fn run(
let mut output_paths = vec![];
let mut formats = vec![];
if let Some(format) = args.format {
let format = parse_format(&format)?;
for path in files.iter() {
let file_name = path.file_name().ok_or_else(|| Error::NotFound {
error_title: format!("{} does not have a file name", EscapedPathDisplay::new(path)),
})?;
output_paths.push(file_name.as_ref());
formats.push(format.clone());
}
} else {
for path in files.iter() {
let (file_output_path, file_formats) = extension::separate_known_extensions_from_name(path);
output_paths.push(file_output_path);
formats.push(file_formats);
}
}
if let ControlFlow::Break(_) = check_mime_type(&files, &mut formats, question_policy)? {
return Ok(());
@ -292,6 +323,12 @@ pub fn run(
Subcommand::List { archives: files, tree } => {
let mut formats = vec![];
if let Some(format) = args.format {
let format = parse_format(&format)?;
for _ in 0..files.len() {
formats.push(format.clone());
}
} else {
for path in files.iter() {
let file_formats = extension::extensions_from_path(path);
formats.push(file_formats);
@ -300,6 +337,7 @@ pub fn run(
if let ControlFlow::Break(_) = check_mime_type(&files, &mut formats, question_policy)? {
return Ok(());
}
}
// Ensure we were not told to list the content of a non-archive compressed file
check_for_non_archive_formats(&files, &formats)?;

View File

@ -32,6 +32,8 @@ pub enum Error {
WalkdirError { reason: String },
/// Custom and unique errors are reported in this variant
Custom { reason: FinalError },
/// Invalid format passed to `--format`
InvalidFormat { reason: String },
}
/// Alias to std's Result with ouch's Error
@ -135,6 +137,7 @@ impl fmt::Display for Error {
FinalError::with_title(error_title.to_string()).detail("Permission denied")
}
Error::UnsupportedZipArchive(reason) => FinalError::with_title("Unsupported zip archive").detail(*reason),
Error::InvalidFormat { reason } => FinalError::with_title("Invalid archive format").detail(reason.clone()),
Error::Custom { reason } => reason.clone(),
};

View File

@ -1,11 +1,11 @@
//! Our representation of all the supported compression formats.
use std::{fmt, path::Path};
use std::{ffi::OsStr, fmt, path::Path};
use bstr::ByteSlice;
use self::CompressionFormat::*;
use crate::warning;
use crate::{error::Error, warning};
/// A wrapper around `CompressionFormat` that allows combinations like `tgz`
#[derive(Debug, Clone, Eq)]
@ -138,6 +138,22 @@ pub fn split_extension<'a>(name: &mut &'a [u8]) -> Option<&'a [u8]> {
Some(ext)
}
pub fn parse_format(fmt: &OsStr) -> crate::Result<Vec<Extension>> {
let fmt = <[u8] as ByteSlice>::from_os_str(fmt).ok_or_else(|| Error::InvalidFormat {
reason: "Invalid UTF-8".into(),
})?;
let mut extensions = Vec::new();
for extension in fmt.split_str(b".") {
let extension = to_extension(extension).ok_or_else(|| Error::InvalidFormat {
reason: format!("Unsupported extension: {}", extension.to_str_lossy()),
})?;
extensions.push(extension);
}
Ok(extensions)
}
/// Extracts extensions from a path.
///
/// Returns both the remaining path and the list of extension objects

View File

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{ffi::OsString, path::PathBuf};
use clap::{Parser, ValueHint};
@ -37,6 +37,10 @@ pub struct Opts {
#[arg(short = 'g', long, global = true)]
pub gitignore: bool,
/// Specify the format of the archive
#[arg(short, long, global = true)]
pub format: Option<OsString>,
/// Ouch and claps subcommands
#[command(subcommand)]
pub cmd: Subcommand,