Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Categories Used:
- 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))
- 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

Expand Down
42 changes: 21 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ tar = "0.4.42"
tempfile = "3.10.1"
time = { version = "0.3.36", default-features = false }
unrar = { version = "0.5.7", optional = true }
xz2 = "0.1.7"
liblzma = "0.4"
zip = { version = "0.6.6", default-features = false, features = [
"time",
"aes-crypto",
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ Output:

# Supported formats

| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` |
|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ |
| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz` | `.lzma` | `.lz` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` |
|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓⁴ | ✓⁴ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ |

✓: Supports compression and decompression.

Expand All @@ -122,10 +122,13 @@ Output:
✓²: Supported, and compression runs in parallel.

✓³: 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
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:

Expand Down
2 changes: 1 addition & 1 deletion src/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clap::{Parser, ValueHint};
// Ouch command line options (docstrings below are part of --help)
/// 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
#[derive(Parser, Debug, PartialEq)]
Expand Down
14 changes: 12 additions & 2 deletions src/commands/compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,20 @@ pub fn compress_files(
)
}
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,
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(
gzp::par::compress::ParCompress::<gzp::snap::Snap>::builder()
.compression_level(gzp::par::compress::Compression::new(
Expand Down Expand Up @@ -108,7 +118,7 @@ pub fn compress_files(
}

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)?;
let mut reader = fs::File::open(&files[0])?;

Expand Down
12 changes: 10 additions & 2 deletions src/commands/decompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,15 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
Box::new(bzip3::read::Bz3Decoder::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)),
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
Expand All @@ -144,7 +152,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
}

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)?;

let mut writer = match utils::ask_to_create_file(
Expand Down
12 changes: 10 additions & 2 deletions src/commands/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,15 @@ pub fn list_archive_contents(
Box::new(bzip3::read::Bz3Decoder::new(decoder).unwrap())
}
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)),
Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
Brotli => Box::new(brotli::Decompressor::new(decoder, BUFFER_CAPACITY)),
Expand Down Expand Up @@ -127,7 +135,7 @@ pub fn list_archive_contents(

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.");
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ impl FinalError {
///
/// This is what it looks like:
/// ```
/// hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst
/// hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
/// hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, lz, sz, zst
/// hint: Supported aliases are: tgz, tbz, tlz4, txz, tlzma, tsz, tzst, tlz
/// ```
pub fn hint_all_supported_formats(self) -> Self {
self.hint(format!("Supported extensions are: {PRETTY_SUPPORTED_EXTENSIONS}"))
Expand Down
36 changes: 18 additions & 18 deletions src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[
"lz4",
"xz",
"lzma",
"lz",
"sz",
"zst",
#[cfg(feature = "unrar")]
Expand All @@ -27,14 +28,14 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[
"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"))]
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")]
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`
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -85,8 +86,12 @@ pub enum CompressionFormat {
Bzip3,
/// .lz4
Lz4,
/// .xz .lzma
/// .xz
Xz,
/// .lzma
Lzma,
/// .lzip
Lzip,
/// .sz
Snappy,
/// tar, tgz, tbz, tbz2, tbz3, txz, tlz4, tlzma, tsz, tzst
Expand All @@ -95,7 +100,6 @@ pub enum CompressionFormat {
Zstd,
/// .zip
Zip,
// even if built without RAR support, we still want to recognise the format
/// .rar
Rar,
/// .7z
Expand All @@ -105,19 +109,11 @@ pub enum CompressionFormat {
}

impl CompressionFormat {
/// Currently supported archive formats are .tar (and aliases to it) and .zip
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 {
Tar | Zip | Rar | SevenZip => true,
Gzip => false,
Bzip => false,
Bzip3 => false,
Lz4 => false,
Lzma => false,
Snappy => false,
Zstd => false,
Brotli => false,
Bzip | Bzip3 | Lz4 | Lzma | Xz | Lzip | Snappy | Zstd | Brotli | Gzip => false,
}
}
}
Expand All @@ -130,15 +126,19 @@ fn to_extension(ext: &[u8]) -> Option<Extension> {
b"tbz" | b"tbz2" => &[Tar, Bzip],
b"tbz3" => &[Tar, Bzip3],
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"tzst" => &[Tar, Zstd],
b"zip" => &[Zip],
b"bz" | b"bz2" => &[Bzip],
b"bz3" => &[Bzip3],
b"gz" => &[Gzip],
b"lz4" => &[Lz4],
b"xz" | b"lzma" => &[Lzma],
b"xz" => &[Xz],
b"lzma" => &[Lzma],
b"lz" => &[Lzip],
b"sz" => &[Snappy],
b"zst" => &[Zstd],
b"rar" => &[Rar],
Expand Down
12 changes: 11 additions & 1 deletion src/utils/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,15 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
fn is_bz3(buf: &[u8]) -> bool {
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 {
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 {
buf.starts_with(&[0x04, 0x22, 0x4D, 0x18])
}
Expand Down Expand Up @@ -183,8 +189,12 @@ pub fn try_infer_extension(path: &Path) -> Option<Extension> {
Some(Extension::new(&[Bzip], "bz2"))
} else if is_bz3(&buf) {
Some(Extension::new(&[Bzip3], "bz3"))
} else if is_lzma(&buf) {
Some(Extension::new(&[Lzma], "lzma"))
} 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) {
Some(Extension::new(&[Lz4], "lz4"))
} else if is_sz(&buf) {
Expand Down
2 changes: 0 additions & 2 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ enum DirectoryExtension {
Tbz3,
Tgz,
Tlz4,
Tlzma,
Tsz,
Txz,
Tzst,
Expand All @@ -50,7 +49,6 @@ enum FileExtension {
Bz3,
Gz,
Lz4,
Lzma,
Sz,
Xz,
Zst,
Expand Down
5 changes: 1 addition & 4 deletions tests/mime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ fn sanity_check_through_mime() {
write_random_content(test_file, &mut SmallRng::from_entropy());

let formats = [
"7z", "tar", "zip", "tar.gz", "tgz", "tbz", "tbz2", "txz", "tlzma", "tzst", "tar.bz", "tar.bz2", "tar.lzma",
"tar.xz", "tar.zst",
"7z", "tar", "zip", "tar.gz", "tgz", "tbz", "tbz2", "txz", "tzst", "tar.bz", "tar.bz2", "tar.xz", "tar.zst",
];

let expected_mimes = [
Expand All @@ -30,12 +29,10 @@ fn sanity_check_through_mime() {
"application/x-bzip2",
"application/x-bzip2",
"application/x-xz",
"application/x-xz",
"application/zstd",
"application/x-bzip2",
"application/x-bzip2",
"application/x-xz",
"application/x-xz",
"application/zstd",
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ expression: "run_ouch(\"ouch decompress a\", dir)"
- Files with missing extensions: <TMP_DIR>/a
- 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 aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
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, tlzma, tsz, tzst, tlz
hint:
hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/a --format tar.gz
Loading
Loading