mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-07 12:05:46 +00:00
commit
38e19536af
@ -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,10 +248,21 @@ pub fn run(
|
||||
let mut output_paths = vec![];
|
||||
let mut formats = vec![];
|
||||
|
||||
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 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)? {
|
||||
@ -292,13 +323,20 @@ pub fn run(
|
||||
Subcommand::List { archives: files, tree } => {
|
||||
let mut formats = vec![];
|
||||
|
||||
for path in files.iter() {
|
||||
let file_formats = extension::extensions_from_path(path);
|
||||
formats.push(file_formats);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if let ControlFlow::Break(_) = check_mime_type(&files, &mut formats, question_policy)? {
|
||||
return Ok(());
|
||||
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
|
||||
|
@ -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(),
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user