mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-07 20:15:27 +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::{
|
extension::{
|
||||||
self,
|
self,
|
||||||
CompressionFormat::{self, *},
|
CompressionFormat::{self, *},
|
||||||
|
Extension,
|
||||||
},
|
},
|
||||||
info,
|
info,
|
||||||
utils::nice_directory_display,
|
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));
|
return Err(Error::with_reason(reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !formats.get(0).map(CompressionFormat::is_archive_format).unwrap_or(false)
|
if !formats.get(0).map(Extension::is_archive).unwrap_or(false) && represents_several_files(&files) {
|
||||||
&& represents_several_files(&files)
|
|
||||||
{
|
|
||||||
// This piece of code creates a suggestion for compressing multiple files
|
// This piece of code creates a suggestion for compressing multiple files
|
||||||
// It says:
|
// It says:
|
||||||
// Change from file.bz.xz
|
// 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));
|
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)))
|
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!("Found the format '{}' in an incorrect position.", format))
|
||||||
.detail(format!("'{}' can only be used at the start of the file extension.", 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`
|
// `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]);
|
let input_extensions = extension::extensions_from_path(&files[0]);
|
||||||
|
|
||||||
// If the input is a sublist at the start of `formats` then remove the extensions
|
// 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
|
||||||
// Note: If input_extensions is empty this counts as true
|
let mut new_formats = Vec::with_capacity(formats.len());
|
||||||
if !input_extensions.is_empty()
|
for (inp_ext, out_ext) in input_extensions.iter().zip(&formats) {
|
||||||
&& input_extensions.len() < formats.len()
|
if inp_ext.compression_formats == out_ext.compression_formats {
|
||||||
&& input_extensions.iter().zip(&formats).all(|(inp, out)| inp == out)
|
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 then it will make `formats` empty too, which we don't want
|
||||||
|
if !input_extensions.is_empty() && new_formats != formats {
|
||||||
// Safety:
|
// Safety:
|
||||||
// We checked above that input_extensions isn't empty, so files[0] has a extension.
|
// 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(files[0].as_path().file_name().unwrap()),
|
||||||
to_utf(&output_path)
|
to_utf(&output_path)
|
||||||
);
|
);
|
||||||
let drain_iter = formats.drain(..input_extensions.len());
|
formats = new_formats;
|
||||||
drop(drain_iter); // Remove the extensions from `formats`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let compress_result = compress_files(files, formats, output_file);
|
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(())
|
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 file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
||||||
|
|
||||||
let mut writer: Box<dyn Write> = Box::new(file_writer);
|
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
|
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);
|
writer = chain_writer_encoder(format, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
match formats[0] {
|
match formats[0].compression_formats[0] {
|
||||||
Gzip | Bzip | Lzma | Zstd => {
|
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();
|
let mut reader = fs::File::open(&files[0]).unwrap();
|
||||||
io::copy(&mut reader, &mut writer)?;
|
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
|
// file_name is only used when extracting single file formats, no archive formats like .tar or .zip
|
||||||
fn decompress_file(
|
fn decompress_file(
|
||||||
input_file_path: &Path,
|
input_file_path: &Path,
|
||||||
formats: Vec<extension::CompressionFormat>,
|
formats: Vec<Extension>,
|
||||||
output_folder: Option<&Path>,
|
output_folder: Option<&Path>,
|
||||||
file_name: &Path,
|
file_name: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
skip_questions_positively: Option<bool>,
|
||||||
@ -270,7 +284,7 @@ fn decompress_file(
|
|||||||
// in-memory decompression/copying first.
|
// in-memory decompression/copying first.
|
||||||
//
|
//
|
||||||
// Any other Zip decompression done can take up the whole RAM and freeze ouch.
|
// 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)?;
|
utils::create_dir_if_non_existent(output_folder)?;
|
||||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||||
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
||||||
@ -294,15 +308,15 @@ fn decompress_file(
|
|||||||
Ok(decoder)
|
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)?;
|
reader = chain_reader_decoder(format, reader)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::create_dir_if_non_existent(output_folder)?;
|
utils::create_dir_if_non_existent(output_folder)?;
|
||||||
|
|
||||||
match formats[0] {
|
match formats[0].compression_formats[0] {
|
||||||
Gzip | Bzip | Lzma | Zstd => {
|
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
|
// TODO: improve error treatment
|
||||||
let mut writer = fs::File::create(&output_path)?;
|
let mut writer = fs::File::create(&output_path)?;
|
||||||
|
@ -4,7 +4,40 @@ use std::{ffi::OsStr, fmt, path::Path};
|
|||||||
|
|
||||||
use self::CompressionFormat::*;
|
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
|
/// Accepted extensions for input and output
|
||||||
pub enum CompressionFormat {
|
pub enum CompressionFormat {
|
||||||
Gzip, // .gz
|
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
|
// // 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
|
// // 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 there is known extensions at the tail, grab them
|
||||||
while let Some(extension) = path.extension().and_then(OsStr::to_str) {
|
while let Some(extension) = path.extension().and_then(OsStr::to_str) {
|
||||||
extensions.append(&mut match extension {
|
extensions.push(match extension {
|
||||||
"tar" => vec![Tar],
|
"tar" => Extension::new([Tar], extension),
|
||||||
"tgz" => vec![Gzip, Tar],
|
"tgz" => Extension::new([Tar, Gzip], extension),
|
||||||
"tbz" | "tbz2" => vec![Bzip, Tar],
|
"tbz" | "tbz2" => Extension::new([Tar, Bzip], extension),
|
||||||
"txz" | "tlz" | "tlzma" => vec![Lzma, Tar],
|
"txz" | "tlz" | "tlzma" => Extension::new([Tar, Lzma], extension),
|
||||||
"tzst" => vec![Zstd, Tar],
|
"tzst" => Extension::new([Tar, Zstd], ".tzst"),
|
||||||
"zip" => vec![Zip],
|
"zip" => Extension::new([Zip], extension),
|
||||||
"bz" | "bz2" => vec![Bzip],
|
"bz" | "bz2" => Extension::new([Bzip], extension),
|
||||||
"gz" => vec![Gzip],
|
"gz" => Extension::new([Gzip], extension),
|
||||||
"xz" | "lzma" | "lz" => vec![Lzma],
|
"xz" | "lzma" | "lz" => Extension::new([Lzma], extension),
|
||||||
"zst" => vec![Zstd],
|
"zst" => Extension::new([Zstd], extension),
|
||||||
_ => break,
|
_ => break,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,7 +114,7 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Compr
|
|||||||
(path, extensions)
|
(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);
|
let (_, extensions) = separate_known_extensions_from_name(path);
|
||||||
extensions
|
extensions
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user