From ba9f9c00f38f1c22b9b080eddb25dd72abd88f77 Mon Sep 17 00:00:00 2001 From: Jonas Frei Date: Wed, 20 Sep 2023 21:18:27 +0200 Subject: [PATCH] Add support for bzip3 Closes #398 Signed-off-by: Jonas Frei --- Cargo.lock | 178 ++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 + README.md | 8 +- src/cli/args.rs | 2 +- src/commands/compress.rs | 6 +- src/commands/decompress.rs | 3 +- src/commands/list.rs | 3 +- src/extension.rs | 11 ++- src/utils/fs.rs | 6 ++ 9 files changed, 198 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06a96be..97cc31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,28 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -205,6 +227,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + [[package]] name = "bzip2" version = "0.4.4" @@ -226,6 +254,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "bzip3" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a396e70a91080ca308fe4d37ebf2d15b444beef3b53853d9e36110b3a331e82e" +dependencies = [ + "byteorder", + "bytesize", + "libbzip3-sys", + "thiserror", +] + [[package]] name = "cbc" version = "0.1.2" @@ -246,6 +286,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -282,6 +331,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.20" @@ -322,7 +382,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -601,6 +661,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "globset" version = "0.4.15" @@ -661,6 +727,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "ignore" version = "0.4.23" @@ -748,12 +823,40 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libbzip3-sys" +version = "0.3.3+1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb2f193373a540601f16a3375d351d685e4ca288c766fe35ce035874c2711f1" +dependencies = [ + "bindgen", + "cc", + "cfg-if", + "regex", +] + [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "libm" version = "0.2.8" @@ -846,6 +949,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -864,6 +973,16 @@ dependencies = [ "getrandom", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nt-time" version = "0.8.1" @@ -913,7 +1032,9 @@ dependencies = [ "assert_cmd", "atty", "bstr", + "bytesize", "bzip2", + "bzip3", "clap", "clap_complete", "clap_mangen", @@ -970,7 +1091,7 @@ dependencies = [ "regex", "regex-syntax", "structmeta", - "syn", + "syn 2.0.77", ] [[package]] @@ -996,6 +1117,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pin-project" version = "1.1.5" @@ -1013,7 +1140,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1211,6 +1338,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.35" @@ -1268,7 +1401,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1361,7 +1494,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn", + "syn 2.0.77", ] [[package]] @@ -1372,7 +1505,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1381,6 +1514,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.77" @@ -1431,7 +1575,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta", - "syn", + "syn 2.0.77", ] [[package]] @@ -1451,7 +1595,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1606,7 +1750,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -1628,7 +1772,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1639,6 +1783,18 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "widestring" version = "1.1.0" @@ -1796,7 +1952,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b4516b2..96c3c91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,9 @@ description = "A command-line utility for easily compressing and decompressing f [dependencies] atty = "0.2.14" bstr = { version = "1.10.0", default-features = false, features = ["std"] } +bytesize = "1.3.0" bzip2 = "0.4.4" +bzip3 = "0.8.1" clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } diff --git a/README.md b/README.md index ee5a6cf..74c7678 100644 --- a/README.md +++ b/README.md @@ -111,9 +111,9 @@ Output: # Supported formats -| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | -|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | +| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | +|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓: Supports compression and decompression. @@ -176,7 +176,9 @@ Otherwise, you'll need these libraries installed on your system: * [liblzma](https://www.7-zip.org/sdk.html) * [libbz2](https://www.sourceware.org/bzip2) +* [libbz3](https://github.com/kspalaiologos/bzip3) * [libz](https://www.zlib.net) +>>>>>>> 066184e (Add support for bzip3) These should be available in your system's package manager. diff --git a/src/cli/args.rs b/src/cli/args.rs index dcdf768..05b522c 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -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, lz4, sz (Snappy), zst and rar. +/// Supported formats: tar, zip, gz, 7z, xz/lzma, bz/bz2, bz3, lz4, sz (Snappy), zst and rar. /// /// Repository: https://github.com/ouch-org/ouch #[derive(Parser, Debug, PartialEq)] diff --git a/src/commands/compress.rs b/src/commands/compress.rs index 00f448a..3b0ef3d 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -56,6 +56,10 @@ pub fn compress_files( encoder, level.map_or_else(Default::default, |l| bzip2::Compression::new((l as u32).clamp(1, 9))), )), + Bzip3 => Box::new( + // Unwrap is safe when using a valid block size + bzip3::write::Bz3Encoder::new(encoder, bytesize::ByteSize::mib(5).0 as usize).unwrap(), + ), Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()), Lzma => Box::new(xz2::write::XzEncoder::new( encoder, @@ -91,7 +95,7 @@ pub fn compress_files( } match first_format { - Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => { + Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { writer = chain_writer_encoder(&first_format, writer)?; let mut reader = fs::File::open(&files[0]).unwrap(); diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 0e98533..3674c4b 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -100,6 +100,7 @@ pub fn decompress_file( let decoder: Box = match format { Gzip => Box::new(flate2::read::GzDecoder::new(decoder)), Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)), + Bzip3 => 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)), Snappy => Box::new(snap::read::FrameDecoder::new(decoder)), @@ -116,7 +117,7 @@ pub fn decompress_file( } let files_unpacked = match first_extension { - Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => { + Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { reader = chain_reader_decoder(&first_extension, reader)?; let mut writer = match utils::ask_to_create_file(&output_file_path, question_policy)? { diff --git a/src/commands/list.rs b/src/commands/list.rs index fc8aa75..4d87a8c 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -50,6 +50,7 @@ pub fn list_archive_contents( let decoder: Box = match format { Gzip => Box::new(flate2::read::GzDecoder::new(decoder)), Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)), + Bzip3 => 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)), Snappy => Box::new(snap::read::FrameDecoder::new(decoder)), @@ -111,7 +112,7 @@ pub fn list_archive_contents( Box::new(sevenz::list_archive(archive_path, password)?) } - Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => { + Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { panic!("Not an archive! This should never happen, if it does, something is wrong with `CompressionFormat::is_archive()`. Please report this error!"); } }; diff --git a/src/extension.rs b/src/extension.rs index 4b0057e..0b6af9a 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -26,9 +26,9 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[ pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst"]; #[cfg(not(feature = "unrar"))] -pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z"; +pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z"; #[cfg(feature = "unrar")] -pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z"; +pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, rar, 7z"; pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tzlma, tsz, tzst"; @@ -77,13 +77,15 @@ pub enum CompressionFormat { Gzip, /// .bz .bz2 Bzip, + /// .bz3 + Bzip3, /// .lz4 Lz4, /// .xz .lzma Lzma, /// .sz Snappy, - /// tar, tgz, tbz, tbz2, txz, tlz4, tlzma, tsz, tzst + /// tar, tgz, tbz, tbz2, tbz3, txz, tlz4, tlzma, tsz, tzst Tar, /// .zst Zstd, @@ -104,6 +106,7 @@ impl CompressionFormat { Tar | Zip | Rar | SevenZip => true, Gzip => false, Bzip => false, + Bzip3 => false, Lz4 => false, Lzma => false, Snappy => false, @@ -118,12 +121,14 @@ fn to_extension(ext: &[u8]) -> Option { b"tar" => &[Tar], b"tgz" => &[Tar, Gzip], b"tbz" | b"tbz2" => &[Tar, Bzip], + b"tbz3" => &[Tar, Bzip3], b"tlz4" => &[Tar, Lz4], b"txz" | b"tlzma" => &[Tar, Lzma], 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], diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 2e4f766..a0928f2 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -82,6 +82,9 @@ pub fn try_infer_extension(path: &Path) -> Option { fn is_bz2(buf: &[u8]) -> bool { buf.starts_with(&[0x42, 0x5A, 0x68]) } + fn is_bz3(buf: &[u8]) -> bool { + buf.starts_with(bzip3::MAGIC_NUMBER) + } fn is_xz(buf: &[u8]) -> bool { buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]) } @@ -125,6 +128,8 @@ pub fn try_infer_extension(path: &Path) -> Option { Some(Extension::new(&[Gzip], "gz")) } else if is_bz2(&buf) { Some(Extension::new(&[Bzip], "bz2")) + } else if is_bz3(&buf) { + Some(Extension::new(&[Bzip3], "bz3")) } else if is_xz(&buf) { Some(Extension::new(&[Lzma], "xz")) } else if is_lz4(&buf) { @@ -143,6 +148,7 @@ pub fn try_infer_extension(path: &Path) -> Option { } /// Returns true if a path is a symlink. +/// /// This is the same as the nightly /// Useful to detect broken symlinks when compressing. (So we can safely ignore them) pub fn is_symlink(path: &Path) -> bool {