mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-06 11:35:45 +00:00
Add Extension
wrapper that lets us keep tgz instead of forcing tar.gz
This commit is contained in:
parent
604616e042
commit
4921d3d3d2
@ -17,6 +17,7 @@ use crate::{
|
||||
extension::{
|
||||
self,
|
||||
CompressionFormat::{self, *},
|
||||
Extension,
|
||||
},
|
||||
info,
|
||||
utils::nice_directory_display,
|
||||
@ -56,9 +57,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
||||
return Err(Error::with_reason(reason));
|
||||
}
|
||||
|
||||
if !formats.get(0).map(CompressionFormat::is_archive_format).unwrap_or(false)
|
||||
&& represents_several_files(&files)
|
||||
{
|
||||
if !formats.get(0).map(Extension::is_archive).unwrap_or(false) && represents_several_files(&files) {
|
||||
// This piece of code creates a suggestion for compressing multiple files
|
||||
// It says:
|
||||
// Change from file.bz.xz
|
||||
@ -86,7 +85,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
||||
return Err(Error::with_reason(reason));
|
||||
}
|
||||
|
||||
if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive_format()) {
|
||||
if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive()) {
|
||||
let reason = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path)))
|
||||
.detail(format!("Found the format '{}' in an incorrect position.", format))
|
||||
.detail(format!("'{}' can only be used at the start of the file extension.", format))
|
||||
@ -108,12 +107,28 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
||||
// `ouch compress file.tar.gz file.tar.gz.xz` should produce `file.tar.gz.xz` and not `file.tar.gz.tar.gz.xz`
|
||||
let input_extensions = extension::extensions_from_path(&files[0]);
|
||||
|
||||
// We calculate the formats that are left if we filter out a sublist at the start of what we have that's the same as the input formats
|
||||
let mut new_formats = Vec::with_capacity(formats.len());
|
||||
for (inp_ext, out_ext) in input_extensions.iter().zip(&formats) {
|
||||
if inp_ext.compression_formats == out_ext.compression_formats {
|
||||
new_formats.push(out_ext.clone());
|
||||
} else if inp_ext
|
||||
.compression_formats
|
||||
.iter()
|
||||
.zip(&out_ext.compression_formats)
|
||||
.all(|(inp, out)| inp == out)
|
||||
{
|
||||
let new_ext = Extension::new(
|
||||
&out_ext.compression_formats[..inp_ext.compression_formats.len()],
|
||||
&out_ext.display_text,
|
||||
);
|
||||
new_formats.push(new_ext);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the input is a sublist at the start of `formats` then remove the extensions
|
||||
// Note: If input_extensions is empty this counts as true
|
||||
if !input_extensions.is_empty()
|
||||
&& input_extensions.len() < formats.len()
|
||||
&& input_extensions.iter().zip(&formats).all(|(inp, out)| inp == out)
|
||||
{
|
||||
// Note: If input_extensions is empty then it will make `formats` empty too, which we don't want
|
||||
if !input_extensions.is_empty() && new_formats != formats {
|
||||
// Safety:
|
||||
// We checked above that input_extensions isn't empty, so files[0] has a extension.
|
||||
//
|
||||
@ -124,8 +139,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
||||
to_utf(files[0].as_path().file_name().unwrap()),
|
||||
to_utf(&output_path)
|
||||
);
|
||||
let drain_iter = formats.drain(..input_extensions.len());
|
||||
drop(drain_iter); // Remove the extensions from `formats`
|
||||
formats = new_formats;
|
||||
}
|
||||
}
|
||||
let compress_result = compress_files(files, formats, output_file);
|
||||
@ -185,7 +199,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_file: fs::File) -> crate::Result<()> {
|
||||
fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs::File) -> crate::Result<()> {
|
||||
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
||||
|
||||
let mut writer: Box<dyn Write> = Box::new(file_writer);
|
||||
@ -208,13 +222,13 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_f
|
||||
encoder
|
||||
};
|
||||
|
||||
for format in formats.iter().skip(1).rev() {
|
||||
for format in formats.iter().flat_map(Extension::iter).skip(1).collect::<Vec<_>>().iter().rev() {
|
||||
writer = chain_writer_encoder(format, writer);
|
||||
}
|
||||
|
||||
match formats[0] {
|
||||
match formats[0].compression_formats[0] {
|
||||
Gzip | Bzip | Lzma | Zstd => {
|
||||
writer = chain_writer_encoder(&formats[0], writer);
|
||||
writer = chain_writer_encoder(&formats[0].compression_formats[0], writer);
|
||||
let mut reader = fs::File::open(&files[0]).unwrap();
|
||||
io::copy(&mut reader, &mut writer)?;
|
||||
}
|
||||
@ -248,7 +262,7 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_f
|
||||
// file_name is only used when extracting single file formats, no archive formats like .tar or .zip
|
||||
fn decompress_file(
|
||||
input_file_path: &Path,
|
||||
formats: Vec<extension::CompressionFormat>,
|
||||
formats: Vec<Extension>,
|
||||
output_folder: Option<&Path>,
|
||||
file_name: &Path,
|
||||
skip_questions_positively: Option<bool>,
|
||||
@ -270,7 +284,7 @@ fn decompress_file(
|
||||
// in-memory decompression/copying first.
|
||||
//
|
||||
// Any other Zip decompression done can take up the whole RAM and freeze ouch.
|
||||
if let [Zip] = *formats.as_slice() {
|
||||
if formats.len() == 1 && *formats[0].compression_formats.as_slice() == [Zip] {
|
||||
utils::create_dir_if_non_existent(output_folder)?;
|
||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
||||
@ -294,15 +308,15 @@ fn decompress_file(
|
||||
Ok(decoder)
|
||||
};
|
||||
|
||||
for format in formats.iter().skip(1).rev() {
|
||||
for format in formats.iter().flat_map(Extension::iter).skip(1).collect::<Vec<_>>().iter().rev() {
|
||||
reader = chain_reader_decoder(format, reader)?;
|
||||
}
|
||||
|
||||
utils::create_dir_if_non_existent(output_folder)?;
|
||||
|
||||
match formats[0] {
|
||||
match formats[0].compression_formats[0] {
|
||||
Gzip | Bzip | Lzma | Zstd => {
|
||||
reader = chain_reader_decoder(&formats[0], reader)?;
|
||||
reader = chain_reader_decoder(&formats[0].compression_formats[0], reader)?;
|
||||
|
||||
// TODO: improve error treatment
|
||||
let mut writer = fs::File::create(&output_path)?;
|
||||
|
@ -4,7 +4,40 @@ use std::{ffi::OsStr, fmt, path::Path};
|
||||
|
||||
use self::CompressionFormat::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
/// A wrapper around `CompressionFormat` that allows combinations like `tgz`
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Extension {
|
||||
pub compression_formats: Vec<CompressionFormat>,
|
||||
pub display_text: String,
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
/// # Panics:
|
||||
/// Will panic if `formats` is empty
|
||||
pub fn new(formats: impl Into<Vec<CompressionFormat>>, text: impl Into<String>) -> Self {
|
||||
let formats = formats.into();
|
||||
assert!(!formats.is_empty());
|
||||
Self { compression_formats: formats, display_text: text.into() }
|
||||
}
|
||||
|
||||
/// Checks if the first format in `compression_formats` is an archive
|
||||
pub fn is_archive(&self) -> bool {
|
||||
// Safety: we check that `compression_formats` is not empty in `Self::new`
|
||||
self.compression_formats[0].is_archive_format()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &CompressionFormat> {
|
||||
self.compression_formats.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Extension {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.display_text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
/// Accepted extensions for input and output
|
||||
pub enum CompressionFormat {
|
||||
Gzip, // .gz
|
||||
@ -45,7 +78,7 @@ impl fmt::Display for CompressionFormat {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<CompressionFormat>) {
|
||||
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Extension>) {
|
||||
// // TODO: check for file names with the name of an extension
|
||||
// // TODO2: warn the user that currently .tar.gz is a .gz file named .tar
|
||||
//
|
||||
@ -58,17 +91,17 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Compr
|
||||
|
||||
// While there is known extensions at the tail, grab them
|
||||
while let Some(extension) = path.extension().and_then(OsStr::to_str) {
|
||||
extensions.append(&mut match extension {
|
||||
"tar" => vec![Tar],
|
||||
"tgz" => vec![Gzip, Tar],
|
||||
"tbz" | "tbz2" => vec![Bzip, Tar],
|
||||
"txz" | "tlz" | "tlzma" => vec![Lzma, Tar],
|
||||
"tzst" => vec![Zstd, Tar],
|
||||
"zip" => vec![Zip],
|
||||
"bz" | "bz2" => vec![Bzip],
|
||||
"gz" => vec![Gzip],
|
||||
"xz" | "lzma" | "lz" => vec![Lzma],
|
||||
"zst" => vec![Zstd],
|
||||
extensions.push(match extension {
|
||||
"tar" => Extension::new([Tar], extension),
|
||||
"tgz" => Extension::new([Tar, Gzip], extension),
|
||||
"tbz" | "tbz2" => Extension::new([Tar, Bzip], extension),
|
||||
"txz" | "tlz" | "tlzma" => Extension::new([Tar, Lzma], extension),
|
||||
"tzst" => Extension::new([Tar, Zstd], ".tzst"),
|
||||
"zip" => Extension::new([Zip], extension),
|
||||
"bz" | "bz2" => Extension::new([Bzip], extension),
|
||||
"gz" => Extension::new([Gzip], extension),
|
||||
"xz" | "lzma" | "lz" => Extension::new([Lzma], extension),
|
||||
"zst" => Extension::new([Zstd], extension),
|
||||
_ => break,
|
||||
});
|
||||
|
||||
@ -81,7 +114,7 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Compr
|
||||
(path, extensions)
|
||||
}
|
||||
|
||||
pub fn extensions_from_path(path: &Path) -> Vec<CompressionFormat> {
|
||||
pub fn extensions_from_path(path: &Path) -> Vec<Extension> {
|
||||
let (_, extensions) = separate_known_extensions_from_name(path);
|
||||
extensions
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user