feat: Add flag '--no-smart-unpack' to disable smart unpack (#809)

This commit is contained in:
Talison Fabio 2025-04-28 11:03:50 -03:00 committed by GitHub
parent 2b9da1e441
commit c97bb6a2d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 229 additions and 9 deletions

View File

@ -21,6 +21,7 @@ Categories Used:
## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.1...HEAD)
### New Features
- Add `--no-smart-unpack` flag to decompression command to disable smart unpack [\#809](https://github.com/ouch-org/ouch/pull/809) ([talis-fb](https://github.com/talis-fb))
### Improvements
### Bug Fixes
### Tweaks

View File

@ -100,6 +100,10 @@ pub enum Subcommand {
/// Remove the source file after successful decompression
#[arg(short = 'r', long)]
remove: bool,
/// Disable Smart Unpack
#[arg(long)]
no_smart_unpack: bool,
},
/// List contents of an archive
#[command(visible_aliases = ["l", "ls"])]
@ -156,6 +160,7 @@ mod tests {
files: vec!["\x00\x11\x22".into()],
output_dir: None,
remove: false,
no_smart_unpack: false,
},
}
}
@ -169,6 +174,7 @@ mod tests {
files: to_paths(["file.tar.gz"]),
output_dir: None,
remove: false,
no_smart_unpack: false,
},
..mock_cli_args()
}
@ -180,6 +186,7 @@ mod tests {
files: to_paths(["file.tar.gz"]),
output_dir: None,
remove: false,
no_smart_unpack: false,
},
..mock_cli_args()
}
@ -191,6 +198,7 @@ mod tests {
files: to_paths(["a", "b", "c"]),
output_dir: None,
remove: false,
no_smart_unpack: false,
},
..mock_cli_args()
}

View File

