Merge branch 'main' into close-issue-825

This commit is contained in:
tommady 2025-07-16 16:23:53 +08:00 committed by GitHub
commit 1b7389252f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 146 additions and 104 deletions

View File

@ -25,10 +25,13 @@ Categories Used:
- Merge folders in decompression [\#798](https://github.com/ouch-org/ouch/pull/798) ([tommady](https://github.com/tommady)) - Merge folders in decompression [\#798](https://github.com/ouch-org/ouch/pull/798) ([tommady](https://github.com/tommady))
- 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)) - 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))
- Provide Nushell completions (packages still need to install them) [\#827](https://github.com/ouch-org/ouch/pull/827) ([FrancescElies](https://github.com/FrancescElies)) - Provide Nushell completions (packages still need to install them) [\#827](https://github.com/ouch-org/ouch/pull/827) ([FrancescElies](https://github.com/FrancescElies))
- Support `.lz` decompression [\#838](https://github.com/ouch-org/ouch/pull/838) ([zzzsyyy](https://github.com/zzzsyyy))
- Support `.lzma` decompression (and fix `.lzma` being a wrong alias for `.xz`) [\#838](https://github.com/ouch-org/ouch/pull/838) ([zzzsyyy](https://github.com/zzzsyyy))
### Improvements ### Improvements
- Give better error messages when archive extensions are invalid [\#817](https://github.com/ouch-org/ouch/pull/817) ([marcospb19](https://github.com/marcospb19)) - Give better error messages when archive extensions are invalid [\#817](https://github.com/ouch-org/ouch/pull/817) ([marcospb19](https://github.com/marcospb19))
- Add aliases for `--password` flag (`--pass` and `--pw`) [\#847](https://github.com/ouch-org/ouch/pull/847) ([marcospb19](https://github.com/marcospb19))
### Bug Fixes ### Bug Fixes

View File

@ -27,6 +27,10 @@ Before opening the PR, open an issue to discuss your addition, this increases th
[CHANGELOG.md]: https://github.com/ouch-org/ouch [CHANGELOG.md]: https://github.com/ouch-org/ouch
### CI Tests
The CI tests will run for a combination of features, `--no-default-features` will also be tested.
## Dealing with UI tests ## Dealing with UI tests
We use snapshots to do UI testing and guarantee a consistent output, this way, you can catch accidental changes or see what output changed in the PR diff. We use snapshots to do UI testing and guarantee a consistent output, this way, you can catch accidental changes or see what output changed in the PR diff.
@ -35,14 +39,16 @@ We use snapshots to do UI testing and guarantee a consistent output, this way, y
```sh ```sh
cargo test cargo test
# Only run UI tests # Or, if you only want to run UI tests
cargo test -- ui # cargo test -- ui
``` ```
- If some UI test failed, you should review it: - If some UI test failed, you should review them (requires `cargo install cargo-insta`):
```sh ```sh
cargo insta review cargo insta review
``` ```
- After addressing all, you should be able to `git add` and `commit` accordingly. - After addressing all, you should be able to `git add` and `commit` accordingly.
NOTE: Sometimes, you'll have to run these two commands multiple times.

42
Cargo.lock generated
View File

@ -926,6 +926,26 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "liblzma"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.3" version = "0.1.3"
@ -995,17 +1015,6 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "lzma-sys"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -1115,6 +1124,7 @@ dependencies = [
"is_executable", "is_executable",
"itertools", "itertools",
"libc", "libc",
"liblzma",
"linked-hash-map", "linked-hash-map",
"lz4_flex", "lz4_flex",
"memchr", "memchr",
@ -1134,7 +1144,6 @@ dependencies = [
"test-strategy", "test-strategy",
"time", "time",
"unrar", "unrar",
"xz2",
"zip", "zip",
"zstd", "zstd",
] ]
@ -2078,15 +2087,6 @@ dependencies = [
"rustix", "rustix",
] ]
[[package]]
name = "xz2"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
dependencies = [
"lzma-sys",
]
[[package]] [[package]]
name = "yansi" name = "yansi"
version = "1.0.1" version = "1.0.1"

View File

@ -41,7 +41,7 @@ tar = "0.4.42"
tempfile = "3.10.1" tempfile = "3.10.1"
time = { version = "0.3.36", default-features = false } time = { version = "0.3.36", default-features = false }
unrar = { version = "0.5.7", optional = true } unrar = { version = "0.5.7", optional = true }
xz2 = "0.1.7" liblzma = "0.4"
zip = { version = "0.6.6", default-features = false, features = [ zip = { version = "0.6.6", default-features = false, features = [
"time", "time",
"aes-crypto", "aes-crypto",

View File

@ -111,9 +111,9 @@ Output:
# Supported formats # Supported formats
| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` | | Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz` | `.lzma` | `.lz` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` |
|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ | | Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓⁴ | ✓⁴ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ |
✓: Supports compression and decompression. ✓: Supports compression and decompression.
@ -122,10 +122,13 @@ Output:
✓²: Supported, and compression runs in parallel. ✓²: Supported, and compression runs in parallel.
✓³: Due to RAR's restrictive license, only decompression and listing can be supported. ✓³: Due to RAR's restrictive license, only decompression and listing can be supported.
✓⁴: Only decompression is supported, compression is not implemented yet.
If you wish to exclude non-free code from your build, you can disable RAR support If you wish to exclude non-free code from your build, you can disable RAR support
by building without the `unrar` feature. by building without the `unrar` feature.
`tar` aliases are also supported: `tgz`, `tbz`, `tbz2`, `tlz4`, `txz`, `tlzma`, `tsz`, `tzst`. `tar` aliases are also supported: `tgz`, `tbz`, `tbz2`, `tlz4`, `txz`, `tlzma`, `tsz`, `tzst`, `tlz`.
Formats can be chained: Formats can be chained:

View File

@ -56,8 +56,7 @@ pub fn check_mime_type(
.ends_with(detected_format.compression_formats) .ends_with(detected_format.compression_formats)
{ {
warning(format!( warning(format!(
"The file extension: `{}` differ from the detected extension: `{}`", "The file extension: `{outer_ext}` differ from the detected extension: `{detected_format}`"
outer_ext, detected_format
)); ));
if !user_wants_to_continue(path, question_policy, QuestionAction::Decompression)? { if !user_wants_to_continue(path, question_policy, QuestionAction::Decompression)? {

View File

@ -5,7 +5,7 @@ use clap::{Parser, ValueHint};
// Ouch command line options (docstrings below are part of --help) // Ouch command line options (docstrings below are part of --help)
/// A command-line utility for easily compressing and decompressing files and directories. /// A command-line utility for easily compressing and decompressing files and directories.
/// ///
/// Supported formats: tar, zip, gz, 7z, xz/lzma, bz/bz2, bz3, lz4, sz (Snappy), zst, rar and br. /// Supported formats: tar, zip, gz, 7z, xz, lzma, lzip, bz/bz2, bz3, lz4, sz (Snappy), zst, rar and br.
/// ///
/// Repository: https://github.com/ouch-org/ouch /// Repository: https://github.com/ouch-org/ouch
#[derive(Parser, Debug, PartialEq)] #[derive(Parser, Debug, PartialEq)]
@ -30,11 +30,11 @@ pub struct CliArgs {
pub hidden: bool, pub hidden: bool,
/// Silence output /// Silence output
#[arg(short = 'q', long, global = true)] #[arg(short, long, global = true)]
pub quiet: bool, pub quiet: bool,
/// Ignore files matched by git's ignore files /// Ignore files matched by git's ignore files
#[arg(short = 'g', long, global = true)] #[arg(short, long, global = true)]
pub gitignore: bool, pub gitignore: bool,
/// Specify the format of the archive /// Specify the format of the archive
@ -42,7 +42,7 @@ pub struct CliArgs {
pub format: Option<OsString>, pub format: Option<OsString>,
/// Decompress or list with password /// Decompress or list with password
#[arg(short = 'p', long = "password", global = true)] #[arg(short, long = "password", aliases = ["pass", "pw"], global = true)]
pub password: Option<OsString>, pub password: Option<OsString>,
/// Concurrent working threads /// Concurrent working threads

View File

@ -68,10 +68,20 @@ pub fn compress_files(
) )
} }
Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()), Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()),
Lzma => Box::new(xz2::write::XzEncoder::new( Lzma => {
return Err(crate::Error::UnsupportedFormat {
reason: "LZMA1 compression is not supported in ouch, use .xz instead.".to_string(),
})
}
Xz => Box::new(liblzma::write::XzEncoder::new(
encoder, encoder,
level.map_or(6, |l| (l as u32).clamp(0, 9)), level.map_or(6, |l| (l as u32).clamp(0, 9)),
)), )),
Lzip => {
return Err(crate::Error::UnsupportedFormat {
reason: "Lzip compression is not supported in ouch.".to_string(),
})
}
Snappy => Box::new( Snappy => Box::new(
gzp::par::compress::ParCompress::<gzp::snap::Snap>::builder() gzp::par::compress::ParCompress::<gzp::snap::Snap>::builder()
.compression_level(gzp::par::compress::Compression::new( .compression_level(gzp::par::compress::Compression::new(
@ -108,7 +118,7 @@ pub fn compress_files(
} }
match first_format { match first_format {
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Xz | Lzip | Snappy | Zstd | Brotli => {
writer = chain_writer_encoder(&first_format, writer)?; writer = chain_writer_encoder(&first_format, writer)?;
let mut reader = fs::File::open(&files[0])?; let mut reader = fs::File::open(&files[0])?;

View File

@ -128,7 +128,15 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
Box::new(bzip3::read::Bz3Decoder::new(decoder)?) Box::new(bzip3::read::Bz3Decoder::new(decoder)?)
} }
Lz4 => Box::new(lz4_flex::frame::FrameDecoder::new(decoder)), Lz4 => Box::new(lz4_flex::frame::FrameDecoder::new(decoder)),
Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), Lzma => Box::new(liblzma::read::XzDecoder::new_stream(
decoder,
liblzma::stream::Stream::new_lzma_decoder(u64::MAX).unwrap(),
)),
Xz => Box::new(liblzma::read::XzDecoder::new(decoder)),
Lzip => Box::new(liblzma::read::XzDecoder::new_stream(
decoder,
liblzma::stream::Stream::new_lzip_decoder(u64::MAX, 0).unwrap(),
)),
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)), Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?), Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)), Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
@ -144,7 +152,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
} }
let files_unpacked = match first_extension { let files_unpacked = match first_extension {
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Xz | Lzip | Snappy | Zstd | Brotli => {
reader = chain_reader_decoder(&first_extension, reader)?; reader = chain_reader_decoder(&first_extension, reader)?;
let mut writer = match utils::ask_to_create_file( let mut writer = match utils::ask_to_create_file(
@ -295,7 +303,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
"Successfully decompressed archive in {}", "Successfully decompressed archive in {}",
nice_directory_display(options.output_dir) nice_directory_display(options.output_dir)
)); ));
info_accessible(format!("Files unpacked: {}", files_unpacked)); info_accessible(format!("Files unpacked: {files_unpacked}"));
if !input_is_stdin && options.remove { if !input_is_stdin && options.remove {
fs::remove_file(options.input_file_path)?; fs::remove_file(options.input_file_path)?;

View File

@ -57,7 +57,15 @@ pub fn list_archive_contents(
Box::new(bzip3::read::Bz3Decoder::new(decoder).unwrap()) Box::new(bzip3::read::Bz3Decoder::new(decoder).unwrap())
} }
Lz4 => Box::new(lz4_flex::frame::FrameDecoder::new(decoder)), Lz4 => Box::new(lz4_flex::frame::FrameDecoder::new(decoder)),
Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), Lzma => Box::new(liblzma::read::XzDecoder::new_stream(
decoder,
liblzma::stream::Stream::new_lzma_decoder(u64::MAX).unwrap(),
)),
Xz => Box::new(liblzma::read::XzDecoder::new(decoder)),
Lzip => Box::new(liblzma::read::XzDecoder::new_stream(
decoder,
liblzma::stream::Stream::new_lzip_decoder(u64::MAX, 0).unwrap(),
)),
Snappy => Box::new(snap::read::FrameDecoder::new(decoder)), Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?), Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)), Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
@ -127,7 +135,7 @@ pub fn list_archive_contents(
Box::new(archive::sevenz::list_archive(io::Cursor::new(vec), password)?) Box::new(archive::sevenz::list_archive(io::Cursor::new(vec), password)?)
} }
Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Xz | Lzip | Snappy | Zstd | Brotli => {
unreachable!("Not an archive, should be validated before calling this function."); unreachable!("Not an archive, should be validated before calling this function.");
} }
}; };

View File

@ -135,12 +135,12 @@ impl FinalError {
/// ///
/// This is what it looks like: /// This is what it looks like:
/// ``` /// ```
/// hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst /// hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, lz, sz, zst
/// hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst /// hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
/// ``` /// ```
pub fn hint_all_supported_formats(self) -> Self { pub fn hint_all_supported_formats(self) -> Self {
self.hint(format!("Supported extensions are: {}", PRETTY_SUPPORTED_EXTENSIONS)) self.hint(format!("Supported extensions are: {PRETTY_SUPPORTED_EXTENSIONS}"))
.hint(format!("Supported aliases are: {}", PRETTY_SUPPORTED_ALIASES)) .hint(format!("Supported aliases are: {PRETTY_SUPPORTED_ALIASES}"))
} }
} }

View File

@ -19,6 +19,7 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[
"lz4", "lz4",
"xz", "xz",
"lzma", "lzma",
"lz",
"sz", "sz",
"zst", "zst",
#[cfg(feature = "unrar")] #[cfg(feature = "unrar")]
@ -27,14 +28,14 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[
"br", "br",
]; ];
pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst"]; pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tlzma", "tsz", "tzst", "tlz"];
#[cfg(not(feature = "unrar"))] #[cfg(not(feature = "unrar"))]
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z"; pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z";
#[cfg(feature = "unrar")] #[cfg(feature = "unrar")]
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z"; pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z";
pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tzlma, tsz, tzst"; pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz";
/// A wrapper around `CompressionFormat` that allows combinations like `tgz` /// A wrapper around `CompressionFormat` that allows combinations like `tgz`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -85,8 +86,12 @@ pub enum CompressionFormat {
Bzip3, Bzip3,
/// .lz4 /// .lz4
Lz4, Lz4,
/// .xz .lzma /// .xz
Xz,
/// .lzma
Lzma, Lzma,
/// .lzip
Lzip,
/// .sz /// .sz
Snappy, Snappy,
/// tar, tgz, tbz, tbz2, tbz3, txz, tlz4, tlzma, tsz, tzst /// tar, tgz, tbz, tbz2, tbz3, txz, tlz4, tlzma, tsz, tzst
@ -95,7 +100,6 @@ pub enum CompressionFormat {
Zstd, Zstd,
/// .zip /// .zip
Zip, Zip,
// even if built without RAR support, we still want to recognise the format
/// .rar /// .rar
Rar, Rar,
/// .7z /// .7z
@ -105,19 +109,11 @@ pub enum CompressionFormat {
} }
impl CompressionFormat { impl CompressionFormat {
/// Currently supported archive formats are .tar (and aliases to it) and .zip
pub fn archive_format(&self) -> bool { pub fn archive_format(&self) -> bool {
// Keep this match like that without a wildcard `_` so we don't forget to update it // Keep this match without a wildcard `_` so we never forget to update it
match self { match self {
Tar | Zip | Rar | SevenZip => true, Tar | Zip | Rar | SevenZip => true,
Gzip => false, Bzip | Bzip3 | Lz4 | Lzma | Xz | Lzip | Snappy | Zstd | Brotli | Gzip => false,
Bzip => false,
Bzip3 => false,
Lz4 => false,
Lzma => false,
Snappy => false,
Zstd => false,
Brotli => false,
} }
} }
} }
@ -130,7 +126,9 @@ fn to_extension(ext: &[u8]) -> Option<Extension> {
b"tbz" | b"tbz2" => &[Tar, Bzip], b"tbz" | b"tbz2" => &[Tar, Bzip],
b"tbz3" => &[Tar, Bzip3], b"tbz3" => &[Tar, Bzip3],
b"tlz4" => &[Tar, Lz4], b"tlz4" => &[Tar, Lz4],
b"txz" | b"tlzma" => &[Tar, Lzma], b"txz" => &[Tar, Xz],
b"tlzma" => &[Tar, Lzma],
b"tlz" => &[Tar, Lzip],
b"tsz" => &[Tar, Snappy], b"tsz" => &[Tar, Snappy],
b"tzst" => &[Tar, Zstd], b"tzst" => &[Tar, Zstd],
b"zip" => &[Zip], b"zip" => &[Zip],
@ -138,7 +136,9 @@ fn to_extension(ext: &[u8]) -> Option<Extension> {
b"bz3" => &[Bzip3], b"bz3" => &[Bzip3],
b"gz" => &[Gzip], b"gz" => &[Gzip],
b"lz4" => &[Lz4], b"lz4" => &[Lz4],
b"xz" | b"lzma" => &[Lzma], b"xz" => &[Xz],
b"lzma" => &[Lzma],
b"lz" => &[Lzip],
b"sz" => &[Snappy], b"sz" => &[Snappy],
b"zst" => &[Zstd], b"zst" => &[Zstd],
b"rar" => &[Rar], b"rar" => &[Rar],
@ -173,7 +173,7 @@ pub fn parse_format_flag(input: &OsStr) -> crate::Result<Vec<Extension>> {
.map(|extension| { .map(|extension| {
to_extension(extension.as_bytes()).ok_or_else(|| Error::InvalidFormatFlag { to_extension(extension.as_bytes()).ok_or_else(|| Error::InvalidFormatFlag {
text: input.to_owned(), text: input.to_owned(),
reason: format!("Unsupported extension '{}'", extension), reason: format!("Unsupported extension '{extension}'"),
}) })
}) })
.collect::<crate::Result<_>>()?; .collect::<crate::Result<_>>()?;

View File

@ -109,7 +109,7 @@ impl std::fmt::Display for Bytes {
debug_assert!(num >= 0.0); debug_assert!(num >= 0.0);
if num < 1_f64 { if num < 1_f64 {
return write!(f, "{:>6.2} B", num); return write!(f, "{num:>6.2} B");
} }
let delimiter = 1000_f64; let delimiter = 1000_f64;

View File

@ -82,7 +82,7 @@ pub fn rename_or_increment_filename(path: &Path) -> PathBuf {
let number = number_str.parse::<u32>().unwrap_or(0); let number = number_str.parse::<u32>().unwrap_or(0);
format!("{}_{}", base, number + 1) format!("{}_{}", base, number + 1)
} }
_ => format!("{}_1", filename), _ => format!("{filename}_1"),
}; };
let mut new_path = parent.join(new_filename); let mut new_path = parent.join(new_filename);
@ -135,9 +135,15 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
fn is_bz3(buf: &[u8]) -> bool { fn is_bz3(buf: &[u8]) -> bool {
buf.starts_with(b"BZ3v1") buf.starts_with(b"BZ3v1")
} }
fn is_lzma(buf: &[u8]) -> bool {
buf.len() >= 14 && buf[0] == 0x5d && (buf[12] == 0x00 || buf[12] == 0xff) && buf[13] == 0x00
}
fn is_xz(buf: &[u8]) -> bool { fn is_xz(buf: &[u8]) -> bool {
buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]) buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])
} }
fn is_lzip(buf: &[u8]) -> bool {
buf.starts_with(&[0x4C, 0x5A, 0x49, 0x50])
}
fn is_lz4(buf: &[u8]) -> bool { fn is_lz4(buf: &[u8]) -> bool {
buf.starts_with(&[0x04, 0x22, 0x4D, 0x18]) buf.starts_with(&[0x04, 0x22, 0x4D, 0x18])
} }
@ -183,8 +189,12 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
Some(Extension::new(&[Bzip], "bz2")) Some(Extension::new(&[Bzip], "bz2"))
} else if is_bz3(&buf) { } else if is_bz3(&buf) {
Some(Extension::new(&[Bzip3], "bz3")) Some(Extension::new(&[Bzip3], "bz3"))
} else if is_lzma(&buf) {
Some(Extension::new(&[Lzma], "lzma"))
} else if is_xz(&buf) { } else if is_xz(&buf) {
Some(Extension::new(&[Lzma], "xz")) Some(Extension::new(&[Xz], "xz"))
} else if is_lzip(&buf) {
Some(Extension::new(&[Lzip], "lzip"))
} else if is_lz4(&buf) { } else if is_lz4(&buf) {
Some(Extension::new(&[Lz4], "lz4")) Some(Extension::new(&[Lz4], "lz4"))
} else if is_sz(&buf) { } else if is_sz(&buf) {

View File

@ -187,7 +187,7 @@ impl<'a, T: Default> ChoicePrompt<'a, T> {
#[cfg(not(feature = "allow_piped_choice"))] #[cfg(not(feature = "allow_piped_choice"))]
if !stdin().is_terminal() { if !stdin().is_terminal() {
eprintln!("{}", message); eprintln!("{message}");
eprintln!("Pass --yes to proceed"); eprintln!("Pass --yes to proceed");
return Ok(T::default()); return Ok(T::default());
} }
@ -222,10 +222,10 @@ impl<'a, T: Default> ChoicePrompt<'a, T> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("/"); .join("/");
format!("[{}]", choises) format!("[{choises}]")
}; };
eprintln!("{} {}", message, choice_prompt); eprintln!("{message} {choice_prompt}");
let mut answer = String::new(); let mut answer = String::new();
let bytes_read = stdin_lock.read_line(&mut answer)?; let bytes_read = stdin_lock.read_line(&mut answer)?;
@ -284,7 +284,7 @@ impl<'a> Confirmation<'a> {
#[cfg(not(feature = "allow_piped_choice"))] #[cfg(not(feature = "allow_piped_choice"))]
if !stdin().is_terminal() { if !stdin().is_terminal() {
eprintln!("{}", message); eprintln!("{message}");
eprintln!("Pass --yes to proceed"); eprintln!("Pass --yes to proceed");
return Ok(false); return Ok(false);
} }

View File

@ -33,7 +33,6 @@ enum DirectoryExtension {
Tbz3, Tbz3,
Tgz, Tgz,
Tlz4, Tlz4,
Tlzma,
Tsz, Tsz,
Txz, Txz,
Tzst, Tzst,
@ -50,7 +49,6 @@ enum FileExtension {
Bz3, Bz3,
Gz, Gz,
Lz4, Lz4,
Lzma,
Sz, Sz,
Xz, Xz,
Zst, Zst,
@ -175,7 +173,7 @@ fn single_file_stdin(
fs::create_dir(before).unwrap(); fs::create_dir(before).unwrap();
let before_file = &before.join("file"); let before_file = &before.join("file");
let format = merge_extensions(&ext, &exts); let format = merge_extensions(&ext, &exts);
let archive = &dir.join(format!("file.{}", format)); let archive = &dir.join(format!("file.{format}"));
let after = &dir.join("after"); let after = &dir.join("after");
write_random_content( write_random_content(
&mut fs::File::create(before_file).unwrap(), &mut fs::File::create(before_file).unwrap(),

View File

@ -17,8 +17,7 @@ fn sanity_check_through_mime() {
write_random_content(test_file, &mut SmallRng::from_entropy()); write_random_content(test_file, &mut SmallRng::from_entropy());
let formats = [ let formats = [
"7z", "tar", "zip", "tar.gz", "tgz", "tbz", "tbz2", "txz", "tlzma", "tzst", "tar.bz", "tar.bz2", "tar.lzma", "7z", "tar", "zip", "tar.gz", "tgz", "tbz", "tbz2", "txz", "tzst", "tar.bz", "tar.bz2", "tar.xz", "tar.zst",
"tar.xz", "tar.zst",
]; ];
let expected_mimes = [ let expected_mimes = [
@ -30,12 +29,10 @@ fn sanity_check_through_mime() {
"application/x-bzip2", "application/x-bzip2",
"application/x-bzip2", "application/x-bzip2",
"application/x-xz", "application/x-xz",
"application/x-xz",
"application/zstd", "application/zstd",
"application/x-bzip2", "application/x-bzip2",
"application/x-bzip2", "application/x-bzip2",
"application/x-xz", "application/x-xz",
"application/x-xz",
"application/zstd", "application/zstd",
]; ];

View File

@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress a\", dir)"
- Files with missing extensions: <TMP_DIR>/a - Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension - Decompression formats are detected automatically from file extension
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Alternatively, you can pass an extension to the '--format' flag: hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/a --format tar.gz hint: ouch decompress <TMP_DIR>/a --format tar.gz

View File

@ -7,5 +7,5 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)"
- Files with missing extensions: <TMP_DIR>/a - Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension - Decompression formats are detected automatically from file extension
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz

View File

@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)"
- Files with unsupported extensions: <TMP_DIR>/b.unknown - Files with unsupported extensions: <TMP_DIR>/b.unknown
- Decompression formats are detected automatically from file extension - Decompression formats are detected automatically from file extension
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Alternatively, you can pass an extension to the '--format' flag: hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz

View File

@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress a\", dir)"
- Files with missing extensions: <TMP_DIR>/a - Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension - Decompression formats are detected automatically from file extension
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Alternatively, you can pass an extension to the '--format' flag: hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/a --format tar.gz hint: ouch decompress <TMP_DIR>/a --format tar.gz

View File

@ -7,5 +7,5 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)"
- Files with missing extensions: <TMP_DIR>/a - Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension - Decompression formats are detected automatically from file extension
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz

View File

@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)"
- Files with unsupported extensions: <TMP_DIR>/b.unknown - Files with unsupported extensions: <TMP_DIR>/b.unknown
- Decompression formats are detected automatically from file extension - Decompression formats are detected automatically from file extension
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Alternatively, you can pass an extension to the '--format' flag: hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz

View File

@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format tar.gz.unknown\", di
[ERROR] Failed to parse `--format tar.gz.unknown` [ERROR] Failed to parse `--format tar.gz.unknown`
- Unsupported extension 'unknown' - Unsupported extension 'unknown'
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Examples: hint: Examples:
hint: --format tar hint: --format tar

View File

@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format targz\", dir)"
[ERROR] Failed to parse `--format targz` [ERROR] Failed to parse `--format targz`
- Unsupported extension 'targz' - Unsupported extension 'targz'
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Examples: hint: Examples:
hint: --format tar hint: --format tar

View File

@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format .tar.$#!@.rest\", di
[ERROR] Failed to parse `--format .tar.$#!@.rest` [ERROR] Failed to parse `--format .tar.$#!@.rest`
- Unsupported extension '$#!@' - Unsupported extension '$#!@'
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Examples: hint: Examples:
hint: --format tar hint: --format tar

View File

@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format tar.gz.unknown\", di
[ERROR] Failed to parse `--format tar.gz.unknown` [ERROR] Failed to parse `--format tar.gz.unknown`
- Unsupported extension 'unknown' - Unsupported extension 'unknown'
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Examples: hint: Examples:
hint: --format tar hint: --format tar

View File

@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format targz\", dir)"
[ERROR] Failed to parse `--format targz` [ERROR] Failed to parse `--format targz`
- Unsupported extension 'targz' - Unsupported extension 'targz'
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Examples: hint: Examples:
hint: --format tar hint: --format tar

View File

@ -5,8 +5,8 @@ expression: "run_ouch(\"ouch compress input output --format .tar.$#!@.rest\", di
[ERROR] Failed to parse `--format .tar.$#!@.rest` [ERROR] Failed to parse `--format .tar.$#!@.rest`
- Unsupported extension '$#!@' - Unsupported extension '$#!@'
hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, lz, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
hint: hint:
hint: Examples: hint: Examples:
hint: --format tar hint: --format tar

View File

@ -5,7 +5,7 @@ snapshot_kind: text
--- ---
A command-line utility for easily compressing and decompressing files and directories. A command-line utility for easily compressing and decompressing files and directories.
Supported formats: tar, zip, gz, 7z, xz/lzma, bz/bz2, bz3, lz4, sz (Snappy), zst, rar and br. Supported formats: tar, zip, gz, 7z, xz, lzma, lzip, bz/bz2, bz3, lz4, sz (Snappy), zst, rar and br.
Repository: https://github.com/ouch-org/ouch Repository: https://github.com/ouch-org/ouch