@ -32,6 +32,7 @@ pub struct DecompressOptions<'a> {
pub output_dir: &'a Path,
pub output_file_path: PathBuf,
pub is_output_dir_provided: bool,
pub is_smart_unpack: bool,
pub question_policy: QuestionPolicy,
pub quiet: bool,
pub password: Option<&'a [u8]>,
@ -75,6 +76,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
&options.output_file_path,
options.question_policy,
options.is_output_dir_provided,
options.is_smart_unpack,
)? {
files
} else {
@ -153,6 +155,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
&options.output_file_path,
options.question_policy,
options.is_output_dir_provided,
options.is_smart_unpack,
)? {
files
} else {
@ -187,6 +190,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
&options.output_file_path,
options.question_policy,
options.is_output_dir_provided,
options.is_smart_unpack,
)? {
files
} else {
@ -219,6 +223,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
&options.output_file_path,
options.question_policy,
options.is_output_dir_provided,
options.is_smart_unpack,
)? {
files
} else {
@ -261,6 +266,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
&options.output_file_path,
options.question_policy,
options.is_output_dir_provided,
options.is_smart_unpack,
)? {
files
} else {
@ -296,12 +302,19 @@ fn execute_decompression(
output_file_path: &Path,
question_policy: QuestionPolicy,
is_output_dir_provided: bool,
is_smart_unpack: bool,
) -> crate::Result<ControlFlow<(), usize>> {
if is_output_dir_provided {
unpack(unpack_fn, output_dir, question_policy)
} else {
smart_unpack(unpack_fn, output_dir, output_file_path, question_policy)
if is_smart_unpack {
return smart_unpack(unpack_fn, output_dir, output_file_path, question_policy);
}
let target_output_dir = if is_output_dir_provided {
output_dir
} else {
output_file_path
};
unpack(unpack_fn, target_output_dir, question_policy)
}
/// Unpacks an archive creating the output directory, this function will create the output_dir

View File

@ -148,6 +148,7 @@ pub fn run(
files,
output_dir,
remove,
no_smart_unpack,
} => {
let mut output_paths = vec![];
let mut formats = vec![];
@ -176,9 +177,11 @@ pub fn run(
check::check_missing_formats_when_decompressing(&files, &formats)?;
let is_output_dir_provided = output_dir.is_some();
let is_smart_unpack = !is_output_dir_provided && !no_smart_unpack;
// The directory that will contain the output files
// We default to the current directory if the user didn't specify an output directory with --dir
let is_output_dir_provided = output_dir.is_some();
let output_dir = if let Some(dir) = output_dir {
utils::create_dir_if_non_existent(&dir)?;
dir
@ -200,9 +203,10 @@ pub fn run(
decompress_file(DecompressOptions {
input_file_path: input_path,
formats,
is_output_dir_provided,
output_dir: &output_dir,
output_file_path,
is_output_dir_provided,
is_smart_unpack,
question_policy,
quiet: args.quiet,
password: args.password.as_deref().map(|str| {

View File

@ -367,6 +367,201 @@ fn multiple_files_with_conflict_and_choice_to_rename_with_already_a_renamed(
assert_same_directory(src_files_path, dest_files_path_renamed.join("src_files"), false);
}
#[proptest(cases = 25)]
fn smart_unpack_with_single_file(
ext: DirectoryExtension,
#[any(size_range(0..1).lift())] extra_extensions: Vec<FileExtension>,
) {
let temp_dir = tempdir().unwrap();
let root_path = temp_dir.path();
let src_files_path = root_path.join("src_files");
fs::create_dir_all(&src_files_path).unwrap();
let files_path = ["file1.txt"]
.into_iter()
.map(|f| src_files_path.join(f))
.inspect(|path| {
let mut file = fs::File::create(path).unwrap();
file.write_all("Some content".as_bytes()).unwrap();
})
.collect::<Vec<_>>();
let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions)));
crate::utils::cargo_bin()
.arg("compress")
.args(files_path)
.arg(archive)
.assert()
.success();
let output_file = root_path.join("file1.txt");
assert!(!output_file.exists());
// Decompress the archive with Smart Unpack
crate::utils::cargo_bin()
.current_dir(root_path)
.arg("decompress")
.arg(archive)
.assert()
.success();
assert!(output_file.exists());
let output_content = fs::read_to_string(&output_file).unwrap();
assert_eq!(output_content, "Some content");
}
#[proptest(cases = 25)]
fn smart_unpack_with_multiple_files(
ext: DirectoryExtension,
#[any(size_range(0..1).lift())] extra_extensions: Vec<FileExtension>,
) {
let temp_dir = tempdir().unwrap();
let root_path = temp_dir.path();
let src_files_path = root_path.join("src_files");
fs::create_dir_all(&src_files_path).unwrap();
["file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"]
.into_iter()
.map(|f| src_files_path.join(f))
.for_each(|path| {
let mut file = fs::File::create(&path).unwrap();
file.write_all("Some content".as_bytes()).unwrap();
});
let input_files = src_files_path
.read_dir()
.unwrap()
.map(|entry| entry.unwrap().path())
.collect::<Vec<PathBuf>>();
let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions)));
let output_path = root_path.join("archive");
assert!(!output_path.exists());
crate::utils::cargo_bin()
.arg("compress")
.args(input_files)
.arg(archive)
.assert()
.success();
crate::utils::cargo_bin()
.current_dir(root_path)
.arg("decompress")
.arg(archive)
.assert()
.success();
assert!(output_path.exists(), "Output directory does not exist");
assert_same_directory(src_files_path, output_path, false);
}
#[proptest(cases = 25)]
fn no_smart_unpack_with_single_file(
ext: DirectoryExtension,
#[any(size_range(0..1).lift())] extra_extensions: Vec<FileExtension>,
) {
let temp_dir = tempdir().unwrap();
let root_path = temp_dir.path();
let src_files_path = root_path.join("src_files");
fs::create_dir_all(&src_files_path).unwrap();
["file1.txt"]
.into_iter()
.map(|f| src_files_path.join(f))
.for_each(|path| {
let mut file = fs::File::create(&path).unwrap();
file.write_all("Some content".as_bytes()).unwrap();
});
let input_files = src_files_path
.read_dir()
.unwrap()
.map(|entry| entry.unwrap().path())
.collect::<Vec<PathBuf>>();
let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions)));
let output_path = root_path.join("archive");
assert!(!output_path.exists());
crate::utils::cargo_bin()
.arg("compress")
.args(input_files)
.arg(archive)
.assert()
.success();
crate::utils::cargo_bin()
.current_dir(root_path)
.arg("decompress")
.arg("--no-smart-unpack")
.arg(archive)
.assert()
.success();
assert!(output_path.exists(), "Output directory does not exist");
assert_same_directory(src_files_path, output_path, false);
}
#[proptest(cases = 25)]
fn no_smart_unpack_with_multiple_files(
ext: DirectoryExtension,
#[any(size_range(0..1).lift())] extra_extensions: Vec<FileExtension>,
) {
let temp_dir = tempdir().unwrap();
let root_path = temp_dir.path();
let src_files_path = root_path.join("src_files");
fs::create_dir_all(&src_files_path).unwrap();
["file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"]
.into_iter()
.map(|f| src_files_path.join(f))
.for_each(|path| {
let mut file = fs::File::create(&path).unwrap();
file.write_all("Some content".as_bytes()).unwrap();
});
let input_files = src_files_path
.read_dir()
.unwrap()
.map(|entry| entry.unwrap().path())
.collect::<Vec<PathBuf>>();
let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions)));
let output_path = root_path.join("archive");
assert!(!output_path.exists());
crate::utils::cargo_bin()
.arg("compress")
.args(input_files)
.arg(archive)
.assert()
.success();
crate::utils::cargo_bin()
.current_dir(root_path)
.arg("decompress")
.arg("--no-smart-unpack")
.arg(archive)
.assert()
.success();
assert!(output_path.exists(), "Output directory does not exist");
assert_same_directory(src_files_path, output_path, false);
}
#[proptest(cases = 25)]
fn multiple_files_with_disabled_smart_unpack_by_dir(
ext: DirectoryExtension,
@ -490,10 +685,9 @@ fn symlink_pack_and_unpack(
let mut files_path = ["file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"]
.into_iter()
.map(|f| src_files_path.join(f))
.map(|path| {
let mut file = fs::File::create(&path).unwrap();
.inspect(|path| {
let mut file = fs::File::create(path).unwrap();
file.write_all("Some content".as_bytes()).unwrap();
path
})
.collect::<Vec<_>>();