From 83f28cf64a07cf36909451df594c96e3a0098d42 Mon Sep 17 00:00:00 2001 From: valoq Date: Sat, 5 Oct 2024 16:24:57 +0200 Subject: [PATCH 001/101] change temporary path to a more unique name --- src/commands/decompress.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 55086a2..ec25e22 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -248,7 +248,9 @@ fn smart_unpack( question_policy: QuestionPolicy, ) -> crate::Result> { assert!(output_dir.exists()); - let temp_dir = tempfile::tempdir_in(output_dir)?; + let temp_dir = tempfile::Builder::new() + .prefix(".tmp-ouch-") + .tempdir_in(output_dir)?; let temp_dir_path = temp_dir.path(); info_accessible(format!( From e2151c93d5227f6ff6373845f818e8c55fbc99c4 Mon Sep 17 00:00:00 2001 From: valoq Date: Sun, 6 Oct 2024 13:31:12 +0200 Subject: [PATCH 002/101] fix format to appease linter --- src/commands/decompress.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index ec25e22..0e98533 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -248,9 +248,7 @@ fn smart_unpack( question_policy: QuestionPolicy, ) -> crate::Result> { assert!(output_dir.exists()); - let temp_dir = tempfile::Builder::new() - .prefix(".tmp-ouch-") - .tempdir_in(output_dir)?; + let temp_dir = tempfile::Builder::new().prefix(".tmp-ouch-").tempdir_in(output_dir)?; let temp_dir_path = temp_dir.path(); info_accessible(format!( From 7e30545e96d0216350dc51dcb9a30942bba659a9 Mon Sep 17 00:00:00 2001 From: valoq Date: Sun, 6 Oct 2024 13:34:06 +0200 Subject: [PATCH 003/101] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a3b6f..e76aef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Categories Used: ### Tweaks - CI refactor [\#578](https://github.com/ouch-org/ouch/pull/578) ([cyqsimon](https://github.com/cyqsimon)) +- Use a more unique name for temporary decompression path [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) ### Improvements From f3af70915d1b9ad9b6253366ae7f5f5a9ca1ec09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:29:51 +0000 Subject: [PATCH 004/101] build(deps): bump ignore from 0.4.22 to 0.4.23 Bumps [ignore](https://github.com/BurntSushi/ripgrep) from 0.4.22 to 0.4.23. - [Release notes](https://github.com/BurntSushi/ripgrep/releases) - [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md) - [Commits](https://github.com/BurntSushi/ripgrep/commits) --- updated-dependencies: - dependency-name: ignore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf27574..5feee1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,9 +603,9 @@ dependencies = [ [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", diff --git a/Cargo.toml b/Cargo.toml index aaa1fba..06f9e94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } fs-err = "2.11.0" gzp = { version = "0.11.3", default-features = false, features = ["snappy_default"] } -ignore = "0.4.22" +ignore = "0.4.23" libc = "0.2.155" linked-hash-map = "0.5.6" lz4_flex = "0.11.3" From be40b4439cb9fffc1138be80fc308638d887e513 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:29:52 +0000 Subject: [PATCH 005/101] build(deps): bump clap_complete from 4.5.24 to 4.5.28 Bumps [clap_complete](https://github.com/clap-rs/clap) from 4.5.24 to 4.5.28. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.24...clap_complete-v4.5.28) --- updated-dependencies: - dependency-name: clap_complete dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5feee1e..aeae36a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.24" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" +checksum = "9b378c786d3bde9442d2c6dd7e6080b2a818db2b96e30d6e7f1b6d224eb617d3" dependencies = [ "clap", ] diff --git a/Cargo.toml b/Cargo.toml index 06f9e94..d1cfd60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ is_executable = "1.0.1" [build-dependencies] clap = { version = "4.5.16", features = ["derive", "env", "string"] } -clap_complete = "4.5.13" +clap_complete = "4.5.28" clap_mangen = "0.2.20" [dev-dependencies] From 1876f5eef1e98c10fa6ad99d38a78a61e1b2f38f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:29:42 +0000 Subject: [PATCH 006/101] build(deps): bump insta from 1.39.0 to 1.40.0 Bumps [insta](https://github.com/mitsuhiko/insta) from 1.39.0 to 1.40.0. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.39.0...1.40.0) --- updated-dependencies: - dependency-name: insta dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aeae36a..8fbd15b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,9 +698,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" dependencies = [ "console", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index d1cfd60..14fe0af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ clap_mangen = "0.2.20" [dev-dependencies] assert_cmd = "2.0.14" infer = "0.16.0" -insta = { version = "1.39.0", features = ["filters"] } +insta = { version = "1.40.0", features = ["filters"] } parse-display = "0.9.1" proptest = "1.5.0" rand = { version = "0.8.5", default-features = false, features = ["small_rng", "std"] } From 3f6368dbe144bdfbf30326ad15f95297810312aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:08:25 +0000 Subject: [PATCH 007/101] build(deps): bump clap from 4.5.16 to 4.5.20 Bumps [clap](https://github.com/clap-rs/clap) from 4.5.16 to 4.5.20. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.16...clap_complete-v4.5.20) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 12 ++++++------ Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fbd15b..0f50b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,9 +284,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 14fe0af..4f46303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ description = "A command-line utility for easily compressing and decompressing f atty = "0.2.14" bstr = { version = "1.10.0", default-features = false, features = ["std"] } bzip2 = "0.4.4" -clap = { version = "4.5.16", features = ["derive", "env"] } +clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } fs-err = "2.11.0" @@ -41,7 +41,7 @@ zstd = { version = "0.13.2", default-features = false, features = ["zstdmt"]} is_executable = "1.0.1" [build-dependencies] -clap = { version = "4.5.16", features = ["derive", "env", "string"] } +clap = { version = "4.5.20", features = ["derive", "env", "string"] } clap_complete = "4.5.28" clap_mangen = "0.2.20" From 49c8a079a6ef3ee166aa9ebcccf18534c314384f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Mon, 28 Oct 2024 20:41:34 -0300 Subject: [PATCH 008/101] Update dependabot.yml --- .github/dependabot.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ad2f84d..04d0861 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,8 +5,3 @@ updates: directory: "/" schedule: interval: "daily" - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" From d7427b25ab4de27b290363614a4fd16004422058 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:16:51 +0000 Subject: [PATCH 009/101] build(deps): bump clap_mangen from 0.2.23 to 0.2.24 Bumps [clap_mangen](https://github.com/clap-rs/clap) from 0.2.23 to 0.2.24. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/clap_mangen-v0.2.23...clap_mangen-v0.2.24) --- updated-dependencies: - dependency-name: clap_mangen dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f50b03..3a185ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,9 +333,9 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clap_mangen" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17415fd4dfbea46e3274fcd8d368284519b358654772afb700dc2e8d2b24eeb" +checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" dependencies = [ "clap", "roff", diff --git a/Cargo.toml b/Cargo.toml index 4f46303..8da8b5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ is_executable = "1.0.1" [build-dependencies] clap = { version = "4.5.20", features = ["derive", "env", "string"] } clap_complete = "4.5.28" -clap_mangen = "0.2.20" +clap_mangen = "0.2.24" [dev-dependencies] assert_cmd = "2.0.14" From d098fec5cf54b9181b66c51dc3d561e396d46032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:16:57 +0000 Subject: [PATCH 010/101] build(deps): bump tar from 0.4.41 to 0.4.42 Bumps [tar](https://github.com/alexcrichton/tar-rs) from 0.4.41 to 0.4.42. - [Commits](https://github.com/alexcrichton/tar-rs/compare/0.4.41...0.4.42) --- updated-dependencies: - dependency-name: tar dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a185ad..a80de1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,9 +1394,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", diff --git a/Cargo.toml b/Cargo.toml index 8da8b5c..cce54d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ rayon = "1.10.0" same-file = "1.0.6" sevenz-rust = { version = "0.6.1", features = ["compress", "aes256"] } snap = "1.1.1" -tar = "0.4.41" +tar = "0.4.42" tempfile = "3.10.1" time = { version = "0.3.36", default-features = false } unrar = { version = "0.5.6", optional = true } From 896562d76d05d6971b39d81a964a01de78c221be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:17:03 +0000 Subject: [PATCH 011/101] build(deps): bump once_cell from 1.19.0 to 1.20.2 Bumps [once_cell](https://github.com/matklad/once_cell) from 1.19.0 to 1.20.2. - [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md) - [Commits](https://github.com/matklad/once_cell/compare/v1.19.0...v1.20.2) --- updated-dependencies: - dependency-name: once_cell dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a80de1d..06a96be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -902,9 +902,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "ouch" diff --git a/Cargo.toml b/Cargo.toml index cce54d7..b4516b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ libc = "0.2.155" linked-hash-map = "0.5.6" lz4_flex = "0.11.3" num_cpus = "1.16.0" -once_cell = "1.19.0" +once_cell = "1.20.2" rayon = "1.10.0" same-file = "1.0.6" sevenz-rust = { version = "0.6.1", features = ["compress", "aes256"] } From 362418364fe8c0c2ec9f1284a9b35f26e4a1f69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Tue, 29 Oct 2024 23:55:38 -0300 Subject: [PATCH 012/101] remove dependabot --- .github/dependabot.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 04d0861..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "daily" From ba9f9c00f38f1c22b9b080eddb25dd72abd88f77 Mon Sep 17 00:00:00 2001 From: Jonas Frei Date: Wed, 20 Sep 2023 21:18:27 +0200 Subject: [PATCH 013/101] 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 { From 32b50e9c7aacb4ae8c4a853903f3519895b14177 Mon Sep 17 00:00:00 2001 From: Jonas Frei Date: Thu, 21 Sep 2023 05:55:37 +0200 Subject: [PATCH 014/101] Added test code, handled BlockSize error, block size = 16MiB Signed-off-by: Jonas Frei --- README.md | 1 - src/commands/compress.rs | 4 ++-- src/error.rs | 12 ++++++++++++ tests/integration.rs | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74c7678..70caede 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,6 @@ Otherwise, you'll need these libraries installed on your system: * [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/commands/compress.rs b/src/commands/compress.rs index 3b0ef3d..986b11d 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -57,8 +57,8 @@ pub fn compress_files( 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(), + // Use block size of 16 MiB + bzip3::write::Bz3Encoder::new(encoder, 16 * 2_usize.pow(20))?, ), Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()), Lzma => Box::new(xz2::write::XzEncoder::new( diff --git a/src/error.rs b/src/error.rs index 2a3a2b1..b8c274d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -200,6 +200,18 @@ impl From for Error { } } +impl From for Error { + fn from(err: bzip3::Error) -> Self { + use bzip3::Error as Bz3Error; + match err { + Bz3Error::Io(inner) => inner.into(), + Bz3Error::BlockSize | Bz3Error::ProcessBlock(_) | Bz3Error::InvalidSignature => { + FinalError::with_title("bzip3 error").detail(err.to_string()).into() + } + } + } +} + impl From for Error { fn from(err: zip::result::ZipError) -> Self { use zip::result::ZipError; diff --git a/tests/integration.rs b/tests/integration.rs index b31258e..82315c3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -21,6 +21,7 @@ enum DirectoryExtension { Tar, Tbz, Tbz2, + Tbz3, Tgz, Tlz4, Tlzma, @@ -36,6 +37,7 @@ enum DirectoryExtension { enum FileExtension { Bz, Bz2, + Bz3, Gz, Lz4, Lzma, From 8edd8d2e1c17ed6724386c465061806c2a2cbc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Thu, 21 Sep 2023 19:09:49 -0300 Subject: [PATCH 015/101] Replace `.unwrap()`s by `?` --- src/commands/compress.rs | 2 +- src/commands/decompress.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/compress.rs b/src/commands/compress.rs index 986b11d..180bb2e 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -97,7 +97,7 @@ pub fn compress_files( match first_format { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { writer = chain_writer_encoder(&first_format, writer)?; - let mut reader = fs::File::open(&files[0]).unwrap(); + let mut reader = fs::File::open(&files[0])?; io::copy(&mut reader, &mut writer)?; } diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 3674c4b..7c5c842 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -100,7 +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()), + Bzip3 => Box::new(bzip3::read::Bz3Decoder::new(decoder)?), 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)), From ae9f4e0151b24dbf59494e86d0d5967c6f1366af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Thu, 21 Sep 2023 18:11:26 -0300 Subject: [PATCH 016/101] Add `bundled` feature to `bzip3` --- Cargo.lock | 8 +++----- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97cc31c..18e45fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,9 +256,8 @@ dependencies = [ [[package]] name = "bzip3" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a396e70a91080ca308fe4d37ebf2d15b444beef3b53853d9e36110b3a331e82e" +version = "0.8.1" +source = "git+https://github.com/bczhc/bzip3-rs?rev=094b80c321732636f32733cc72f05ac3deffbe88#094b80c321732636f32733cc72f05ac3deffbe88" dependencies = [ "byteorder", "bytesize", @@ -832,8 +831,7 @@ 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" +source = "git+https://github.com/bczhc/bzip3-rs?rev=094b80c321732636f32733cc72f05ac3deffbe88#094b80c321732636f32733cc72f05ac3deffbe88" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 96c3c91..2784653 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ 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" +bzip3 = { git = "https://github.com/bczhc/bzip3-rs", rev = "094b80c321732636f32733cc72f05ac3deffbe88", features = ["bundled"] } clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } From 179b0555451d26b19521f534b96803228791fc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Thu, 21 Sep 2023 18:17:41 -0300 Subject: [PATCH 017/101] CI: install libclang on Linux --- .github/workflows/build-and-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8200d44..861707b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -122,7 +122,6 @@ jobs: target/${{ matrix.target }}/release/ouch.exe artifacts/ - clippy-rustfmt: name: clippy-rustfmt runs-on: ubuntu-latest From 011a29f208177e4b7a28120f00a697008c0cd2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 22 Oct 2023 23:39:32 -0300 Subject: [PATCH 018/101] Bump bzip3 version to 0.8.3 --- Cargo.lock | 8 +++++--- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18e45fd..97cc31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,8 +256,9 @@ dependencies = [ [[package]] name = "bzip3" -version = "0.8.1" -source = "git+https://github.com/bczhc/bzip3-rs?rev=094b80c321732636f32733cc72f05ac3deffbe88#094b80c321732636f32733cc72f05ac3deffbe88" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a396e70a91080ca308fe4d37ebf2d15b444beef3b53853d9e36110b3a331e82e" dependencies = [ "byteorder", "bytesize", @@ -831,7 +832,8 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libbzip3-sys" version = "0.3.3+1.3.2" -source = "git+https://github.com/bczhc/bzip3-rs?rev=094b80c321732636f32733cc72f05ac3deffbe88#094b80c321732636f32733cc72f05ac3deffbe88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb2f193373a540601f16a3375d351d685e4ca288c766fe35ce035874c2711f1" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 2784653..39d0e27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ atty = "0.2.14" bstr = { version = "1.10.0", default-features = false, features = ["std"] } bytesize = "1.3.0" bzip2 = "0.4.4" -bzip3 = { git = "https://github.com/bczhc/bzip3-rs", rev = "094b80c321732636f32733cc72f05ac3deffbe88", features = ["bundled"] } +bzip3 = { version = "0.8.3", features = ["bundled"] } clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } From 28060ded3a8cbbe9cfe8cd3167b2334bb5c0754e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 22 Oct 2023 23:53:27 -0300 Subject: [PATCH 019/101] Add LGPL legal notices for bzip3[-rs] --- LICENSE | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 7011177..df500f5 100644 --- a/LICENSE +++ b/LICENSE @@ -20,7 +20,18 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--- + Copyright notices from other projects: -Copyright (c) 2019 Bojan -https://github.com/bojand/infer +Infer crate (MIT LICENSE): +> Copyright (c) 2019 Bojan +> Code at https://github.com/bojand/infer + +Bzip3-rs crate (LGPL 3.0): +> Code for this crate is available at https://github.com/bczhc/bzip3-rs +> See its license at https://github.com/bczhc/bzip3-rs/blob/master/LICENSE + +Bzip3 library (LGPL 3.0): +> Code for this library is available at https://github.com/kspalaiologos/bzip3 +> See its license at https://github.com/kspalaiologos/bzip3/blob/master/LICENSE From 730ccbcf2a9ed12b2b6763354bcda30d462ebcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Fri, 29 Sep 2023 16:38:11 -0300 Subject: [PATCH 020/101] Update snapshot tests --- ...ui__ui_test_err_decompress_missing_extension_with_rar-1.snap | 2 +- ...ui__ui_test_err_decompress_missing_extension_with_rar-2.snap | 2 +- ...ui__ui_test_err_decompress_missing_extension_with_rar-3.snap | 2 +- ..._ui_test_err_decompress_missing_extension_without_rar-1.snap | 2 +- ..._ui_test_err_decompress_missing_extension_without_rar-2.snap | 2 +- ..._ui_test_err_decompress_missing_extension_without_rar-3.snap | 2 +- tests/snapshots/ui__ui_test_err_format_flag_with_rar-1.snap | 2 +- tests/snapshots/ui__ui_test_err_format_flag_with_rar-2.snap | 2 +- tests/snapshots/ui__ui_test_err_format_flag_with_rar-3.snap | 2 +- tests/snapshots/ui__ui_test_err_format_flag_without_rar-1.snap | 2 +- tests/snapshots/ui__ui_test_err_format_flag_without_rar-2.snap | 2 +- tests/snapshots/ui__ui_test_err_format_flag_without_rar-3.snap | 2 +- tests/snapshots/ui__ui_test_usage_help_flag.snap | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-1.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-1.snap index 8322cf2..61ffed6 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-1.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-1.snap @@ -6,7 +6,7 @@ expression: "run_ouch(\"ouch decompress a\", dir)" - Files with missing extensions: /a - Decompression formats are detected automatically from file extension -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z +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: hint: Alternatively, you can pass an extension to the '--format' flag: diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-2.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-2.snap index 643702b..fe1bbf9 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-2.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-2.snap @@ -7,5 +7,5 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)" - Files with missing extensions: /a - Decompression formats are detected automatically from file extension -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z +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 diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-3.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-3.snap index 96a49a2..0ef66a2 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-3.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_with_rar-3.snap @@ -6,7 +6,7 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)" - Files with unsupported extensions: /b.unknown - Decompression formats are detected automatically from file extension -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z +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: hint: Alternatively, you can pass an extension to the '--format' flag: diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-1.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-1.snap index fec78bb..2885d83 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-1.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-1.snap @@ -6,7 +6,7 @@ expression: "run_ouch(\"ouch decompress a\", dir)" - Files with missing extensions: /a - Decompression formats are detected automatically from file extension -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z +hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: hint: Alternatively, you can pass an extension to the '--format' flag: diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-2.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-2.snap index 4ccff13..9cdcbd6 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-2.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-2.snap @@ -7,5 +7,5 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)" - Files with missing extensions: /a - Decompression formats are detected automatically from file extension -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z +hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst diff --git a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-3.snap b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-3.snap index d8b9076..5e57d8b 100644 --- a/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-3.snap +++ b/tests/snapshots/ui__ui_test_err_decompress_missing_extension_without_rar-3.snap @@ -6,7 +6,7 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)" - Files with unsupported extensions: /b.unknown - Decompression formats are detected automatically from file extension -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z +hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: hint: Alternatively, you can pass an extension to the '--format' flag: diff --git a/tests/snapshots/ui__ui_test_err_format_flag_with_rar-1.snap b/tests/snapshots/ui__ui_test_err_format_flag_with_rar-1.snap index ca5e05c..5cb36e4 100644 --- a/tests/snapshots/ui__ui_test_err_format_flag_with_rar-1.snap +++ b/tests/snapshots/ui__ui_test_err_format_flag_with_rar-1.snap @@ -5,7 +5,7 @@ expression: "run_ouch(\"ouch compress input output --format tar.gz.unknown\", di [ERROR] Failed to parse `--format tar.gz.unknown` - Unsupported extension 'unknown' -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z +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: hint: Examples: diff --git a/tests/snapshots/ui__ui_test_err_format_flag_with_rar-2.snap b/tests/snapshots/ui__ui_test_err_format_flag_with_rar-2.snap index d8a14c9..a1196c7 100644 --- a/tests/snapshots/ui__ui_test_err_format_flag_with_rar-2.snap +++ b/tests/snapshots/ui__ui_test_err_format_flag_with_rar-2.snap @@ -5,7 +5,7 @@ expression: "run_ouch(\"ouch compress input output --format targz\", dir)" [ERROR] Failed to parse `--format targz` - Unsupported extension 'targz' -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z +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: hint: Examples: diff --git a/tests/snapshots/ui__ui_test_err_format_flag_with_rar-3.snap b/tests/snapshots/ui__ui_test_err_format_flag_with_rar-3.snap index a1c8aab..4269a23 100644 --- a/tests/snapshots/ui__ui_test_err_format_flag_with_rar-3.snap +++ b/tests/snapshots/ui__ui_test_err_format_flag_with_rar-3.snap @@ -5,7 +5,7 @@ expression: "run_ouch(\"ouch compress input output --format .tar.$#!@.rest\", di [ERROR] Failed to parse `--format .tar.$#!@.rest` - Unsupported extension '$#!@' -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z +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: hint: Examples: diff --git a/tests/snapshots/ui__ui_test_err_format_flag_without_rar-1.snap b/tests/snapshots/ui__ui_test_err_format_flag_without_rar-1.snap index e386260..cd7ccbc 100644 --- a/tests/snapshots/ui__ui_test_err_format_flag_without_rar-1.snap +++ b/tests/snapshots/ui__ui_test_err_format_flag_without_rar-1.snap @@ -5,7 +5,7 @@ expression: "run_ouch(\"ouch compress input output --format tar.gz.unknown\", di [ERROR] Failed to parse `--format tar.gz.unknown` - Unsupported extension 'unknown' -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z +hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: hint: Examples: diff --git a/tests/snapshots/ui__ui_test_err_format_flag_without_rar-2.snap b/tests/snapshots/ui__ui_test_err_format_flag_without_rar-2.snap index 3ac4dfa..0913264 100644 --- a/tests/snapshots/ui__ui_test_err_format_flag_without_rar-2.snap +++ b/tests/snapshots/ui__ui_test_err_format_flag_without_rar-2.snap @@ -5,7 +5,7 @@ expression: "run_ouch(\"ouch compress input output --format targz\", dir)" [ERROR] Failed to parse `--format targz` - Unsupported extension 'targz' -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z +hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: hint: Examples: diff --git a/tests/snapshots/ui__ui_test_err_format_flag_without_rar-3.snap b/tests/snapshots/ui__ui_test_err_format_flag_without_rar-3.snap index f317bbf..7d9b12c 100644 --- a/tests/snapshots/ui__ui_test_err_format_flag_without_rar-3.snap +++ b/tests/snapshots/ui__ui_test_err_format_flag_without_rar-3.snap @@ -5,7 +5,7 @@ expression: "run_ouch(\"ouch compress input output --format .tar.$#!@.rest\", di [ERROR] Failed to parse `--format .tar.$#!@.rest` - Unsupported extension '$#!@' -hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z +hint: Supported extensions are: tar, zip, bz, bz2, bz3, gz, lz4, xz, lzma, sz, zst, 7z hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst hint: hint: Examples: diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap index 3b5cd8f..921aa8d 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -4,7 +4,7 @@ expression: "output_to_string(ouch!(\"--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 From b65ee9c3f4d915dc91ffd3103388a9b0bcc2cea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 26 Aug 2024 05:28:22 -0300 Subject: [PATCH 021/101] bump bzip3 version to 0.9.0 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97cc31c..caf9997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "bzip3" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a396e70a91080ca308fe4d37ebf2d15b444beef3b53853d9e36110b3a331e82e" +checksum = "e908c8163856f1138894e7bce70d2ae79c2d9a19a515dfe96e10888a839aa10c" dependencies = [ "byteorder", "bytesize", @@ -831,9 +831,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libbzip3-sys" -version = "0.3.3+1.3.2" +version = "0.4.0+1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb2f193373a540601f16a3375d351d685e4ca288c766fe35ce035874c2711f1" +checksum = "001a0e4d146f247a741755ebc538d592b3603923df85d6e4472e4268822b5d6b" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 39d0e27..3c34e34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ atty = "0.2.14" bstr = { version = "1.10.0", default-features = false, features = ["std"] } bytesize = "1.3.0" bzip2 = "0.4.4" -bzip3 = { version = "0.8.3", features = ["bundled"] } +bzip3 = { version = "0.9.0", features = ["bundled"] } clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } From e7d6c5e0f54965f2070933fe71718f98341ced8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 01:44:23 -0300 Subject: [PATCH 022/101] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e76aef7..a22b3ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Categories Used: ### New Features - Add multithreading support for `zstd` compression [\#689](https://github.com/ouch-org/ouch/pull/689) ([nalabrie](https://github.com/nalabrie)) +- Add `bzip3` support [\#522](https://github.com/ouch-org/ouch/pull/522) ([freijon](https://github.com/freijon)) ### Bug Fixes From ca31742394fda061b9ca3ff5e3dae6de3aafa07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 14:51:33 -0300 Subject: [PATCH 023/101] run clippy for tests too --- .github/workflows/build-and-test.yml | 1 + tests/integration.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 861707b..752db32 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -134,6 +134,7 @@ jobs: rustup toolchain install stable --profile minimal -c clippy rustup toolchain install nightly --profile minimal -c rustfmt cargo +stable clippy -- -D warnings + cargo +stable clippy --tests -- -D warnings cargo +nightly fmt -- --check github-release: diff --git a/tests/integration.rs b/tests/integration.rs index 82315c3..ed2b4b1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -79,7 +79,7 @@ fn create_random_files(dir: impl Into, depth: u8, rng: &mut SmallRng) { // create more random files in 0 to 2 new directories for _ in 0..rng.gen_range(0..=2u32) { - create_random_files(&tempfile::tempdir_in(dir).unwrap().into_path(), depth - 1, rng); + create_random_files(tempfile::tempdir_in(dir).unwrap().into_path(), depth - 1, rng); } } From 1d70a810e575f0694b772fc7dce16afb8fee8cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 14:52:22 -0300 Subject: [PATCH 024/101] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a22b3ec..94d03ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Categories Used: - CI refactor [\#578](https://github.com/ouch-org/ouch/pull/578) ([cyqsimon](https://github.com/cyqsimon)) - Use a more unique name for temporary decompression path [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) +- Run clippy for tests too [\#738](https://github.com/ouch-org/ouch/pull/738) ([marcospb19](https://github.com/marcospb19)) ### Improvements From 1c6fb9a0b3ed8278cf55f6002c243c1f612dcf9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 17:30:05 -0300 Subject: [PATCH 025/101] CI: refac: reorganizing + renaming --- .github/workflows/build-and-test.yml | 91 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 752db32..9164d12 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -1,4 +1,4 @@ -name: build-and-test +name: PR checks on: push: @@ -9,14 +9,54 @@ on: pull_request: jobs: - build: - name: build - runs-on: ${{ matrix.os }} + clippy-rustfmt: + name: clippy-rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: "Cargo: clippy, fmt" + run: | + rustup toolchain install stable --profile minimal -c clippy + rustup toolchain install nightly --profile minimal -c rustfmt + cargo +stable clippy -- -D warnings + cargo +nightly fmt -- --check + + github-release: + name: github-release + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + needs: build-and-test + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download artifacts + uses: dawidd6/action-download-artifact@v3 + with: + path: artifacts + + - name: Package release assets + run: scripts/package-release-assets.sh + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + draft: true + files: release/ouch-* + + build-and-test: + name: build-and-test + runs-on: ${{ matrix.os || 'ubuntu-latest' }} env: CARGO: cargo strategy: fail-fast: false matrix: + feature-use-zlib: [true, false] + feature-use-zstd-thin: [true, false] + feature-unrar: [true, false] target: # native - x86_64-unknown-linux-gnu @@ -30,13 +70,8 @@ jobs: - aarch64-unknown-linux-musl - armv7-unknown-linux-gnueabihf - armv7-unknown-linux-musleabihf - feature-use-zlib: [true, false] - feature-use-zstd-thin: [true, false] - feature-unrar: [true, false] include: - # default runner - - os: ubuntu-latest # runner overrides - target: x86_64-pc-windows-gnu os: windows-latest @@ -121,41 +156,3 @@ jobs: target/${{ matrix.target }}/release/ouch target/${{ matrix.target }}/release/ouch.exe artifacts/ - - clippy-rustfmt: - name: clippy-rustfmt - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: "Cargo: clippy, fmt" - run: | - rustup toolchain install stable --profile minimal -c clippy - rustup toolchain install nightly --profile minimal -c rustfmt - cargo +stable clippy -- -D warnings - cargo +stable clippy --tests -- -D warnings - cargo +nightly fmt -- --check - - github-release: - name: github-release - runs-on: ubuntu-latest - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - needs: build - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Download artifacts - uses: dawidd6/action-download-artifact@v3 - with: - path: artifacts - - - name: Package release assets - run: scripts/package-release-assets.sh - - - name: Create release - uses: softprops/action-gh-release@v2 - with: - draft: true - files: release/ouch-* From 40f1234ad0557a5af6a6a160c6f4480415ee3cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 18:55:41 -0300 Subject: [PATCH 026/101] CI: enha: don't run if only `.md` was modified --- .github/workflows/build-and-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9164d12..177c02e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -7,6 +7,8 @@ on: tags: - "[0-9]+.[0-9]+.[0-9]+" pull_request: + paths-ignore: + - "*.md" jobs: clippy-rustfmt: From 256fedbcc29a7b63be430abd9a516345154f215d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 18:56:21 -0300 Subject: [PATCH 027/101] CI: tweak: don't run CI for all features --- .github/workflows/build-and-test.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 177c02e..cf75e4e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -56,9 +56,9 @@ jobs: strategy: fail-fast: false matrix: - feature-use-zlib: [true, false] - feature-use-zstd-thin: [true, false] - feature-unrar: [true, false] + feature-use-zlib: [false] + feature-use-zstd-thin: [false] + feature-unrar: [false] target: # native - x86_64-unknown-linux-gnu @@ -94,6 +94,13 @@ jobs: use-cross: true - target: armv7-unknown-linux-musleabihf use-cross: true + # features + - feature-use-zlib: true + target: x86_64-unknown-linux-gnu + - feature-use-zstd-thin: true + target: x86_64-unknown-linux-gnu + - feature-unrar: true + target: x86_64-unknown-linux-gnu steps: - name: Checkout @@ -114,9 +121,9 @@ jobs: shell: bash run: | FEATURES=() - if [[ ${{ matrix.feature-use-zlib }} == true ]]; then FEATURES+=(use_zlib); fi - if [[ ${{ matrix.feature-use-zstd-thin }} == true ]]; then FEATURES+=(use_zstd_thin); fi - if [[ ${{ matrix.feature-unrar }} == true ]]; then FEATURES+=(unrar); fi + if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi + if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi + if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi IFS=',' echo "FEATURES=${FEATURES[*]}" >> $GITHUB_OUTPUT From 48f83e75f77efb849d4d99eb4f46b5acf5e1481d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 18:59:34 -0300 Subject: [PATCH 028/101] CI: tweak: separate clippy and rustfmt jobs --- .github/workflows/build-and-test.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index cf75e4e..54eab73 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -11,18 +11,28 @@ on: - "*.md" jobs: - clippy-rustfmt: - name: clippy-rustfmt + clippy-lints-check: + name: clippy-lints-check runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: "Cargo: clippy, fmt" + - name: "Cargo: clippy" run: | rustup toolchain install stable --profile minimal -c clippy - rustup toolchain install nightly --profile minimal -c rustfmt cargo +stable clippy -- -D warnings + + rustfmt-check: + name: rustfmt-check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: "Cargo: fmt" + run: | + rustup toolchain install nightly --profile minimal -c rustfmt cargo +nightly fmt -- --check github-release: From 534d39c0693f2bfb74e0c5e859a4aa2ab5496912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 19:04:23 -0300 Subject: [PATCH 029/101] CI: chore: rename jobs and workflow files --- ...ifacts.yml => manual-trigger-draft-release.yml} | 9 ++++++--- .../{build-and-test.yml => pr-workflow.yml} | 14 +++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) rename .github/workflows/{create-draft-release-with-artifacts.yml => manual-trigger-draft-release.yml} (73%) rename .github/workflows/{build-and-test.yml => pr-workflow.yml} (96%) diff --git a/.github/workflows/create-draft-release-with-artifacts.yml b/.github/workflows/manual-trigger-draft-release.yml similarity index 73% rename from .github/workflows/create-draft-release-with-artifacts.yml rename to .github/workflows/manual-trigger-draft-release.yml index 0b46fa6..530018f 100644 --- a/.github/workflows/create-draft-release-with-artifacts.yml +++ b/.github/workflows/manual-trigger-draft-release.yml @@ -1,4 +1,8 @@ -name: create-draft-release-with-artifacts +# we have two workflows for releases, this is the manual one to be triggered +# in the Actions tab in the repository +# +# the automatic one runs in another workflow and checks for tag pushes +name: manual-trigger-draft-release on: workflow_dispatch: @@ -8,8 +12,7 @@ on: required: true jobs: - github-release: - name: github-release + create-draft-release-from-manual-trigger: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/pr-workflow.yml similarity index 96% rename from .github/workflows/build-and-test.yml rename to .github/workflows/pr-workflow.yml index 54eab73..54c8fe3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/pr-workflow.yml @@ -1,4 +1,4 @@ -name: PR checks +name: PR workflow on: push: @@ -11,8 +11,7 @@ on: - "*.md" jobs: - clippy-lints-check: - name: clippy-lints-check + clippy-checks: runs-on: ubuntu-latest steps: - name: Checkout @@ -24,7 +23,6 @@ jobs: cargo +stable clippy -- -D warnings rustfmt-check: - name: rustfmt-check runs-on: ubuntu-latest steps: - name: Checkout @@ -35,11 +33,10 @@ jobs: rustup toolchain install nightly --profile minimal -c rustfmt cargo +nightly fmt -- --check - github-release: - name: github-release + automated-draft-release: runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - needs: build-and-test + needs: build-artifacts-and-run-tests steps: - name: Checkout uses: actions/checkout@v4 @@ -58,8 +55,7 @@ jobs: draft: true files: release/ouch-* - build-and-test: - name: build-and-test + build-artifacts-and-run-tests: runs-on: ${{ matrix.os || 'ubuntu-latest' }} env: CARGO: cargo From 269058089fd962a7e845f4870aeb763f032864f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 19:19:14 -0300 Subject: [PATCH 030/101] CI: tweak: make workflows reusable --- .github/workflows/all-tests-slow.yml | 17 ++ .../build-artifacts-and-run-tests.yml | 146 ++++++++++++++++ .../draft-release-automatic-trigger.yml | 35 ++++ ...e.yml => draft-release-manual-trigger.yml} | 6 +- .github/workflows/pr-workflow.yml | 156 +----------------- 5 files changed, 208 insertions(+), 152 deletions(-) create mode 100644 .github/workflows/all-tests-slow.yml create mode 100644 .github/workflows/build-artifacts-and-run-tests.yml create mode 100644 .github/workflows/draft-release-automatic-trigger.yml rename .github/workflows/{manual-trigger-draft-release.yml => draft-release-manual-trigger.yml} (78%) diff --git a/.github/workflows/all-tests-slow.yml b/.github/workflows/all-tests-slow.yml new file mode 100644 index 0000000..7a5a644 --- /dev/null +++ b/.github/workflows/all-tests-slow.yml @@ -0,0 +1,17 @@ +name: Run tests for all combinations + +on: + schedule: + - cron: "0 0 1,15 * *" # biweekly + push: + branches: + - main + paths-ignore: + - "*.md" + +jobs: + run-tests-for-all-combinations: + uses: ouch-org/ouch/.github/workflows/build-artifacts-and-run-tests.yml@main + with: + matrix_all_combinations: true + upload_artifacts: false diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml new file mode 100644 index 0000000..788c7a1 --- /dev/null +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -0,0 +1,146 @@ +# This is a reusable workflow + +name: Build artifacts and run tests + +on: + workflow_dispatch: + inputs: + matrix_all_combinations: + description: "if matrix should have all combinations of targets and features" + type: boolean + required: true + default: true + upload_artifacts: + description: "if built artifacts should be uploaded" + type: boolean + required: true + default: true + workflow_call: + inputs: + matrix_all_combinations: + description: "if matrix should have all combinations of targets and features" + type: boolean + required: true + upload_artifacts: + description: "if built artifacts should be uploaded" + type: boolean + required: true + +jobs: + build-artifacts-and-run-tests: + runs-on: ${{ matrix.os || 'ubuntu-latest' }} + env: + CARGO: cargo + strategy: + fail-fast: false + matrix: + feature-unrar: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[false]')}} + feature-use-zlib: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[false]')}} + feature-use-zstd-thin: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[false]')}} + target: + # native + - x86_64-unknown-linux-gnu + - x86_64-pc-windows-gnu + - x86_64-pc-windows-msvc + - aarch64-pc-windows-msvc + - x86_64-apple-darwin + # cross + - x86_64-unknown-linux-musl + - aarch64-unknown-linux-gnu + - aarch64-unknown-linux-musl + - armv7-unknown-linux-gnueabihf + - armv7-unknown-linux-musleabihf + + include: + # runner overrides + - target: x86_64-pc-windows-gnu + os: windows-latest + - target: x86_64-pc-windows-msvc + os: windows-latest + - target: aarch64-pc-windows-msvc + os: windows-latest + - target: x86_64-apple-darwin + os: macos-latest + # targets that use cross + - target: x86_64-unknown-linux-musl + use-cross: true + - target: aarch64-unknown-linux-gnu + use-cross: true + - target: aarch64-unknown-linux-musl + use-cross: true + - target: armv7-unknown-linux-gnueabihf + use-cross: true + - target: armv7-unknown-linux-musleabihf + use-cross: true + # features (unless `matrix_all_combinations` is true, we only run these on linux-gnu) + - feature-unrar: true + target: x86_64-unknown-linux-gnu + - feature-use-zlib: true + target: x86_64-unknown-linux-gnu + - feature-use-zstd-thin: true + target: x86_64-unknown-linux-gnu + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install cross + if: matrix.use-cross + run: | + pushd "$(mktemp -d)" + wget https://github.com/cross-rs/cross/releases/download/v0.2.4/cross-x86_64-unknown-linux-musl.tar.gz + tar xf cross-x86_64-unknown-linux-musl.tar.gz + cp cross ~/.cargo/bin + popd + echo CARGO=cross >> $GITHUB_ENV + + - name: Concatenate features + id: concat-features + shell: bash + run: | + FEATURES=() + if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi + if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi + if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi + IFS=',' + echo "FEATURES=${FEATURES[*]}" >> $GITHUB_OUTPUT + + - name: Set up extra cargo flags + env: + FEATURES: ${{steps.concat-features.outputs.FEATURES}} + shell: bash + run: | + FLAGS="--no-default-features" + if [[ -n "$FEATURES" ]]; then FLAGS+=" --features $FEATURES"; fi + echo "EXTRA_CARGO_FLAGS=$FLAGS" >> $GITHUB_ENV + + - name: Install Rust + run: | + rustup toolchain install stable nightly --profile minimal -t ${{ matrix.target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: "${{ matrix.target }}-${{ matrix.feature-unrar }}-${{ matrix.feature-use-zstd-thin }}-${{ matrix.feature-unrar }}" + + - name: Test on stable + # there's no way to run tests for ARM64 Windows for now + if: matrix.target != 'aarch64-pc-windows-msvc' + run: | + ${{ env.CARGO }} +stable test --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS + + - name: Release on nightly + run: | + ${{ env.CARGO }} +nightly build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS + env: + OUCH_ARTIFACTS_FOLDER: artifacts + RUSTFLAGS: -C strip=symbols + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: ${{ inputs.upload_artifacts }} + with: + name: ouch-${{ matrix.target }}-${{ steps.concat-features.outputs.FEATURES }} + path: | + target/${{ matrix.target }}/release/ouch + target/${{ matrix.target }}/release/ouch.exe + artifacts/ diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml new file mode 100644 index 0000000..531ccc7 --- /dev/null +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -0,0 +1,35 @@ +name: Automatic trigger draft release + +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +jobs: + call-workflow-build-artifacts-and-run-tests: + uses: ouch-org/ouch/.github/workflows/build-artifacts-and-run-tests.yml@main + with: + matrix_all_combinations: true + upload_artifacts: true + + automated-draft-release: + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + needs: build-artifacts-and-run-tests + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download artifacts + uses: dawidd6/action-download-artifact@v3 + with: + path: artifacts + + - name: Package release assets + run: scripts/package-release-assets.sh + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + draft: true + files: release/ouch-* diff --git a/.github/workflows/manual-trigger-draft-release.yml b/.github/workflows/draft-release-manual-trigger.yml similarity index 78% rename from .github/workflows/manual-trigger-draft-release.yml rename to .github/workflows/draft-release-manual-trigger.yml index 530018f..6e296fe 100644 --- a/.github/workflows/manual-trigger-draft-release.yml +++ b/.github/workflows/draft-release-manual-trigger.yml @@ -1,8 +1,4 @@ -# we have two workflows for releases, this is the manual one to be triggered -# in the Actions tab in the repository -# -# the automatic one runs in another workflow and checks for tag pushes -name: manual-trigger-draft-release +name: Manual trigger draft release on: workflow_dispatch: diff --git a/.github/workflows/pr-workflow.yml b/.github/workflows/pr-workflow.yml index 54c8fe3..f1d606e 100644 --- a/.github/workflows/pr-workflow.yml +++ b/.github/workflows/pr-workflow.yml @@ -1,27 +1,11 @@ name: PR workflow on: - push: - branches: - - main - tags: - - "[0-9]+.[0-9]+.[0-9]+" pull_request: paths-ignore: - "*.md" jobs: - clippy-checks: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: "Cargo: clippy" - run: | - rustup toolchain install stable --profile minimal -c clippy - cargo +stable clippy -- -D warnings - rustfmt-check: runs-on: ubuntu-latest steps: @@ -33,141 +17,19 @@ jobs: rustup toolchain install nightly --profile minimal -c rustfmt cargo +nightly fmt -- --check - automated-draft-release: + clippy-checks: runs-on: ubuntu-latest - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - needs: build-artifacts-and-run-tests steps: - name: Checkout uses: actions/checkout@v4 - - name: Download artifacts - uses: dawidd6/action-download-artifact@v3 - with: - path: artifacts - - - name: Package release assets - run: scripts/package-release-assets.sh - - - name: Create release - uses: softprops/action-gh-release@v2 - with: - draft: true - files: release/ouch-* - - build-artifacts-and-run-tests: - runs-on: ${{ matrix.os || 'ubuntu-latest' }} - env: - CARGO: cargo - strategy: - fail-fast: false - matrix: - feature-use-zlib: [false] - feature-use-zstd-thin: [false] - feature-unrar: [false] - target: - # native - - x86_64-unknown-linux-gnu - - x86_64-pc-windows-gnu - - x86_64-pc-windows-msvc - - aarch64-pc-windows-msvc - - x86_64-apple-darwin - # cross - - x86_64-unknown-linux-musl - - aarch64-unknown-linux-gnu - - aarch64-unknown-linux-musl - - armv7-unknown-linux-gnueabihf - - armv7-unknown-linux-musleabihf - - include: - # runner overrides - - target: x86_64-pc-windows-gnu - os: windows-latest - - target: x86_64-pc-windows-msvc - os: windows-latest - - target: aarch64-pc-windows-msvc - os: windows-latest - - target: x86_64-apple-darwin - os: macos-latest - # targets that use cross - - target: x86_64-unknown-linux-musl - use-cross: true - - target: aarch64-unknown-linux-gnu - use-cross: true - - target: aarch64-unknown-linux-musl - use-cross: true - - target: armv7-unknown-linux-gnueabihf - use-cross: true - - target: armv7-unknown-linux-musleabihf - use-cross: true - # features - - feature-use-zlib: true - target: x86_64-unknown-linux-gnu - - feature-use-zstd-thin: true - target: x86_64-unknown-linux-gnu - - feature-unrar: true - target: x86_64-unknown-linux-gnu - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install cross - if: matrix.use-cross + - name: "Cargo: clippy" run: | - pushd "$(mktemp -d)" - wget https://github.com/cross-rs/cross/releases/download/v0.2.4/cross-x86_64-unknown-linux-musl.tar.gz - tar xf cross-x86_64-unknown-linux-musl.tar.gz - cp cross ~/.cargo/bin - popd - echo CARGO=cross >> $GITHUB_ENV + rustup toolchain install stable --profile minimal -c clippy + cargo +stable clippy -- -D warnings - - name: Concatenate features - id: concat-features - shell: bash - run: | - FEATURES=() - if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi - if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi - if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi - IFS=',' - echo "FEATURES=${FEATURES[*]}" >> $GITHUB_OUTPUT - - - name: Set up extra cargo flags - env: - FEATURES: ${{steps.concat-features.outputs.FEATURES}} - shell: bash - run: | - FLAGS="--no-default-features" - if [[ -n "$FEATURES" ]]; then FLAGS+=" --features $FEATURES"; fi - echo "EXTRA_CARGO_FLAGS=$FLAGS" >> $GITHUB_ENV - - - name: Install Rust - run: | - rustup toolchain install stable nightly --profile minimal -t ${{ matrix.target }} - - - uses: Swatinem/rust-cache@v2 - with: - key: "${{ matrix.target }}-${{ matrix.feature-unrar }}-${{ matrix.feature-use-zstd-thin }}-${{ matrix.feature-unrar }}" - - - name: Test on stable - # there's no way to run tests for ARM64 Windows for now - if: matrix.target != 'aarch64-pc-windows-msvc' - run: | - ${{ env.CARGO }} +stable test --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS - - - name: Release on nightly - run: | - ${{ env.CARGO }} +nightly build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS - env: - OUCH_ARTIFACTS_FOLDER: artifacts - RUSTFLAGS: -C strip=symbols - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: ouch-${{ matrix.target }}-${{ steps.concat-features.outputs.FEATURES }} - path: | - target/${{ matrix.target }}/release/ouch - target/${{ matrix.target }}/release/ouch.exe - artifacts/ + build-and-test: + uses: ouch-org/ouch/.github/workflows/build-artifacts-and-run-tests.yml@main + with: + matrix_all_combinations: false + upload_artifacts: false From 17499d7b5d9a3d5571b541035918b62c220974c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 20:36:08 -0300 Subject: [PATCH 031/101] CI: tweak: reference reusable workflow locally this allows non-main branches to run their own latest workflows, and helps with forks too --- .github/workflows/all-tests-slow.yml | 2 +- .github/workflows/draft-release-automatic-trigger.yml | 2 +- .github/workflows/pr-workflow.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/all-tests-slow.yml b/.github/workflows/all-tests-slow.yml index 7a5a644..da08e15 100644 --- a/.github/workflows/all-tests-slow.yml +++ b/.github/workflows/all-tests-slow.yml @@ -11,7 +11,7 @@ on: jobs: run-tests-for-all-combinations: - uses: ouch-org/ouch/.github/workflows/build-artifacts-and-run-tests.yml@main + uses: ./.github/workflows/build-artifacts-and-run-tests.yml with: matrix_all_combinations: true upload_artifacts: false diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml index 531ccc7..f007f80 100644 --- a/.github/workflows/draft-release-automatic-trigger.yml +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -7,7 +7,7 @@ on: jobs: call-workflow-build-artifacts-and-run-tests: - uses: ouch-org/ouch/.github/workflows/build-artifacts-and-run-tests.yml@main + uses: ./.github/workflows/build-artifacts-and-run-tests.yml with: matrix_all_combinations: true upload_artifacts: true diff --git a/.github/workflows/pr-workflow.yml b/.github/workflows/pr-workflow.yml index f1d606e..ec89459 100644 --- a/.github/workflows/pr-workflow.yml +++ b/.github/workflows/pr-workflow.yml @@ -29,7 +29,7 @@ jobs: cargo +stable clippy -- -D warnings build-and-test: - uses: ouch-org/ouch/.github/workflows/build-artifacts-and-run-tests.yml@main + uses: ./.github/workflows/build-artifacts-and-run-tests.yml with: matrix_all_combinations: false upload_artifacts: false From 917355685c1a087af39c1852ab2d4687d62b9dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 21:14:30 -0300 Subject: [PATCH 032/101] CI: fix: skip CI if just edited .md files --- .github/workflows/all-tests-slow.yml | 2 +- .github/workflows/pr-workflow.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/all-tests-slow.yml b/.github/workflows/all-tests-slow.yml index da08e15..7d090ac 100644 --- a/.github/workflows/all-tests-slow.yml +++ b/.github/workflows/all-tests-slow.yml @@ -7,7 +7,7 @@ on: branches: - main paths-ignore: - - "*.md" + - "**/*.md" jobs: run-tests-for-all-combinations: diff --git a/.github/workflows/pr-workflow.yml b/.github/workflows/pr-workflow.yml index ec89459..bf20e08 100644 --- a/.github/workflows/pr-workflow.yml +++ b/.github/workflows/pr-workflow.yml @@ -3,7 +3,7 @@ name: PR workflow on: pull_request: paths-ignore: - - "*.md" + - "**/*.md" jobs: rustfmt-check: From 92059c3de4154285e66fbf196ad354708e0640f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 21:05:02 -0300 Subject: [PATCH 033/101] CI: tweak: compile artifacts in stable Rust --- .github/workflows/build-artifacts-and-run-tests.yml | 9 ++++----- .github/workflows/pr-workflow.yml | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml index 788c7a1..83d5984 100644 --- a/.github/workflows/build-artifacts-and-run-tests.yml +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -116,7 +116,7 @@ jobs: - name: Install Rust run: | - rustup toolchain install stable nightly --profile minimal -t ${{ matrix.target }} + rustup toolchain install stable --profile minimal -t ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 with: @@ -126,14 +126,13 @@ jobs: # there's no way to run tests for ARM64 Windows for now if: matrix.target != 'aarch64-pc-windows-msvc' run: | - ${{ env.CARGO }} +stable test --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS + ${{ env.CARGO }} +stable test --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS - - name: Release on nightly + - name: Build artifacts (binary and completions) run: | - ${{ env.CARGO }} +nightly build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS + ${{ env.CARGO }} +stable build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS env: OUCH_ARTIFACTS_FOLDER: artifacts - RUSTFLAGS: -C strip=symbols - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/pr-workflow.yml b/.github/workflows/pr-workflow.yml index bf20e08..e804c0c 100644 --- a/.github/workflows/pr-workflow.yml +++ b/.github/workflows/pr-workflow.yml @@ -6,7 +6,7 @@ on: - "**/*.md" jobs: - rustfmt-check: + rustfmt-nightly-check: runs-on: ubuntu-latest steps: - name: Checkout From a99aee6a42ff0b9eadb7f901bc3414411ba6551b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 21:12:12 -0300 Subject: [PATCH 034/101] chore: update pull request template --- .github/pull_request_template.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c19b79f..8bdf383 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,6 @@ From 639ef19fbc5800256c3bfd46db4db2cfcd2dfed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 21:05:58 -0300 Subject: [PATCH 035/101] chore: simplify code after feature stabilization no need to reimplement `Path::is_symlink` anymore --- src/archive/sevenz.rs | 7 +++---- src/archive/tar.rs | 5 ++--- src/archive/zip.rs | 7 +++---- src/utils/fs.rs | 10 ---------- src/utils/mod.rs | 2 +- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/archive/sevenz.rs b/src/archive/sevenz.rs index 04da19c..af5ca83 100644 --- a/src/archive/sevenz.rs +++ b/src/archive/sevenz.rs @@ -15,7 +15,7 @@ use crate::{ error::{Error, FinalError}, list::FileInArchive, utils::{ - self, cd_into_same_dir_as, + cd_into_same_dir_as, logger::{info, warning}, Bytes, EscapedPathDisplay, FileVisibilityPolicy, }, @@ -68,9 +68,8 @@ where let metadata = match path.metadata() { Ok(metadata) => metadata, Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound && utils::is_symlink(path) { - // This path is for a broken symlink - // We just ignore it + if e.kind() == std::io::ErrorKind::NotFound && path.is_symlink() { + // This path is for a broken symlink, ignore it continue; } return Err(e.into()); diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 57c3737..6da1fc0 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -131,9 +131,8 @@ where let mut file = match fs::File::open(path) { Ok(f) => f, Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound && utils::is_symlink(path) { - // This path is for a broken symlink - // We just ignore it + if e.kind() == std::io::ErrorKind::NotFound && path.is_symlink() { + // This path is for a broken symlink, ignore it continue; } return Err(e.into()); diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 39b640e..13018bd 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -20,7 +20,7 @@ use crate::{ error::FinalError, list::FileInArchive, utils::{ - self, cd_into_same_dir_as, get_invalid_utf8_paths, + cd_into_same_dir_as, get_invalid_utf8_paths, logger::{info, info_accessible, warning}, pretty_format_list_of_paths, strip_cur_dir, Bytes, EscapedPathDisplay, FileVisibilityPolicy, }, @@ -214,9 +214,8 @@ where let metadata = match path.metadata() { Ok(metadata) => metadata, Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound && utils::is_symlink(path) { - // This path is for a broken symlink - // We just ignore it + if e.kind() == std::io::ErrorKind::NotFound && path.is_symlink() { + // This path is for a broken symlink, ignore it continue; } return Err(e.into()); diff --git a/src/utils/fs.rs b/src/utils/fs.rs index a0928f2..c5f8c1d 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -146,13 +146,3 @@ pub fn try_infer_extension(path: &Path) -> Option { None } } - -/// 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 { - fs::symlink_metadata(path) - .map(|m| m.file_type().is_symlink()) - .unwrap_or(false) -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e2b5177..c153689 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -18,7 +18,7 @@ pub use self::{ EscapedPathDisplay, }, fs::{ - cd_into_same_dir_as, clear_path, create_dir_if_non_existent, is_path_stdin, is_symlink, remove_file_or_dir, + cd_into_same_dir_as, clear_path, create_dir_if_non_existent, is_path_stdin, remove_file_or_dir, try_infer_extension, }, question::{ask_to_create_file, user_wants_to_continue, user_wants_to_overwrite, QuestionAction, QuestionPolicy}, From 5b78b96fa11ab6d66e1dbf877189f62502a7174f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 23:39:31 -0300 Subject: [PATCH 036/101] fix: warnings not shown before stdin interaction --- src/utils/io.rs | 4 ++++ src/utils/logger.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/utils/io.rs b/src/utils/io.rs index 7e05d84..27a1aef 100644 --- a/src/utils/io.rs +++ b/src/utils/io.rs @@ -1,8 +1,12 @@ use std::io::{self, stderr, stdout, StderrLock, StdoutLock, Write}; +use crate::utils::logger; + type StdioOutputLocks = (StdoutLock<'static>, StderrLock<'static>); pub fn lock_and_flush_output_stdio() -> io::Result { + logger::flush_messages(); + let mut stdout = stdout().lock(); stdout.flush()?; let mut stderr = stderr().lock(); diff --git a/src/utils/logger.rs b/src/utils/logger.rs index c03dd9a..aeedecd 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,10 +1,16 @@ -use std::sync::{mpsc, OnceLock}; +use std::sync::{mpsc, Arc, Barrier, OnceLock}; pub use logger_thread::spawn_logger_thread; use super::colors::{ORANGE, RESET, YELLOW}; use crate::accessible::is_running_in_accessible_mode; +/// Asks logger to flush all messages, useful before starting STDIN interaction. +#[track_caller] +pub fn flush_messages() { + logger_thread::send_flush_message_and_wait(); +} + /// An `[INFO]` log to be displayed if we're not running accessibility mode. /// /// Same as `.info_accessible()`, but only displayed if accessibility mode @@ -49,6 +55,7 @@ pub fn warning(contents: String) { #[derive(Debug)] enum Message { + Flush { finished_barrier: Arc }, FlushAndShutdown, PrintMessage(PrintMessage), } @@ -134,6 +141,19 @@ mod logger_thread { .expect("Failed to send shutdown message"); } + #[track_caller] + pub(super) fn send_flush_message_and_wait() { + let barrier = Arc::new(Barrier::new(2)); + + get_sender() + .send(Message::Flush { + finished_barrier: barrier.clone(), + }) + .expect("Failed to send shutdown message"); + + barrier.wait(); + } + pub struct LoggerThreadHandle { shutdown_barrier: Arc, } @@ -173,7 +193,7 @@ mod logger_thread { } fn run_logger(log_receiver: LogReceiver, shutdown_barrier: Arc) { - const FLUSH_TIMEOUT: Duration = Duration::from_millis(250); + const FLUSH_TIMEOUT: Duration = Duration::from_millis(200); let mut buffer = Vec::::with_capacity(16); @@ -202,6 +222,11 @@ mod logger_thread { flush_logs_to_stderr(&mut buffer); break; } + Message::Flush { finished_barrier } => { + flush_logs_to_stderr(&mut buffer); + finished_barrier.wait(); + break; + } } } From 4bb759b21ccd1f6b7f5ac4c2831e5406961d1992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 23:44:33 -0300 Subject: [PATCH 037/101] tweak: improve error message in case the user is trying to decompress a file with no filestem, just the extension, which is confusing --- src/check.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/check.rs b/src/check.rs index 7face91..bcad3cf 100644 --- a/src/check.rs +++ b/src/check.rs @@ -35,10 +35,9 @@ pub fn check_mime_type( if let Some(detected_format) = try_infer_extension(path) { // Inferring the file extension can have unpredicted consequences (e.g. the user just // mistyped, ...) which we should always inform the user about. - info_accessible(format!( - "Detected file: `{}` extension as `{}`", + warning(format!( + "We detected a file named `{}`, do you want to decompress it?", path.display(), - detected_format )); if user_wants_to_continue(path, question_policy, QuestionAction::Decompression)? { From 065124cd308881480568c8ba55ce6c1f383a0ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 23:44:33 -0300 Subject: [PATCH 038/101] tweak: improve error message in case the user is trying to decompress a file with no filestem, just the extension, which is confusing --- src/utils/logger.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/utils/logger.rs b/src/utils/logger.rs index aeedecd..2c4a99c 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -54,10 +54,10 @@ pub fn warning(contents: String) { } #[derive(Debug)] -enum Message { +enum LoggerCommand { + Print(PrintMessage), Flush { finished_barrier: Arc }, FlushAndShutdown, - PrintMessage(PrintMessage), } /// Message object used for sending logs from worker threads to a logging thread via channels. @@ -110,8 +110,8 @@ mod logger_thread { use super::*; - type LogReceiver = mpsc::Receiver; - type LogSender = mpsc::Sender; + type LogReceiver = mpsc::Receiver; + type LogSender = mpsc::Sender; static SENDER: OnceLock = OnceLock::new(); @@ -130,14 +130,14 @@ mod logger_thread { #[track_caller] pub(super) fn send_log_message(msg: PrintMessage) { get_sender() - .send(Message::PrintMessage(msg)) + .send(LoggerCommand::Print(msg)) .expect("Failed to send print message"); } #[track_caller] fn send_shutdown_message() { get_sender() - .send(Message::FlushAndShutdown) + .send(LoggerCommand::FlushAndShutdown) .expect("Failed to send shutdown message"); } @@ -146,7 +146,7 @@ mod logger_thread { let barrier = Arc::new(Barrier::new(2)); get_sender() - .send(Message::Flush { + .send(LoggerCommand::Flush { finished_barrier: barrier.clone(), }) .expect("Failed to send shutdown message"); @@ -208,7 +208,7 @@ mod logger_thread { }; match msg { - Message::PrintMessage(msg) => { + LoggerCommand::Print(msg) => { // Append message to buffer if let Some(msg) = msg.to_processed_message() { buffer.push(msg); @@ -218,11 +218,11 @@ mod logger_thread { flush_logs_to_stderr(&mut buffer); } } - Message::FlushAndShutdown => { + LoggerCommand::FlushAndShutdown => { flush_logs_to_stderr(&mut buffer); break; } - Message::Flush { finished_barrier } => { + LoggerCommand::Flush { finished_barrier } => { flush_logs_to_stderr(&mut buffer); finished_barrier.wait(); break; From b21b757af1aeb1fbecb37801bb88e2f978cf6f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 23:11:22 -0300 Subject: [PATCH 039/101] chore: improve log message --- src/archive/sevenz.rs | 2 +- src/archive/tar.rs | 2 +- src/archive/zip.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/archive/sevenz.rs b/src/archive/sevenz.rs index af5ca83..587a4a1 100644 --- a/src/archive/sevenz.rs +++ b/src/archive/sevenz.rs @@ -49,7 +49,7 @@ where if let Ok(handle) = &output_handle { if matches!(Handle::from_path(path), Ok(x) if &x == handle) { warning(format!( - "The output file and the input file are the same: `{}`, skipping...", + "Cannot compress `{}` into itself, skipping.", output_path.display() )); diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 6da1fc0..c313f8f 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -109,7 +109,7 @@ where if let Ok(handle) = &output_handle { if matches!(Handle::from_path(path), Ok(x) if &x == handle) { warning(format!( - "The output file and the input file are the same: `{}`, skipping...", + "Cannot compress `{}` into itself, skipping.", output_path.display() )); diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 13018bd..eb420d4 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -197,7 +197,7 @@ where if let Ok(handle) = &output_handle { if matches!(Handle::from_path(path), Ok(x) if &x == handle) { warning(format!( - "The output file and the input file are the same: `{}`, skipping...", + "Cannot compress `{}` into itself, skipping.", output_path.display() )); } From 60d5897de1229ad0260c2224142dd9dd503788ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Sun, 17 Nov 2024 23:51:12 -0300 Subject: [PATCH 040/101] fix: don't shutdown logger after flush --- src/utils/logger.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 2c4a99c..4f0bf53 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -218,13 +218,12 @@ mod logger_thread { flush_logs_to_stderr(&mut buffer); } } - LoggerCommand::FlushAndShutdown => { - flush_logs_to_stderr(&mut buffer); - break; - } LoggerCommand::Flush { finished_barrier } => { flush_logs_to_stderr(&mut buffer); finished_barrier.wait(); + } + LoggerCommand::FlushAndShutdown => { + flush_logs_to_stderr(&mut buffer); break; } } From 223f82d53810b3b2b65424cd2603e2adbb3979d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:01:58 -0300 Subject: [PATCH 041/101] refac: simplify `smart_unpack` --- src/commands/decompress.rs | 40 ++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 7c5c842..a16d892 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -260,7 +260,8 @@ fn smart_unpack( let files = unpack_fn(temp_dir_path)?; let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.count() == 1; - if root_contains_only_one_element { + + let (previous_path, new_path) = if root_contains_only_one_element { // Only one file in the root directory, so we can just move it to the output directory let file = fs::read_dir(temp_dir_path)?.next().expect("item exists")?; let file_path = file.path(); @@ -268,31 +269,24 @@ fn smart_unpack( .file_name() .expect("Should be safe because paths in archives should not end with '..'"); let correct_path = output_dir.join(file_name); - // Before moving, need to check if a file with the same name already exists - if !utils::clear_path(&correct_path, question_policy)? { - return Ok(ControlFlow::Break(())); - } - fs::rename(&file_path, &correct_path)?; - info_accessible(format!( - "Successfully moved {} to {}.", - nice_directory_display(&file_path), - nice_directory_display(&correct_path) - )); + (file_path, correct_path) } else { - // Multiple files in the root directory, so: - // Rename the temporary directory to the archive name, which is output_file_path - // One case to handle tough is we need to check if a file with the same name already exists - if !utils::clear_path(output_file_path, question_policy)? { - return Ok(ControlFlow::Break(())); - } - fs::rename(temp_dir_path, output_file_path)?; - info_accessible(format!( - "Successfully moved {} to {}.", - nice_directory_display(temp_dir_path), - nice_directory_display(output_file_path) - )); + (temp_dir_path.to_owned(), output_file_path.to_owned()) + }; + + // Before moving, need to check if a file with the same name already exists + if !utils::clear_path(&new_path, question_policy)? { + return Ok(ControlFlow::Break(())); } + // Rename the temporary directory to the archive name, which is output_file_path + fs::rename(&previous_path, &new_path)?; + info_accessible(format!( + "Successfully moved {} to {}.", + nice_directory_display(&previous_path), + nice_directory_display(&new_path), + )); + Ok(ControlFlow::Continue(files)) } From e108e5b77807e03be10071518f412397ea22d38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:03:36 -0300 Subject: [PATCH 042/101] chore: improve message after moving file --- src/commands/decompress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index a16d892..7e3f617 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -283,7 +283,7 @@ fn smart_unpack( // Rename the temporary directory to the archive name, which is output_file_path fs::rename(&previous_path, &new_path)?; info_accessible(format!( - "Successfully moved {} to {}.", + "Successfully moved \"{}\" to \"{}\".", nice_directory_display(&previous_path), nice_directory_display(&new_path), )); From 2eca23313260b1c72e183cecbb22fb4df7b51d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:07:32 -0300 Subject: [PATCH 043/101] chore: update comments --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index b8c274d..6b2e92d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,7 +32,7 @@ pub enum Error { PermissionDenied { error_title: String }, /// From zip::result::ZipError::UnsupportedArchive UnsupportedZipArchive(&'static str), - /// TO BE REMOVED + /// We don't support compressing the root folder. CompressingRootFolder, /// Specialized walkdir's io::Error wrapper with additional information on the error WalkdirError { reason: String }, From df6d2cea983c550c667eeaa0490bafe239ea243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:16:54 -0300 Subject: [PATCH 044/101] tweak: don't add period to end of each log --- src/archive/sevenz.rs | 4 ++-- src/archive/tar.rs | 4 ++-- src/archive/zip.rs | 4 ++-- src/commands/decompress.rs | 8 ++++---- src/commands/mod.rs | 2 +- src/extension.rs | 2 +- src/utils/fs.rs | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/archive/sevenz.rs b/src/archive/sevenz.rs index 587a4a1..6ee345d 100644 --- a/src/archive/sevenz.rs +++ b/src/archive/sevenz.rs @@ -49,7 +49,7 @@ where if let Ok(handle) = &output_handle { if matches!(Handle::from_path(path), Ok(x) if &x == handle) { warning(format!( - "Cannot compress `{}` into itself, skipping.", + "Cannot compress `{}` into itself, skipping", output_path.display() )); @@ -62,7 +62,7 @@ where // spoken text for users using screen readers, braille displays // and so on if !quiet { - info(format!("Compressing '{}'.", EscapedPathDisplay::new(path))); + info(format!("Compressing '{}'", EscapedPathDisplay::new(path))); } let metadata = match path.metadata() { diff --git a/src/archive/tar.rs b/src/archive/tar.rs index c313f8f..d20aa7d 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -109,7 +109,7 @@ where if let Ok(handle) = &output_handle { if matches!(Handle::from_path(path), Ok(x) if &x == handle) { warning(format!( - "Cannot compress `{}` into itself, skipping.", + "Cannot compress `{}` into itself, skipping", output_path.display() )); @@ -122,7 +122,7 @@ where // spoken text for users using screen readers, braille displays // and so on if !quiet { - info(format!("Compressing '{}'.", EscapedPathDisplay::new(path))); + info(format!("Compressing '{}'", EscapedPathDisplay::new(path))); } if path.is_dir() { diff --git a/src/archive/zip.rs b/src/archive/zip.rs index eb420d4..af20950 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -197,7 +197,7 @@ where if let Ok(handle) = &output_handle { if matches!(Handle::from_path(path), Ok(x) if &x == handle) { warning(format!( - "Cannot compress `{}` into itself, skipping.", + "Cannot compress `{}` into itself, skipping", output_path.display() )); } @@ -208,7 +208,7 @@ where // spoken text for users using screen readers, braille displays // and so on if !quiet { - info(format!("Compressing '{}'.", EscapedPathDisplay::new(path))); + info(format!("Compressing '{}'", EscapedPathDisplay::new(path))); } let metadata = match path.metadata() { diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 7e3f617..6c35c25 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -78,7 +78,7 @@ pub fn decompress_file( // as screen readers may not read a commands exit code, making it hard to reason // about whether the command succeeded without such a message info_accessible(format!( - "Successfully decompressed archive in {} ({} files).", + "Successfully decompressed archive in {} ({} files)", nice_directory_display(output_dir), files_unpacked )); @@ -228,7 +228,7 @@ pub fn decompress_file( // as screen readers may not read a commands exit code, making it hard to reason // about whether the command succeeded without such a message info_accessible(format!( - "Successfully decompressed archive in {}.", + "Successfully decompressed archive in {}", nice_directory_display(output_dir) )); info_accessible(format!("Files unpacked: {}", files_unpacked)); @@ -253,7 +253,7 @@ fn smart_unpack( let temp_dir_path = temp_dir.path(); info_accessible(format!( - "Created temporary directory {} to hold decompressed elements.", + "Created temporary directory {} to hold decompressed elements", nice_directory_display(temp_dir_path) )); @@ -283,7 +283,7 @@ fn smart_unpack( // Rename the temporary directory to the archive name, which is output_file_path fs::rename(&previous_path, &new_path)?; info_accessible(format!( - "Successfully moved \"{}\" to \"{}\".", + "Successfully moved \"{}\" to \"{}\"", nice_directory_display(&previous_path), nice_directory_display(&new_path), )); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c4cd8e0..f296359 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -111,7 +111,7 @@ pub fn run( // having a final status message is important especially in an accessibility context // as screen readers may not read a commands exit code, making it hard to reason // about whether the command succeeded without such a message - info_accessible(format!("Successfully compressed '{}'.", path_to_str(&output_path))); + info_accessible(format!("Successfully compressed '{}'", path_to_str(&output_path))); } else { // If Ok(false) or Err() occurred, delete incomplete file at `output_path` // diff --git a/src/extension.rs b/src/extension.rs index 0b6af9a..f8d7389 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -200,7 +200,7 @@ pub fn separate_known_extensions_from_name(path: &Path) -> (&Path, Vec crate::Result<()> { fs::create_dir_all(path)?; // creating a directory is an important change to the file system we // should always inform the user about - info_accessible(format!("Directory {} created.", EscapedPathDisplay::new(path))); + info_accessible(format!("Directory {} created", EscapedPathDisplay::new(path))); } Ok(()) } From cc530bea9416b5c099172d3cd481da2d190f4e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:25:08 -0300 Subject: [PATCH 045/101] refac: simplify logger shutdown system --- src/main.rs | 14 +++++---- src/utils/logger.rs | 70 ++++++++++++++++----------------------------- 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/src/main.rs b/src/main.rs index 10ead08..3f1f7dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,11 +11,15 @@ pub mod utils; use std::{env, path::PathBuf}; use cli::CliArgs; -use error::{Error, Result}; use once_cell::sync::Lazy; -use utils::{QuestionAction, QuestionPolicy}; -use crate::utils::logger::spawn_logger_thread; +use self::{ + error::{Error, Result}, + utils::{ + logger::{shutdown_logger_and_wait, spawn_logger_thread}, + QuestionAction, QuestionPolicy, + }, +}; // Used in BufReader and BufWriter to perform less syscalls const BUFFER_CAPACITY: usize = 1024 * 32; @@ -27,9 +31,9 @@ static CURRENT_DIRECTORY: Lazy = Lazy::new(|| env::current_dir().unwrap pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE; fn main() { - let handler = spawn_logger_thread(); + spawn_logger_thread(); let result = run(); - handler.shutdown_and_wait(); + shutdown_logger_and_wait(); if let Err(err) = result { eprintln!("{err}"); diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 4f0bf53..ceaee32 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -5,6 +5,12 @@ pub use logger_thread::spawn_logger_thread; use super::colors::{ORANGE, RESET, YELLOW}; use crate::accessible::is_running_in_accessible_mode; +/// Asks logger to shutdown and waits till it flushes all pending messages. +#[track_caller] +pub fn shutdown_logger_and_wait() { + logger_thread::send_shutdown_command_and_wait(); +} + /// Asks logger to flush all messages, useful before starting STDIN interaction. #[track_caller] pub fn flush_messages() { @@ -57,7 +63,7 @@ pub fn warning(contents: String) { enum LoggerCommand { Print(PrintMessage), Flush { finished_barrier: Arc }, - FlushAndShutdown, + FlushAndShutdown { finished_barrier: Arc }, } /// Message object used for sending logs from worker threads to a logging thread via channels. @@ -134,13 +140,6 @@ mod logger_thread { .expect("Failed to send print message"); } - #[track_caller] - fn send_shutdown_message() { - get_sender() - .send(LoggerCommand::FlushAndShutdown) - .expect("Failed to send shutdown message"); - } - #[track_caller] pub(super) fn send_flush_message_and_wait() { let barrier = Arc::new(Barrier::new(2)); @@ -154,45 +153,25 @@ mod logger_thread { barrier.wait(); } - pub struct LoggerThreadHandle { - shutdown_barrier: Arc, + #[track_caller] + pub(super) fn send_shutdown_command_and_wait() { + let barrier = Arc::new(Barrier::new(2)); + + get_sender() + .send(LoggerCommand::FlushAndShutdown { + finished_barrier: barrier.clone(), + }) + .expect("Failed to send shutdown message"); + + barrier.wait(); } - impl LoggerThreadHandle { - /// Tell logger to shutdown and waits till it does. - pub fn shutdown_and_wait(self) { - // Signal the shutdown - send_shutdown_message(); - // Wait for confirmation - self.shutdown_barrier.wait(); - } - } - - #[cfg(test)] - // shutdown_and_wait must be called manually, but to keep 'em clean, in - // case of tests just do it on drop - impl Drop for LoggerThreadHandle { - fn drop(&mut self) { - send_shutdown_message(); - self.shutdown_barrier.wait(); - } - } - - pub fn spawn_logger_thread() -> LoggerThreadHandle { + pub fn spawn_logger_thread() { let log_receiver = setup_channel(); - - let shutdown_barrier = Arc::new(Barrier::new(2)); - - let handle = LoggerThreadHandle { - shutdown_barrier: shutdown_barrier.clone(), - }; - - rayon::spawn(move || run_logger(log_receiver, shutdown_barrier)); - - handle + rayon::spawn(move || run_logger(log_receiver)); } - fn run_logger(log_receiver: LogReceiver, shutdown_barrier: Arc) { + fn run_logger(log_receiver: LogReceiver) { const FLUSH_TIMEOUT: Duration = Duration::from_millis(200); let mut buffer = Vec::::with_capacity(16); @@ -222,14 +201,13 @@ mod logger_thread { flush_logs_to_stderr(&mut buffer); finished_barrier.wait(); } - LoggerCommand::FlushAndShutdown => { + LoggerCommand::FlushAndShutdown { finished_barrier } => { flush_logs_to_stderr(&mut buffer); - break; + finished_barrier.wait(); + return; } } } - - shutdown_barrier.wait(); } fn flush_logs_to_stderr(buffer: &mut Vec) { From 3e890eb30768d0ca25ce29681364af47d38759ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:27:22 -0300 Subject: [PATCH 046/101] chore: rename functions and fix wrong logs --- src/utils/logger.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/utils/logger.rs b/src/utils/logger.rs index ceaee32..5156308 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -14,7 +14,7 @@ pub fn shutdown_logger_and_wait() { /// Asks logger to flush all messages, useful before starting STDIN interaction. #[track_caller] pub fn flush_messages() { - logger_thread::send_flush_message_and_wait(); + logger_thread::send_flush_command_and_wait(); } /// An `[INFO]` log to be displayed if we're not running accessibility mode. @@ -42,7 +42,7 @@ pub fn info_accessible(contents: String) { #[track_caller] fn info_with_accessibility(contents: String, accessible: bool) { - logger_thread::send_log_message(PrintMessage { + logger_thread::send_print_command(PrintMessage { contents, accessible, level: MessageLevel::Info, @@ -51,7 +51,7 @@ fn info_with_accessibility(contents: String, accessible: bool) { #[track_caller] pub fn warning(contents: String) { - logger_thread::send_log_message(PrintMessage { + logger_thread::send_print_command(PrintMessage { contents, // Warnings are important and unlikely to flood, so they should be displayed accessible: true, @@ -76,7 +76,7 @@ struct PrintMessage { } impl PrintMessage { - fn to_processed_message(&self) -> Option { + fn to_formatted_message(&self) -> Option { match self.level { MessageLevel::Info => { if self.accessible { @@ -134,21 +134,21 @@ mod logger_thread { } #[track_caller] - pub(super) fn send_log_message(msg: PrintMessage) { + pub(super) fn send_print_command(msg: PrintMessage) { get_sender() .send(LoggerCommand::Print(msg)) - .expect("Failed to send print message"); + .expect("Failed to send print command"); } #[track_caller] - pub(super) fn send_flush_message_and_wait() { + pub(super) fn send_flush_command_and_wait() { let barrier = Arc::new(Barrier::new(2)); get_sender() .send(LoggerCommand::Flush { finished_barrier: barrier.clone(), }) - .expect("Failed to send shutdown message"); + .expect("Failed to send flush command"); barrier.wait(); } @@ -161,7 +161,7 @@ mod logger_thread { .send(LoggerCommand::FlushAndShutdown { finished_barrier: barrier.clone(), }) - .expect("Failed to send shutdown message"); + .expect("Failed to send shutdown command"); barrier.wait(); } @@ -189,7 +189,7 @@ mod logger_thread { match msg { LoggerCommand::Print(msg) => { // Append message to buffer - if let Some(msg) = msg.to_processed_message() { + if let Some(msg) = msg.to_formatted_message() { buffer.push(msg); } From 2f7c7e8ff2e1ffbdd9344ad8268e73d2c47a8b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Wed, 20 Nov 2024 03:34:34 -0300 Subject: [PATCH 047/101] tests: fix UI snapshot tests --- tests/snapshots/ui__ui_test_ok_compress-2.snap | 2 +- tests/snapshots/ui__ui_test_ok_compress.snap | 4 ++-- tests/snapshots/ui__ui_test_ok_decompress.snap | 2 +- tests/snapshots/ui__ui_test_ok_format_flag_with_rar-1.snap | 4 ++-- tests/snapshots/ui__ui_test_ok_format_flag_with_rar-2.snap | 4 ++-- tests/snapshots/ui__ui_test_ok_format_flag_without_rar-1.snap | 4 ++-- tests/snapshots/ui__ui_test_ok_format_flag_without_rar-2.snap | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/snapshots/ui__ui_test_ok_compress-2.snap b/tests/snapshots/ui__ui_test_ok_compress-2.snap index da6dd20..ed872d7 100644 --- a/tests/snapshots/ui__ui_test_ok_compress-2.snap +++ b/tests/snapshots/ui__ui_test_ok_compress-2.snap @@ -2,4 +2,4 @@ source: tests/ui.rs expression: "run_ouch(\"ouch compress input output.gz\", dir)" --- -[INFO] Successfully compressed 'output.gz'. +[INFO] Successfully compressed 'output.gz' diff --git a/tests/snapshots/ui__ui_test_ok_compress.snap b/tests/snapshots/ui__ui_test_ok_compress.snap index 93f1165..2e2aea8 100644 --- a/tests/snapshots/ui__ui_test_ok_compress.snap +++ b/tests/snapshots/ui__ui_test_ok_compress.snap @@ -2,5 +2,5 @@ source: tests/ui.rs expression: "run_ouch(\"ouch compress input output.zip\", dir)" --- -[INFO] Compressing 'input'. -[INFO] Successfully compressed 'output.zip'. +[INFO] Compressing 'input' +[INFO] Successfully compressed 'output.zip' diff --git a/tests/snapshots/ui__ui_test_ok_decompress.snap b/tests/snapshots/ui__ui_test_ok_decompress.snap index a226ed3..73d8678 100644 --- a/tests/snapshots/ui__ui_test_ok_decompress.snap +++ b/tests/snapshots/ui__ui_test_ok_decompress.snap @@ -2,5 +2,5 @@ source: tests/ui.rs expression: "run_ouch(\"ouch decompress output.zst\", dir)" --- -[INFO] Successfully decompressed archive in current directory. +[INFO] Successfully decompressed archive in current directory [INFO] Files unpacked: 1 diff --git a/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-1.snap b/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-1.snap index e48f9a1..ba8bb9e 100644 --- a/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-1.snap +++ b/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-1.snap @@ -2,5 +2,5 @@ source: tests/ui.rs expression: "run_ouch(\"ouch compress input output1 --format tar.gz\", dir)" --- -[INFO] Compressing 'input'. -[INFO] Successfully compressed 'output1'. +[INFO] Compressing 'input' +[INFO] Successfully compressed 'output1' diff --git a/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-2.snap b/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-2.snap index aab5ab2..ccaf1dd 100644 --- a/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-2.snap +++ b/tests/snapshots/ui__ui_test_ok_format_flag_with_rar-2.snap @@ -2,5 +2,5 @@ source: tests/ui.rs expression: "run_ouch(\"ouch compress input output2 --format .tar.gz\", dir)" --- -[INFO] Compressing 'input'. -[INFO] Successfully compressed 'output2'. +[INFO] Compressing 'input' +[INFO] Successfully compressed 'output2' diff --git a/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-1.snap b/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-1.snap index e48f9a1..ba8bb9e 100644 --- a/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-1.snap +++ b/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-1.snap @@ -2,5 +2,5 @@ source: tests/ui.rs expression: "run_ouch(\"ouch compress input output1 --format tar.gz\", dir)" --- -[INFO] Compressing 'input'. -[INFO] Successfully compressed 'output1'. +[INFO] Compressing 'input' +[INFO] Successfully compressed 'output1' diff --git a/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-2.snap b/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-2.snap index aab5ab2..ccaf1dd 100644 --- a/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-2.snap +++ b/tests/snapshots/ui__ui_test_ok_format_flag_without_rar-2.snap @@ -2,5 +2,5 @@ source: tests/ui.rs expression: "run_ouch(\"ouch compress input output2 --format .tar.gz\", dir)" --- -[INFO] Compressing 'input'. -[INFO] Successfully compressed 'output2'. +[INFO] Compressing 'input' +[INFO] Successfully compressed 'output2' From 7ea062586048f1e6cd6c6eaa25e18810652d6cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Mon, 18 Nov 2024 00:43:19 -0300 Subject: [PATCH 048/101] refac: simplify error treatment --- src/archive/rar.rs | 12 +++++++----- src/archive/sevenz.rs | 14 +++++--------- src/archive/tar.rs | 4 ++-- src/archive/zip.rs | 4 ++-- src/commands/list.rs | 10 +++++----- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/archive/rar.rs b/src/archive/rar.rs index ce43266..c223192 100644 --- a/src/archive/rar.rs +++ b/src/archive/rar.rs @@ -4,7 +4,11 @@ use std::path::Path; use unrar::Archive; -use crate::{error::Error, list::FileInArchive, utils::logger::info}; +use crate::{ + error::{Error, Result}, + list::FileInArchive, + utils::logger::info, +}; /// Unpacks the archive given by `archive_path` into the folder given by `output_folder`. /// Assumes that output_folder is empty @@ -48,15 +52,13 @@ pub fn unpack_archive( pub fn list_archive( archive_path: &Path, password: Option<&[u8]>, -) -> crate::Result>> { +) -> Result>> { let archive = match password { Some(password) => Archive::with_password(archive_path, password), None => Archive::new(archive_path), }; - let archive = archive.open_for_listing()?; - - Ok(archive.map(|item| { + Ok(archive.open_for_listing()?.map(|item| { let item = item?; let is_dir = item.is_directory(); let path = item.filename; diff --git a/src/archive/sevenz.rs b/src/archive/sevenz.rs index 6ee345d..8898f5f 100644 --- a/src/archive/sevenz.rs +++ b/src/archive/sevenz.rs @@ -12,7 +12,7 @@ use same_file::Handle; use sevenz_rust::SevenZArchiveEntry; use crate::{ - error::{Error, FinalError}, + error::{Error, FinalError, Result}, list::FileInArchive, utils::{ cd_into_same_dir_as, @@ -174,7 +174,7 @@ where pub fn list_archive( archive_path: &Path, password: Option<&[u8]>, -) -> crate::Result>> { +) -> Result>> { let reader = fs::File::open(archive_path)?; let mut files = Vec::new(); @@ -187,7 +187,7 @@ pub fn list_archive( Ok(true) }; - let result = match password { + match password { Some(password) => { let password = match password.to_str() { Ok(p) => p, @@ -202,13 +202,9 @@ pub fn list_archive( ".", sevenz_rust::Password::from(password), entry_extract_fn, - ) + )?; } - None => sevenz_rust::decompress_with_extract_fn(reader, ".", entry_extract_fn), - }; - - if let Err(e) = result { - return Err(e.into()); + None => sevenz_rust::decompress_with_extract_fn(reader, ".", entry_extract_fn)?, } Ok(files.into_iter()) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index d20aa7d..fda05eb 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -54,7 +54,7 @@ pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) /// List contents of `archive`, returning a vector of archive entries pub fn list_archive( mut archive: tar::Archive, -) -> crate::Result>> { +) -> impl Iterator> { struct Files(Receiver>); impl Iterator for Files { type Item = crate::Result; @@ -77,7 +77,7 @@ pub fn list_archive( } }); - Ok(Files(rx)) + Files(rx) } /// Compresses the archives given by `input_filenames` into the file given previously to `writer`. diff --git a/src/archive/zip.rs b/src/archive/zip.rs index af20950..9995e07 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -105,7 +105,7 @@ where pub fn list_archive( mut archive: ZipArchive, password: Option<&[u8]>, -) -> crate::Result>> +) -> impl Iterator> where R: Read + Seek + Send + 'static, { @@ -145,7 +145,7 @@ where } }); - Ok(Files(rx)) + Files(rx) } /// Compresses the archives given by `input_filenames` into the file given previously to `writer`. diff --git a/src/commands/list.rs b/src/commands/list.rs index 4d87a8c..1821d6d 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -34,7 +34,7 @@ pub fn list_archive_contents( // Any other Zip decompression done can take up the whole RAM and freeze ouch. if let &[Zip] = formats.as_slice() { let zip_archive = zip::ZipArchive::new(reader)?; - let files = crate::archive::zip::list_archive(zip_archive, password)?; + let files = crate::archive::zip::list_archive(zip_archive, password); list::list_files(archive_path, files, list_options)?; return Ok(()); @@ -65,7 +65,7 @@ pub fn list_archive_contents( } let files: Box>> = match formats[0] { - Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))?), + Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))), Zip => { if formats.len() > 1 { // Locking necessary to guarantee that warning and question @@ -82,7 +82,7 @@ pub fn list_archive_contents( io::copy(&mut reader, &mut vec)?; let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; - Box::new(crate::archive::zip::list_archive(zip_archive, password)?) + Box::new(crate::archive::zip::list_archive(zip_archive, password)) } #[cfg(feature = "unrar")] Rar => { @@ -116,6 +116,6 @@ pub fn list_archive_contents( panic!("Not an archive! This should never happen, if it does, something is wrong with `CompressionFormat::is_archive()`. Please report this error!"); } }; - list::list_files(archive_path, files, list_options)?; - Ok(()) + + list::list_files(archive_path, files, list_options) } From 162dfbd29f1e116297da2ff404f8809c164aeb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Wed, 20 Nov 2024 04:47:57 -0300 Subject: [PATCH 049/101] chore: format `Cargo.toml` --- Cargo.toml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c34e34..e280682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "ouch" version = "0.5.1" -authors = ["Vinícius Rodrigues Miguel ", "João M. Bezerra "] +authors = [ + "João M. Bezerra ", + "Vinícius Rodrigues Miguel ", +] edition = "2021" readme = "README.md" repository = "https://github.com/ouch-org/ouch" @@ -20,7 +23,9 @@ clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } fs-err = "2.11.0" -gzp = { version = "0.11.3", default-features = false, features = ["snappy_default"] } +gzp = { version = "0.11.3", default-features = false, features = [ + "snappy_default", +] } ignore = "0.4.23" libc = "0.2.155" linked-hash-map = "0.5.6" @@ -36,8 +41,11 @@ tempfile = "3.10.1" time = { version = "0.3.36", default-features = false } unrar = { version = "0.5.6", optional = true } xz2 = "0.1.7" -zip = { version = "0.6.6", default-features = false, features = ["time", "aes-crypto"] } -zstd = { version = "0.13.2", default-features = false, features = ["zstdmt"]} +zip = { version = "0.6.6", default-features = false, features = [ + "time", + "aes-crypto", +] } +zstd = { version = "0.13.2", default-features = false, features = ["zstdmt"] } [target.'cfg(not(unix))'.dependencies] is_executable = "1.0.1" @@ -53,7 +61,10 @@ infer = "0.16.0" insta = { version = "1.40.0", features = ["filters"] } parse-display = "0.9.1" proptest = "1.5.0" -rand = { version = "0.8.5", default-features = false, features = ["small_rng", "std"] } +rand = { version = "0.8.5", default-features = false, features = [ + "small_rng", + "std", +] } regex = "1.10.4" test-strategy = "0.4.0" From 97b46086934a0a60188a328d4f6e3446bee55485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Wed, 20 Nov 2024 04:58:02 -0300 Subject: [PATCH 050/101] chore: use new Cargo profile in CI for shorter compilation new profile is called `fast` and it's not as fast as `release`, but should be fast enough for CI and compiles faster --- .github/workflows/build-artifacts-and-run-tests.yml | 9 +++++---- Cargo.toml | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml index 83d5984..cb3bf30 100644 --- a/.github/workflows/build-artifacts-and-run-tests.yml +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -126,17 +126,18 @@ jobs: # there's no way to run tests for ARM64 Windows for now if: matrix.target != 'aarch64-pc-windows-msvc' run: | - ${{ env.CARGO }} +stable test --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS + ${{ env.CARGO }} +stable test --profile fast --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS - - name: Build artifacts (binary and completions) + - name: Build release artifacts (binary and completions) + if: ${{ inputs.upload_artifacts }} run: | ${{ env.CARGO }} +stable build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS env: OUCH_ARTIFACTS_FOLDER: artifacts - - name: Upload artifacts - uses: actions/upload-artifact@v4 + - name: Upload release artifacts if: ${{ inputs.upload_artifacts }} + uses: actions/upload-artifact@v4 with: name: ouch-${{ matrix.target }}-${{ steps.concat-features.outputs.FEATURES }} path: | diff --git a/Cargo.toml b/Cargo.toml index e280682..c9459ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,17 @@ default = ["use_zlib", "use_zstd_thin", "unrar"] use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-zlib"] use_zstd_thin = ["zstd/thin"] +# For generating binaries for releases [profile.release] lto = true codegen-units = 1 opt-level = 3 strip = true + +# When we need a fast binary that compiles slightly faster `release` (useful for CI) +[profile.fast] +inherits = "release" +lto = false +opt-level = 2 +incremental = true +codegen-units = 16 From e405690d351b655c3a4a017776bb4edf661af436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20P=2E=20Bezerra?= Date: Wed, 20 Nov 2024 05:09:26 -0300 Subject: [PATCH 051/101] chore: tweak tests so they run faster --- tests/integration.rs | 31 +++++++++++++------------------ tests/utils.rs | 2 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index ed2b4b1..779766a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -100,14 +100,13 @@ fn single_empty_file(ext: Extension, #[any(size_range(0..8).lift())] exts: Vec, - #[cfg_attr(not(target_arch = "arm"), strategy(proptest::option::of(0i16..12)))] - // Decrease the value of --level flag for `arm` systems, because our GitHub - // Actions CI runs QEMU which makes the memory consumption higher. - #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..8)))] + #[any(size_range(0..6).lift())] exts: Vec, + // Use faster --level for slower CI targets + #[cfg_attr(not(any(target_arch = "arm", target_abi = "eabihf")), strategy(proptest::option::of(0i16..12)))] + #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..6)))] level: Option, ) { let dir = tempdir().unwrap(); @@ -135,10 +134,9 @@ fn single_file( fn single_file_stdin( ext: Extension, #[any(size_range(0..8).lift())] exts: Vec, - #[cfg_attr(not(target_arch = "arm"), strategy(proptest::option::of(0i16..12)))] - // Decrease the value of --level flag for `arm` systems, because our GitHub - // Actions CI runs QEMU which makes the memory consumption higher. - #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..8)))] + // Use faster --level for slower CI targets + #[cfg_attr(not(any(target_arch = "arm", target_abi = "eabihf")), strategy(proptest::option::of(0i16..12)))] + #[cfg_attr(target_arch = "arm", strategy(proptest::option::of(0i16..6)))] level: Option, ) { let dir = tempdir().unwrap(); @@ -175,22 +173,19 @@ fn single_file_stdin( assert_same_directory(before, after, false); } -/// Compress and decompress a directory with random content generated with create_random_files -/// -/// This one runs only 50 times because there are only `.zip` and `.tar` to be tested, and -/// single-file formats testing is done in the other test -#[proptest(cases = 50)] +/// Compress and decompress a directory with random content generated with `create_random_files` +#[proptest(cases = 25)] fn multiple_files( ext: DirectoryExtension, - #[any(size_range(0..5).lift())] exts: Vec, - #[strategy(0u8..4)] depth: u8, + #[any(size_range(0..1).lift())] extra_extensions: Vec, + #[strategy(0u8..3)] depth: u8, ) { let dir = tempdir().unwrap(); let dir = dir.path(); let before = &dir.join("before"); let before_dir = &before.join("dir"); fs::create_dir_all(before_dir).unwrap(); - let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, exts))); + let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); let after = &dir.join("after"); create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); ouch!("-A", "c", before_dir, archive); diff --git a/tests/utils.rs b/tests/utils.rs index 0cda620..3648cef 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -51,7 +51,7 @@ pub fn create_files_in(dir: &Path, files: &[&str]) { /// Write random content to a file pub fn write_random_content(file: &mut impl Write, rng: &mut impl RngCore) { - let mut data = vec![0; rng.gen_range(0..4096)]; + let mut data = vec![0; rng.gen_range(0..8192)]; rng.fill_bytes(&mut data); file.write_all(&data).unwrap(); From 8c32d2c31adec8dbe3e29607ed28d78c5c6e6c3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:27:57 +0000 Subject: [PATCH 052/101] build(deps): bump dawidd6/action-download-artifact in /.github/workflows Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 3 to 6. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v3...v6) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/draft-release-automatic-trigger.yml | 2 +- .github/workflows/draft-release-manual-trigger.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml index f007f80..2ebf967 100644 --- a/.github/workflows/draft-release-automatic-trigger.yml +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v4 - name: Download artifacts - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v6 with: path: artifacts diff --git a/.github/workflows/draft-release-manual-trigger.yml b/.github/workflows/draft-release-manual-trigger.yml index 6e296fe..d97ec72 100644 --- a/.github/workflows/draft-release-manual-trigger.yml +++ b/.github/workflows/draft-release-manual-trigger.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Download artifacts - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v6 with: path: artifacts workflow: build-and-test.yml From 22d4e0faf0788030156b322c5115a3e31fc76591 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 16:09:54 +0000 Subject: [PATCH 053/101] chore: add comments about RAR 4.x and 5.0 signature --- src/utils/fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 1d5790f..d33411a 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -98,6 +98,9 @@ pub fn try_infer_extension(path: &Path) -> Option { buf.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]) } fn is_rar(buf: &[u8]) -> bool { + // ref https://www.rarlab.com/technote.htm#rarsign + // RAR 5.0 8 bytes length signature: 0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00 + // RAR 4.x 7 bytes length signature: 0x52 0x61 0x72 0x21 0x1A 0x07 0x00 buf.len() >= 7 && buf.starts_with(&[0x52, 0x61, 0x72, 0x21, 0x1A, 0x07]) && (buf[6] == 0x00 || (buf.len() >= 8 && buf[6..=7] == [0x01, 0x00])) From 493213e393c126823ab06f677506c64475c8afaa Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 16:11:38 +0000 Subject: [PATCH 054/101] fix: update unrar crate to fix deompress issue with rar files which include CJK filenames in it --- Cargo.lock | 243 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 2 +- 2 files changed, 123 insertions(+), 122 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index caf9997..83712b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -45,36 +45,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64ct" @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "regex-automata", @@ -223,9 +223,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bytesize" @@ -277,9 +277,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" dependencies = [ "clap_builder", "clap_derive", @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" dependencies = [ "anstream", "anstyle", @@ -366,9 +366,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.28" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b378c786d3bde9442d2c6dd7e6080b2a818db2b96e30d6e7f1b6d224eb617d3" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ "clap", ] @@ -382,14 +382,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clap_mangen" @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "console" @@ -438,9 +438,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -550,19 +550,19 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "filetime" @@ -589,9 +589,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "libz-sys", @@ -628,15 +628,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "generic-array" @@ -773,9 +773,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" dependencies = [ "console", "lazy_static", @@ -786,9 +786,9 @@ dependencies = [ [[package]] name = "is_executable" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" dependencies = [ "winapi", ] @@ -810,10 +810,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -843,15 +844,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.158" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", @@ -859,9 +860,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -1091,7 +1092,7 @@ dependencies = [ "regex", "regex-syntax", "structmeta", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] @@ -1125,29 +1126,29 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -1193,9 +1194,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1296,18 +1297,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1317,9 +1318,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1328,9 +1329,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "roff" @@ -1346,9 +1347,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -1386,22 +1387,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] @@ -1494,7 +1495,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] @@ -1505,14 +1506,14 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1527,9 +1528,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1538,9 +1539,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -1549,9 +1550,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -1575,34 +1576,34 @@ dependencies = [ "proc-macro2", "quote", "structmeta", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "num-conv", @@ -1620,9 +1621,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -1652,15 +1653,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unrar" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99d6a7735a222f2119ca4572e713fb468b5c3a17a4fb90b3cac3e28a8680c29" +checksum = "621a1f538dffc544a8a35ca32140d081908b4f5999ee9886ff0c5121d6bc86a5" dependencies = [ "bitflags 1.3.2", "regex", @@ -1687,9 +1688,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "vcpkg" @@ -1730,9 +1731,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -1741,24 +1742,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1766,22 +1767,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "which" @@ -1952,7 +1953,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c9459ea..f9f602d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ snap = "1.1.1" tar = "0.4.42" tempfile = "3.10.1" time = { version = "0.3.36", default-features = false } -unrar = { version = "0.5.6", optional = true } +unrar = { version = "0.5.7", optional = true } xz2 = "0.1.7" zip = { version = "0.6.6", default-features = false, features = [ "time", From 55aa05b631cd7348ed99fe71c547242f34b8a087 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 16:00:50 +0000 Subject: [PATCH 055/101] feat(cli): add option to remove source file after decompression --- src/cli/args.rs | 8 ++++++++ src/commands/decompress.rs | 24 ++++++++++++++++++++++-- src/commands/mod.rs | 14 +++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 05b522c..c07e85c 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -88,6 +88,10 @@ pub enum Subcommand { /// Place results in a directory other than the current one #[arg(short = 'd', long = "dir", value_hint = ValueHint::FilePath)] output_dir: Option, + + /// Remove the source file after successful decompression + #[arg(short = 'r', long, default_value_t = false)] + remove: bool, }, /// List contents of an archive #[command(visible_aliases = ["l", "ls"])] @@ -142,6 +146,7 @@ mod tests { // Put a crazy value here so no test can assert it unintentionally files: vec!["\x00\x11\x22".into()], output_dir: None, + remove: false, }, } } @@ -154,6 +159,7 @@ mod tests { cmd: Subcommand::Decompress { files: to_paths(["file.tar.gz"]), output_dir: None, + remove: false, }, ..mock_cli_args() } @@ -164,6 +170,7 @@ mod tests { cmd: Subcommand::Decompress { files: to_paths(["file.tar.gz"]), output_dir: None, + remove: false, }, ..mock_cli_args() } @@ -174,6 +181,7 @@ mod tests { cmd: Subcommand::Decompress { files: to_paths(["a", "b", "c"]), output_dir: None, + remove: false, }, ..mock_cli_args() } diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 6c35c25..a1d7e84 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -14,8 +14,11 @@ use crate::{ Extension, }, utils::{ - self, io::lock_and_flush_output_stdio, is_path_stdin, logger::info_accessible, nice_directory_display, - user_wants_to_continue, + self, + io::lock_and_flush_output_stdio, + is_path_stdin, + logger::{info, info_accessible}, + nice_directory_display, user_wants_to_continue, }, QuestionAction, QuestionPolicy, BUFFER_CAPACITY, }; @@ -37,6 +40,7 @@ pub fn decompress_file( question_policy: QuestionPolicy, quiet: bool, password: Option<&[u8]>, + remove: bool, ) -> crate::Result<()> { assert!(output_dir.exists()); let input_is_stdin = is_path_stdin(input_file_path); @@ -83,6 +87,14 @@ pub fn decompress_file( files_unpacked )); + if !input_is_stdin && remove { + fs::remove_file(input_file_path)?; + info(format!( + "Removed input file {}", + nice_directory_display(input_file_path) + )); + } + return Ok(()); } @@ -233,6 +245,14 @@ pub fn decompress_file( )); info_accessible(format!("Files unpacked: {}", files_unpacked)); + if !input_is_stdin && remove { + fs::remove_file(input_file_path)?; + info(format!( + "Removed input file {}", + nice_directory_display(input_file_path) + )); + } + Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f296359..a4b8fca 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -134,7 +134,11 @@ pub fn run( compress_result.map(|_| ()) } - Subcommand::Decompress { files, output_dir } => { + Subcommand::Decompress { + files, + output_dir, + remove, + } => { let mut output_paths = vec![]; let mut formats = vec![]; @@ -182,6 +186,13 @@ pub fn run( } else { output_dir.join(file_name) }; + info_accessible(format!( + "begin decompress file ... input_path: {}, formats: {:?}, file_name: {}, output_file_path: {}", + path_to_str(input_path), + formats, + path_to_str(file_name), + path_to_str(&output_file_path) + )); decompress_file( input_path, formats, @@ -192,6 +203,7 @@ pub fn run( args.password.as_deref().map(|str| { <[u8] as ByteSlice>::from_os_str(str).expect("convert password to bytes failed") }), + remove, ) }) } From 62f3d78f440d0d554def265e004ea4d8846eb124 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 16:27:02 +0000 Subject: [PATCH 056/101] refactor(decompress): refactor function to use DecompressOptions struct to make linter happy (too_many_arguments) --- src/commands/decompress.rs | 100 +++++++++++++++++++------------------ src/commands/mod.rs | 13 ++--- 2 files changed, 58 insertions(+), 55 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index a1d7e84..4a616e2 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -26,24 +26,26 @@ use crate::{ trait ReadSeek: Read + io::Seek {} impl ReadSeek for T {} +pub struct DecompressOptions<'a> { + pub input_file_path: &'a Path, + pub formats: Vec, + pub output_dir: &'a Path, + pub output_file_path: PathBuf, + pub question_policy: QuestionPolicy, + pub quiet: bool, + pub password: Option<&'a [u8]>, + pub remove: bool, +} + /// Decompress a file /// /// File at input_file_path is opened for reading, example: "archive.tar.gz" /// formats contains each format necessary for decompression, example: [Gz, Tar] (in decompression order) /// output_dir it's where the file will be decompressed to, this function assumes that the directory exists /// output_file_path is only used when extracting single file formats, not archive formats like .tar or .zip -pub fn decompress_file( - input_file_path: &Path, - formats: Vec, - output_dir: &Path, - output_file_path: PathBuf, - question_policy: QuestionPolicy, - quiet: bool, - password: Option<&[u8]>, - remove: bool, -) -> crate::Result<()> { - assert!(output_dir.exists()); - let input_is_stdin = is_path_stdin(input_file_path); +pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { + assert!(options.output_dir.exists()); + let input_is_stdin = is_path_stdin(options.input_file_path); // Zip archives are special, because they require io::Seek, so it requires it's logic separated // from decoder chaining. @@ -55,7 +57,7 @@ pub fn decompress_file( if let [Extension { compression_formats: [Zip], .. - }] = formats.as_slice() + }] = options.formats.as_slice() { let mut vec = vec![]; let reader: Box = if input_is_stdin { @@ -63,14 +65,14 @@ pub fn decompress_file( io::copy(&mut io::stdin(), &mut vec)?; Box::new(io::Cursor::new(vec)) } else { - Box::new(fs::File::open(input_file_path)?) + Box::new(fs::File::open(options.input_file_path)?) }; let zip_archive = zip::ZipArchive::new(reader)?; let files_unpacked = if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, password, quiet), - output_dir, - &output_file_path, - question_policy, + |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet), + options.output_dir, + &options.output_file_path, + options.question_policy, )? { files } else { @@ -83,15 +85,15 @@ pub fn decompress_file( // about whether the command succeeded without such a message info_accessible(format!( "Successfully decompressed archive in {} ({} files)", - nice_directory_display(output_dir), + nice_directory_display(options.output_dir), files_unpacked )); - if !input_is_stdin && remove { - fs::remove_file(input_file_path)?; + if !input_is_stdin && options.remove { + fs::remove_file(options.input_file_path)?; info(format!( "Removed input file {}", - nice_directory_display(input_file_path) + nice_directory_display(options.input_file_path) )); } @@ -102,7 +104,7 @@ pub fn decompress_file( let reader: Box = if input_is_stdin { Box::new(io::stdin()) } else { - Box::new(fs::File::open(input_file_path)?) + Box::new(fs::File::open(options.input_file_path)?) }; let reader = BufReader::with_capacity(BUFFER_CAPACITY, reader); let mut reader: Box = Box::new(reader); @@ -122,7 +124,7 @@ pub fn decompress_file( Ok(decoder) }; - let (first_extension, extensions) = split_first_compression_format(&formats); + let (first_extension, extensions) = split_first_compression_format(&options.formats); for format in extensions.iter().rev() { reader = chain_reader_decoder(format, reader)?; @@ -132,7 +134,7 @@ pub fn decompress_file( 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)? { + let mut writer = match utils::ask_to_create_file(&options.output_file_path, options.question_policy)? { Some(file) => file, None => return Ok(()), }; @@ -143,10 +145,10 @@ pub fn decompress_file( } Tar => { if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::tar::unpack_archive(reader, output_dir, quiet), - output_dir, - &output_file_path, - question_policy, + |output_dir| crate::archive::tar::unpack_archive(reader, output_dir, options.quiet), + options.output_dir, + &options.output_file_path, + options.question_policy, )? { files } else { @@ -154,13 +156,13 @@ pub fn decompress_file( } } Zip => { - if formats.len() > 1 { + if options.formats.len() > 1 { // Locking necessary to guarantee that warning and question // messages stay adjacent let _locks = lock_and_flush_output_stdio(); warn_user_about_loading_zip_in_memory(); - if !user_wants_to_continue(input_file_path, question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue(options.input_file_path, options.question_policy, QuestionAction::Decompression)? { return Ok(()); } } @@ -170,10 +172,10 @@ pub fn decompress_file( let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, password, quiet), - output_dir, - &output_file_path, - question_policy, + |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet), + options.output_dir, + &options.output_file_path, + options.question_policy, )? { files } else { @@ -183,18 +185,18 @@ pub fn decompress_file( #[cfg(feature = "unrar")] Rar => { type UnpackResult = crate::Result; - let unpack_fn: Box UnpackResult> = if formats.len() > 1 || input_is_stdin { + let unpack_fn: Box UnpackResult> = if options.formats.len() > 1 || input_is_stdin { let mut temp_file = tempfile::NamedTempFile::new()?; io::copy(&mut reader, &mut temp_file)?; Box::new(move |output_dir| { - crate::archive::rar::unpack_archive(temp_file.path(), output_dir, password, quiet) + crate::archive::rar::unpack_archive(temp_file.path(), output_dir, options.password, options.quiet) }) } else { - Box::new(|output_dir| crate::archive::rar::unpack_archive(input_file_path, output_dir, password, quiet)) + Box::new(|output_dir| crate::archive::rar::unpack_archive(options.input_file_path, output_dir, options.password, options.quiet)) }; if let ControlFlow::Continue(files) = - smart_unpack(unpack_fn, output_dir, &output_file_path, question_policy)? + smart_unpack(unpack_fn, options.output_dir, &options.output_file_path, options.question_policy)? { files } else { @@ -206,13 +208,13 @@ pub fn decompress_file( return Err(crate::archive::rar_stub::no_support()); } SevenZip => { - if formats.len() > 1 { + if options.formats.len() > 1 { // Locking necessary to guarantee that warning and question // messages stay adjacent let _locks = lock_and_flush_output_stdio(); warn_user_about_loading_sevenz_in_memory(); - if !user_wants_to_continue(input_file_path, question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue(options.input_file_path, options.question_policy, QuestionAction::Decompression)? { return Ok(()); } } @@ -222,11 +224,11 @@ pub fn decompress_file( if let ControlFlow::Continue(files) = smart_unpack( |output_dir| { - crate::archive::sevenz::decompress_sevenz(io::Cursor::new(vec), output_dir, password, quiet) + crate::archive::sevenz::decompress_sevenz(io::Cursor::new(vec), output_dir, options.password, options.quiet) }, - output_dir, - &output_file_path, - question_policy, + options.output_dir, + &options.output_file_path, + options.question_policy, )? { files } else { @@ -241,15 +243,15 @@ pub fn decompress_file( // about whether the command succeeded without such a message info_accessible(format!( "Successfully decompressed archive in {}", - nice_directory_display(output_dir) + nice_directory_display(options.output_dir) )); info_accessible(format!("Files unpacked: {}", files_unpacked)); - if !input_is_stdin && remove { - fs::remove_file(input_file_path)?; + if !input_is_stdin && options.remove { + fs::remove_file(options.input_file_path)?; info(format!( "Removed input file {}", - nice_directory_display(input_file_path) + nice_directory_display(options.input_file_path) )); } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a4b8fca..50f9857 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -7,6 +7,7 @@ mod list; use std::{ops::ControlFlow, path::PathBuf}; use bstr::ByteSlice; +use decompress::DecompressOptions; use rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use utils::colors; @@ -193,18 +194,18 @@ pub fn run( path_to_str(file_name), path_to_str(&output_file_path) )); - decompress_file( - input_path, + decompress_file(DecompressOptions { + input_file_path: input_path, formats, - &output_dir, + output_dir: &output_dir, output_file_path, question_policy, - args.quiet, - args.password.as_deref().map(|str| { + quiet: args.quiet, + password: args.password.as_deref().map(|str| { <[u8] as ByteSlice>::from_os_str(str).expect("convert password to bytes failed") }), remove, - ) + }) }) } Subcommand::List { archives: files, tree } => { From 353c360f6f32ed7429ea93ef4a16e3f9f345faaf Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 16:28:16 +0000 Subject: [PATCH 057/101] style: cargo fmt with nightly version --- src/commands/decompress.rs | 41 ++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 4a616e2..1d0152a 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -162,7 +162,11 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { let _locks = lock_and_flush_output_stdio(); warn_user_about_loading_zip_in_memory(); - if !user_wants_to_continue(options.input_file_path, options.question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue( + options.input_file_path, + options.question_policy, + QuestionAction::Decompression, + )? { return Ok(()); } } @@ -172,7 +176,9 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; if let ControlFlow::Continue(files) = smart_unpack( - |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet), + |output_dir| { + crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet) + }, options.output_dir, &options.output_file_path, options.question_policy, @@ -192,12 +198,22 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { crate::archive::rar::unpack_archive(temp_file.path(), output_dir, options.password, options.quiet) }) } else { - Box::new(|output_dir| crate::archive::rar::unpack_archive(options.input_file_path, output_dir, options.password, options.quiet)) + Box::new(|output_dir| { + crate::archive::rar::unpack_archive( + options.input_file_path, + output_dir, + options.password, + options.quiet, + ) + }) }; - if let ControlFlow::Continue(files) = - smart_unpack(unpack_fn, options.output_dir, &options.output_file_path, options.question_policy)? - { + if let ControlFlow::Continue(files) = smart_unpack( + unpack_fn, + options.output_dir, + &options.output_file_path, + options.question_policy, + )? { files } else { return Ok(()); @@ -214,7 +230,11 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { let _locks = lock_and_flush_output_stdio(); warn_user_about_loading_sevenz_in_memory(); - if !user_wants_to_continue(options.input_file_path, options.question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue( + options.input_file_path, + options.question_policy, + QuestionAction::Decompression, + )? { return Ok(()); } } @@ -224,7 +244,12 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { if let ControlFlow::Continue(files) = smart_unpack( |output_dir| { - crate::archive::sevenz::decompress_sevenz(io::Cursor::new(vec), output_dir, options.password, options.quiet) + crate::archive::sevenz::decompress_sevenz( + io::Cursor::new(vec), + output_dir, + options.password, + options.quiet, + ) }, options.output_dir, &options.output_file_path, From 5941afe66ee26b49792284885db97d4fff211267 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 16:34:57 +0000 Subject: [PATCH 058/101] chore: remove decompress file params info to make ui_test_ok_decompress test case pass --- src/commands/mod.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 50f9857..97410d7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -187,13 +187,6 @@ pub fn run( } else { output_dir.join(file_name) }; - info_accessible(format!( - "begin decompress file ... input_path: {}, formats: {:?}, file_name: {}, output_file_path: {}", - path_to_str(input_path), - formats, - path_to_str(file_name), - path_to_str(&output_file_path) - )); decompress_file(DecompressOptions { input_file_path: input_path, formats, From 499ad776571bfddcc8a3b894527be51f87532dd4 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Tue, 3 Dec 2024 17:01:06 +0000 Subject: [PATCH 059/101] fix(cli): remove default value for remove flag in Subcommand --- src/cli/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index c07e85c..7216962 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -90,7 +90,7 @@ pub enum Subcommand { output_dir: Option, /// Remove the source file after successful decompression - #[arg(short = 'r', long, default_value_t = false)] + #[arg(short = 'r', long)] remove: bool, }, /// List contents of an archive From 195483a18238e77b05a3d9d3bbc43d12de2915f0 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Tue, 3 Dec 2024 17:03:03 +0000 Subject: [PATCH 060/101] docs: update changelog with new features --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94d03ad..fe884ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Categories Used: - Add multithreading support for `zstd` compression [\#689](https://github.com/ouch-org/ouch/pull/689) ([nalabrie](https://github.com/nalabrie)) - Add `bzip3` support [\#522](https://github.com/ouch-org/ouch/pull/522) ([freijon](https://github.com/freijon)) +- Add `--remove` flag for decompression subcommand to remove files after successful decompression [\#757](https://github.com/ouch-org/ouch/pull/757) ([ttys3](https://github.com/ttys3)) ### Bug Fixes From 6b38e1dd4650ac71e9454d82dbefa130c0884cf2 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Sat, 30 Nov 2024 17:43:50 +0000 Subject: [PATCH 061/101] feat: add concurrent working threads option to CLI args --- src/cli/args.rs | 5 +++++ src/cli/mod.rs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/cli/args.rs b/src/cli/args.rs index 7216962..2dafe92 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -45,6 +45,10 @@ pub struct CliArgs { #[arg(short = 'p', long = "password", global = true)] pub password: Option, + /// cocurrent working threads + #[arg(short = 't', long, global = true)] + pub threads: Option, + // Ouch and claps subcommands #[command(subcommand)] pub cmd: Subcommand, @@ -142,6 +146,7 @@ mod tests { format: None, // This is usually replaced in assertion tests password: None, + threads: None, cmd: Subcommand::Decompress { // Put a crazy value here so no test can assert it unintentionally files: vec!["\x00\x11\x22".into()], diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ce6b5d5..4144e53 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -28,6 +28,13 @@ impl CliArgs { set_accessible(args.accessible); + if let Some(threads) = args.threads { + rayon::ThreadPoolBuilder::new() + .num_threads(threads) + .build_global() + .unwrap(); + } + let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. } | Subcommand::List { archives: files, .. }) = &mut args.cmd; From 77b01d170fdecf933c236625b3552c21c0c1e45a Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Mon, 2 Dec 2024 12:49:19 +0000 Subject: [PATCH 062/101] refactor(cli): move thread pool setup to command execution, use thread::spawn instead of rayon::spawn in the logger thread --- src/cli/mod.rs | 7 ------- src/commands/mod.rs | 8 ++++++++ src/utils/logger.rs | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 4144e53..ce6b5d5 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -28,13 +28,6 @@ impl CliArgs { set_accessible(args.accessible); - if let Some(threads) = args.threads { - rayon::ThreadPoolBuilder::new() - .num_threads(threads) - .build_global() - .unwrap(); - } - let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. } | Subcommand::List { archives: files, .. }) = &mut args.cmd; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 97410d7..9baf666 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -53,6 +53,14 @@ pub fn run( question_policy: QuestionPolicy, file_visibility_policy: FileVisibilityPolicy, ) -> crate::Result<()> { + + if let Some(threads) = args.threads { + rayon::ThreadPoolBuilder::new() + .num_threads(threads) + .build_global() + .unwrap(); + } + match args.cmd { Subcommand::Compress { files, diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 5156308..60936c6 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,4 +1,5 @@ use std::sync::{mpsc, Arc, Barrier, OnceLock}; +use std::thread; pub use logger_thread::spawn_logger_thread; @@ -168,7 +169,7 @@ mod logger_thread { pub fn spawn_logger_thread() { let log_receiver = setup_channel(); - rayon::spawn(move || run_logger(log_receiver)); + thread::spawn(move || run_logger(log_receiver)); } fn run_logger(log_receiver: LogReceiver) { From e1d7f1424a10f76c5f5656673df1e9bf379f0673 Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Mon, 2 Dec 2024 12:50:46 +0000 Subject: [PATCH 063/101] refactor: improve code formatting in `mod.rs` and `logger.rs` --- src/commands/mod.rs | 1 - src/utils/logger.rs | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9baf666..0bcb9b3 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -53,7 +53,6 @@ pub fn run( question_policy: QuestionPolicy, file_visibility_policy: FileVisibilityPolicy, ) -> crate::Result<()> { - if let Some(threads) = args.threads { rayon::ThreadPoolBuilder::new() .num_threads(threads) diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 60936c6..0de2dfd 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,5 +1,7 @@ -use std::sync::{mpsc, Arc, Barrier, OnceLock}; -use std::thread; +use std::{ + sync::{mpsc, Arc, Barrier, OnceLock}, + thread, +}; pub use logger_thread::spawn_logger_thread; From 28d0933d6c07c1a6224dfe40e0b00526e1dd920a Mon Sep 17 00:00:00 2001 From: ttyS3 Date: Tue, 3 Dec 2024 17:12:31 +0000 Subject: [PATCH 064/101] fix: change threads short flag to -c in cli args to avoid conflict with `tree` --- src/cli/args.rs | 2 +- tests/snapshots/ui__ui_test_usage_help_flag-2.snap | 2 ++ tests/snapshots/ui__ui_test_usage_help_flag.snap | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 2dafe92..e6fd66d 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -46,7 +46,7 @@ pub struct CliArgs { pub password: Option, /// cocurrent working threads - #[arg(short = 't', long, global = true)] + #[arg(short = 'c', long, global = true)] pub threads: Option, // Ouch and claps subcommands diff --git a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap index cb937d7..058d54e 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap @@ -1,6 +1,7 @@ --- source: tests/ui.rs expression: "output_to_string(ouch!(\"-h\"))" +snapshot_kind: text --- A command-line utility for easily compressing and decompressing files and directories. @@ -21,5 +22,6 @@ Options: -g, --gitignore Ignores files matched by git's ignore files -f, --format Specify the format of the archive -p, --password decompress or list with password + -c, --threads cocurrent working threads -h, --help Print help (see more with '--help') -V, --version Print version diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap index 921aa8d..f8a2ad4 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -1,6 +1,7 @@ --- source: tests/ui.rs expression: "output_to_string(ouch!(\"--help\"))" +snapshot_kind: text --- A command-line utility for easily compressing and decompressing files and directories. @@ -43,6 +44,9 @@ Options: -p, --password decompress or list with password + -c, --threads + cocurrent working threads + -h, --help Print help (see a summary with '-h') From aeefa694bff18f66d8aaab53c4a4eb917e456e48 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Wed, 12 Feb 2025 13:53:49 +0100 Subject: [PATCH 065/101] fix: Use BufWriter for list output Also replaces one `print` call with `write`. Fixes #702 --- CHANGELOG.md | 1 + src/list.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe884ab..e44b061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Categories Used: - Fix logging IO bottleneck [\#642](https://github.com/ouch-org/ouch/pull/642) ([AntoniosBarotsis](https://github.com/AntoniosBarotsis)) - Support decompression over stdin [\#692](https://github.com/ouch-org/ouch/pull/692) ([rcorre](https://github.com/rcorre)) - Make `--format` more forgiving with the formatting of the provided format [\#519](https://github.com/ouch-org/ouch/pull/519) ([marcospb19](https://github.com/marcospb19)) +- Use buffered writer for list output [\#764](https://github.com/ouch-org/ouch/pull/764) ([killercup](https://github.com/killercup)) ## [0.5.1](https://github.com/ouch-org/ouch/compare/0.5.0...0.5.1) diff --git a/src/list.rs b/src/list.rs index c065925..8491798 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,7 +1,7 @@ //! Some implementation helpers related to the 'list' command. use std::{ - io::{stdout, Write}, + io::{stdout, BufWriter, Write}, path::{Path, PathBuf}, }; @@ -32,16 +32,16 @@ pub fn list_files( files: impl IntoIterator>, list_options: ListOptions, ) -> crate::Result<()> { - let out = &mut stdout().lock(); + let mut out = BufWriter::new(stdout().lock()); let _ = writeln!(out, "Archive: {}", EscapedPathDisplay::new(archive)); if list_options.tree { let tree = files.into_iter().collect::>()?; - tree.print(out); + tree.print(&mut out); } else { for file in files { let FileInArchive { path, is_dir } = file?; - print_entry(out, EscapedPathDisplay::new(&path), is_dir); + print_entry(&mut out, EscapedPathDisplay::new(&path), is_dir); } } Ok(()) @@ -143,7 +143,7 @@ mod tree { false => draw::FINAL_BRANCH, }; - print!("{prefix}{final_part}"); + let _ = write!(out, "{prefix}{final_part}"); let is_dir = match self.file { Some(FileInArchive { is_dir, .. }) => is_dir, None => true, From f3b7c0277f4230cef592d42d19e233af4c7f3186 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Wed, 12 Feb 2025 10:59:20 +0100 Subject: [PATCH 066/101] fix(cli): Fix typo in 'concurrent' --- src/cli/args.rs | 2 +- tests/snapshots/ui__ui_test_usage_help_flag-2.snap | 2 +- tests/snapshots/ui__ui_test_usage_help_flag.snap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index e6fd66d..79cc47e 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -45,7 +45,7 @@ pub struct CliArgs { #[arg(short = 'p', long = "password", global = true)] pub password: Option, - /// cocurrent working threads + /// concurrent working threads #[arg(short = 'c', long, global = true)] pub threads: Option, diff --git a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap index 058d54e..66cc156 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap @@ -22,6 +22,6 @@ Options: -g, --gitignore Ignores files matched by git's ignore files -f, --format Specify the format of the archive -p, --password decompress or list with password - -c, --threads cocurrent working threads + -c, --threads concurrent working threads -h, --help Print help (see more with '--help') -V, --version Print version diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap index f8a2ad4..370193d 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -45,7 +45,7 @@ Options: decompress or list with password -c, --threads - cocurrent working threads + concurrent working threads -h, --help Print help (see a summary with '-h') From fadfe1a213b4cf565678f1dfbde60b38de509293 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Wed, 12 Feb 2025 11:04:54 +0100 Subject: [PATCH 067/101] fix(cli): Align docs phrasing and capitalization --- src/cli/args.rs | 10 +++++----- tests/snapshots/ui__ui_test_usage_help_flag-2.snap | 10 +++++----- tests/snapshots/ui__ui_test_usage_help_flag.snap | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 79cc47e..5aaf96b 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -25,15 +25,15 @@ pub struct CliArgs { #[arg(short = 'A', long, env = "ACCESSIBLE", global = true)] pub accessible: bool, - /// Ignores hidden files + /// Ignore hidden files #[arg(short = 'H', long, global = true)] pub hidden: bool, - /// Silences output + /// Silence output #[arg(short = 'q', long, global = true)] pub quiet: bool, - /// Ignores files matched by git's ignore files + /// Ignore files matched by git's ignore files #[arg(short = 'g', long, global = true)] pub gitignore: bool, @@ -41,11 +41,11 @@ pub struct CliArgs { #[arg(short, long, global = true)] pub format: Option, - /// decompress or list with password + /// Decompress or list with password #[arg(short = 'p', long = "password", global = true)] pub password: Option, - /// concurrent working threads + /// Concurrent working threads #[arg(short = 'c', long, global = true)] pub threads: Option, diff --git a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap index 66cc156..6e6818d 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap @@ -17,11 +17,11 @@ Options: -y, --yes Skip [Y/n] questions positively -n, --no Skip [Y/n] questions negatively -A, --accessible Activate accessibility mode, reducing visual noise [env: ACCESSIBLE=] - -H, --hidden Ignores hidden files - -q, --quiet Silences output - -g, --gitignore Ignores files matched by git's ignore files + -H, --hidden Ignore hidden files + -q, --quiet Silence output + -g, --gitignore Ignore files matched by git's ignore files -f, --format Specify the format of the archive - -p, --password decompress or list with password - -c, --threads concurrent working threads + -p, --password Decompress or list with password + -c, --threads Concurrent working threads -h, --help Print help (see more with '--help') -V, --version Print version diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap index 370193d..dfab4dc 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -30,22 +30,22 @@ Options: [env: ACCESSIBLE=] -H, --hidden - Ignores hidden files + Ignore hidden files -q, --quiet - Silences output + Silence output -g, --gitignore - Ignores files matched by git's ignore files + Ignore files matched by git's ignore files -f, --format Specify the format of the archive -p, --password - decompress or list with password + Decompress or list with password -c, --threads - concurrent working threads + Concurrent working threads -h, --help Print help (see a summary with '-h') From 58271ab77fa49772450a63118f45806c128dd752 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Wed, 12 Feb 2025 11:06:03 +0100 Subject: [PATCH 068/101] refactor(cli): Clearer docs for -y/-n --- src/cli/args.rs | 4 ++-- tests/snapshots/ui__ui_test_usage_help_flag-2.snap | 4 ++-- tests/snapshots/ui__ui_test_usage_help_flag.snap | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 5aaf96b..22b8372 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -13,11 +13,11 @@ use clap::{Parser, ValueHint}; // Disable rustdoc::bare_urls because rustdoc parses URLs differently than Clap #[allow(rustdoc::bare_urls)] pub struct CliArgs { - /// Skip [Y/n] questions positively + /// Skip [Y/n] questions, default to yes #[arg(short, long, conflicts_with = "no", global = true)] pub yes: bool, - /// Skip [Y/n] questions negatively + /// Skip [Y/n] questions, default to no #[arg(short, long, global = true)] pub no: bool, diff --git a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap index 6e6818d..75a873e 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag-2.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag-2.snap @@ -14,8 +14,8 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -y, --yes Skip [Y/n] questions positively - -n, --no Skip [Y/n] questions negatively + -y, --yes Skip [Y/n] questions, default to yes + -n, --no Skip [Y/n] questions, default to no -A, --accessible Activate accessibility mode, reducing visual noise [env: ACCESSIBLE=] -H, --hidden Ignore hidden files -q, --quiet Silence output diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap index dfab4dc..e807565 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -19,10 +19,10 @@ Commands: Options: -y, --yes - Skip [Y/n] questions positively + Skip [Y/n] questions, default to yes -n, --no - Skip [Y/n] questions negatively + Skip [Y/n] questions, default to no -A, --accessible Activate accessibility mode, reducing visual noise From ecc05cdd60dc7e180ddf10a2e6a3c6bcea475321 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Wed, 12 Feb 2025 14:47:39 +0100 Subject: [PATCH 069/101] feat: Add support for Brotli Using https://crates.io/crates/brotli/7.0.0 #203 --- CHANGELOG.md | 1 + Cargo.lock | 37 +++++++++++++++++++ Cargo.toml | 1 + README.md | 6 +-- src/cli/args.rs | 2 +- src/commands/compress.rs | 8 +++- src/commands/decompress.rs | 3 +- src/commands/list.rs | 3 +- src/extension.rs | 5 +++ tests/integration.rs | 1 + .../ui__ui_test_usage_help_flag.snap | 2 +- 11 files changed, 61 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e44b061..ab5fda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Categories Used: - Add multithreading support for `zstd` compression [\#689](https://github.com/ouch-org/ouch/pull/689) ([nalabrie](https://github.com/nalabrie)) - Add `bzip3` support [\#522](https://github.com/ouch-org/ouch/pull/522) ([freijon](https://github.com/freijon)) - Add `--remove` flag for decompression subcommand to remove files after successful decompression [\#757](https://github.com/ouch-org/ouch/pull/757) ([ttys3](https://github.com/ttys3)) +- Add `br` (Brotli) support [\#765](https://github.com/ouch-org/ouch/pull/765) ([killercup](https://github.com/killercup)) ### Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index 83712b5..5fad15b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "anstream" version = "0.6.18" @@ -198,6 +213,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "1.11.0" @@ -1032,6 +1068,7 @@ version = "0.5.1" dependencies = [ "assert_cmd", "atty", + "brotli", "bstr", "bytesize", "bzip2", diff --git a/Cargo.toml b/Cargo.toml index f9f602d..4192a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ description = "A command-line utility for easily compressing and decompressing f [dependencies] atty = "0.2.14" +brotli = "7.0.0" bstr = { version = "1.10.0", default-features = false, features = ["std"] } bytesize = "1.3.0" bzip2 = "0.4.4" diff --git a/README.md b/README.md index 70caede..ae34414 100644 --- a/README.md +++ b/README.md @@ -111,9 +111,9 @@ Output: # Supported formats -| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | -|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | +| Format | `.tar` | `.zip` | `7z` | `.gz` | `.xz`, `.lzma` | `.bz`, `.bz2` | `.bz3` | `.lz4` | `.sz` (Snappy) | `.zst` | `.rar` | `.br` | +|:---------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +| Supported | ✓ | ✓¹ | ✓¹ | ✓² | ✓ | ✓ | ✓ | ✓ | ✓² | ✓² | ✓³ | ✓ | ✓: Supports compression and decompression. diff --git a/src/cli/args.rs b/src/cli/args.rs index 22b8372..5701f4e 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, bz3, lz4, sz (Snappy), zst and rar. +/// Supported formats: tar, zip, gz, 7z, xz/lzma, bz/bz2, bz3, lz4, sz (Snappy), zst, rar and br. /// /// Repository: https://github.com/ouch-org/ouch #[derive(Parser, Debug, PartialEq)] diff --git a/src/commands/compress.rs b/src/commands/compress.rs index 180bb2e..59987b8 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -83,6 +83,12 @@ pub fn compress_files( zstd_encoder.multithread(num_cpus::get_physical() as u32)?; Box::new(zstd_encoder.auto_finish()) } + Brotli => { + let default_level = 11; // Same as brotli CLI, default to highest compression + let level = level.unwrap_or(default_level).clamp(0, 11) as u32; + let win_size = 22; // default to 2^22 = 4 MiB window size + Box::new(brotli::CompressorWriter::new(encoder, BUFFER_CAPACITY, level, win_size)) + } Tar | Zip | Rar | SevenZip => unreachable!(), }; Ok(encoder) @@ -95,7 +101,7 @@ pub fn compress_files( } match first_format { - Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { + Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { writer = chain_writer_encoder(&first_format, writer)?; let mut reader = fs::File::open(&files[0])?; diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 1d0152a..6d2efee 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -119,6 +119,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), 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)), Tar | Zip | Rar | SevenZip => unreachable!(), }; Ok(decoder) @@ -131,7 +132,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { } let files_unpacked = match first_extension { - Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { + Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { reader = chain_reader_decoder(&first_extension, reader)?; let mut writer = match utils::ask_to_create_file(&options.output_file_path, options.question_policy)? { diff --git a/src/commands/list.rs b/src/commands/list.rs index 1821d6d..a64c55c 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -55,6 +55,7 @@ pub fn list_archive_contents( Lzma => Box::new(xz2::read::XzDecoder::new(decoder)), 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)), Tar | Zip | Rar | SevenZip => unreachable!(), }; Ok(decoder) @@ -112,7 +113,7 @@ pub fn list_archive_contents( Box::new(sevenz::list_archive(archive_path, password)?) } - Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd => { + Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { 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 f8d7389..7250a57 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -21,6 +21,7 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[ #[cfg(feature = "unrar")] "rar", "7z", + "br", ]; pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst"]; @@ -96,6 +97,8 @@ pub enum CompressionFormat { Rar, /// .7z SevenZip, + /// .br + Brotli, } impl CompressionFormat { @@ -111,6 +114,7 @@ impl CompressionFormat { Lzma => false, Snappy => false, Zstd => false, + Brotli => false, } } } @@ -136,6 +140,7 @@ fn to_extension(ext: &[u8]) -> Option { b"zst" => &[Zstd], b"rar" => &[Rar], b"7z" => &[SevenZip], + b"br" => &[Brotli], _ => return None, }, ext.to_str_lossy(), diff --git a/tests/integration.rs b/tests/integration.rs index 779766a..7241cd1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -44,6 +44,7 @@ enum FileExtension { Sz, Xz, Zst, + Br, } #[derive(Arbitrary, Debug, Display)] diff --git a/tests/snapshots/ui__ui_test_usage_help_flag.snap b/tests/snapshots/ui__ui_test_usage_help_flag.snap index e807565..6913b83 100644 --- a/tests/snapshots/ui__ui_test_usage_help_flag.snap +++ b/tests/snapshots/ui__ui_test_usage_help_flag.snap @@ -5,7 +5,7 @@ snapshot_kind: text --- 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 and rar. +Supported formats: tar, zip, gz, 7z, xz/lzma, bz/bz2, bz3, lz4, sz (Snappy), zst, rar and br. Repository: https://github.com/ouch-org/ouch From 27e727ced3ca12f7e5665b68e7cb4c46021b8331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Fri, 28 Feb 2025 23:39:26 -0300 Subject: [PATCH 070/101] update dependencies --- Cargo.lock | 404 +++++++++++++++++++++++++++++------------------------ 1 file changed, 219 insertions(+), 185 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fad15b..1524148 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,11 +84,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] @@ -153,15 +154,6 @@ dependencies = [ "which", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.6.0" @@ -172,10 +164,13 @@ dependencies = [ ] [[package]] -name = "bit-vec" -version = "0.6.3" +name = "bit-set" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] [[package]] name = "bit-vec" @@ -183,6 +178,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -191,9 +192,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -236,9 +237,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", @@ -247,9 +248,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" @@ -259,15 +260,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bytesize" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" [[package]] name = "bzip2" @@ -281,12 +282,11 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", @@ -350,9 +350,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "num-traits", ] @@ -380,9 +380,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -402,36 +402,36 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.38" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" +checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" -version = "0.2.24" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" +checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a" dependencies = [ "clap", "roff", @@ -445,14 +445,14 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -463,9 +463,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core_affinity" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622892f5635ce1fc38c8f16dfc938553ed64af482edb5e150bf4caedbfcb2304" +checksum = "38a2a2d065bbdbcdeedfcfef1104cb56441a0a72650cf0d34089a9965ea469ff" dependencies = [ "libc", "num_cpus", @@ -474,9 +474,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -507,9 +507,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -574,15 +574,15 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "errno" @@ -596,9 +596,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" @@ -625,9 +625,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "libz-sys", @@ -693,21 +693,33 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] -name = "glob" +name = "getrandom" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", @@ -765,11 +777,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -799,9 +811,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array", @@ -809,13 +821,14 @@ dependencies = [ [[package]] name = "insta" -version = "1.41.1" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" dependencies = [ "console", - "lazy_static", "linked-hash-map", + "once_cell", + "pin-project", "regex", "similar", ] @@ -846,9 +859,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -880,9 +893,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libloading" @@ -894,28 +907,22 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "redox_syscall", ] [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "libc", @@ -931,9 +938,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -947,9 +954,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "lz4_flex" @@ -994,9 +1001,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -1007,7 +1014,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -1043,7 +1050,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1058,9 +1064,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "ouch" @@ -1129,7 +1135,7 @@ dependencies = [ "regex", "regex-syntax", "structmeta", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1163,22 +1169,22 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1204,9 +1210,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", @@ -1215,15 +1221,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -1231,22 +1237,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ - "bit-set 0.5.3", - "bit-vec 0.6.3", - "bitflags 2.6.0", + "bit-set 0.8.0", + "bit-vec 0.8.0", + "bitflags 2.8.0", "lazy_static", "num-traits", "rand", @@ -1266,9 +1272,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1300,7 +1306,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -1334,11 +1340,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -1384,17 +1390,23 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -1424,22 +1436,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1492,9 +1504,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "snap" @@ -1532,7 +1544,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1543,7 +1555,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1565,9 +1577,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1576,9 +1588,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ "filetime", "libc", @@ -1587,12 +1599,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1600,9 +1613,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "test-strategy" @@ -1613,7 +1626,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1633,7 +1646,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1678,9 +1691,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unarray" @@ -1690,17 +1703,17 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "unrar" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621a1f538dffc544a8a35ca32140d081908b4f5999ee9886ff0c5121d6bc86a5" +checksum = "92ec61343a630d2b50d13216dea5125e157d3fc180a7d3f447d22fe146b648fc" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", "regex", "unrar_sys", "widestring", @@ -1708,9 +1721,9 @@ dependencies = [ [[package]] name = "unrar_sys" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8325103479fffa0e31b41fd11267446b355037115ae184a63a9fd3f192f3da" +checksum = "8b77675b883cfbe6bf41e6b7a5cd6008e0a83ba497de3d96e41a064bbeead765" dependencies = [ "cc", "libc", @@ -1725,9 +1738,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" [[package]] name = "vcpkg" @@ -1743,9 +1756,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -1767,36 +1780,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.97" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1804,22 +1826,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "which" @@ -1953,10 +1978,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "xattr" -version = "1.3.1" +name = "wit-bindgen-rt" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "xattr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys", @@ -1990,7 +2024,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2013,27 +2047,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", From 31dd9eb9234d42205e6984b6dea568085c6fd792 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Sun, 30 Mar 2025 19:19:21 -0300 Subject: [PATCH 071/101] feat: Add rename option in overwrite menu (#779) * feat: Add generic Choice prompt implementation * feat: use ChoicePrompt in user_wants_to_continue method * feat: add "rename" option in ask_to_create_file method * feat: check accessible mode for choises prompt * feat: rename file in "ask_to_create_file" rename action * feat: create "resolve_path" for smart_unpack to deal with rename * feat: use resolve_path instead clear_path in smart_unpack * fix: remove unused clear_path function * chore: cargo fmt * Add docs * refactor: rename "resolve_path" method * chore: fix ChoicePrompt doc * doc: improve doc of resolve_path_conflict * fix: out of bound when type answer bigger than some choice * doc: improve rename_path docs * chore: cargo fmt * chore: revert user_wants_to_continue * fix: update error message when find EOF in choise prompt response * revert: update message error in ChoicePrompt instead Confirmation * test: add overwrite and cancel tests * test: Add rename test with "allow_piped_choice" new feature * cargo fmt * test: create test for autoincrement new renamed files --- Cargo.toml | 1 + src/commands/decompress.rs | 10 ++- src/utils/fs.rs | 67 ++++++++++++--- src/utils/mod.rs | 9 +- src/utils/question.rs | 169 ++++++++++++++++++++++++++++++++++--- tests/integration.rs | 169 +++++++++++++++++++++++++++++++++++++ 6 files changed, 393 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4192a77..a9f3ba8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ test-strategy = "0.4.0" default = ["use_zlib", "use_zstd_thin", "unrar"] use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-zlib"] use_zstd_thin = ["zstd/thin"] +allow_piped_choice = [] # For generating binaries for releases [profile.release] diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 6d2efee..b14dc3c 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -309,7 +309,7 @@ fn smart_unpack( let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.count() == 1; - let (previous_path, new_path) = if root_contains_only_one_element { + let (previous_path, mut new_path) = if root_contains_only_one_element { // Only one file in the root directory, so we can just move it to the output directory let file = fs::read_dir(temp_dir_path)?.next().expect("item exists")?; let file_path = file.path(); @@ -324,9 +324,11 @@ fn smart_unpack( }; // Before moving, need to check if a file with the same name already exists - if !utils::clear_path(&new_path, question_policy)? { - return Ok(ControlFlow::Break(())); - } + // If it does, need to ask the user what to do + new_path = match utils::resolve_path_conflict(&new_path, question_policy)? { + Some(path) => path, + None => return Ok(ControlFlow::Break(())), + }; // Rename the temporary directory to the archive name, which is output_file_path fs::rename(&previous_path, &new_path)?; diff --git a/src/utils/fs.rs b/src/utils/fs.rs index d33411a..c6ffb8a 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -8,7 +8,7 @@ use std::{ use fs_err as fs; -use super::user_wants_to_overwrite; +use super::{question::FileConflitOperation, user_wants_to_overwrite}; use crate::{ extension::Extension, utils::{logger::info_accessible, EscapedPathDisplay}, @@ -19,19 +19,29 @@ pub fn is_path_stdin(path: &Path) -> bool { path.as_os_str() == "-" } -/// Remove `path` asking the user to overwrite if necessary. +/// Check if &Path exists, if it does then ask the user if they want to overwrite or rename it. +/// If the user want to overwrite then the file or directory will be removed and returned the same input path +/// If the user want to rename then nothing will be removed and a new path will be returned with a new name /// -/// * `Ok(true)` means the path is clear, -/// * `Ok(false)` means the user doesn't want to overwrite +/// * `Ok(None)` means the user wants to cancel the operation +/// * `Ok(Some(path))` returns a valid PathBuf without any another file or directory with the same name /// * `Err(_)` is an error -pub fn clear_path(path: &Path, question_policy: QuestionPolicy) -> crate::Result { - if path.exists() && !user_wants_to_overwrite(path, question_policy)? { - return Ok(false); +pub fn resolve_path_conflict(path: &Path, question_policy: QuestionPolicy) -> crate::Result> { + if path.exists() { + match user_wants_to_overwrite(path, question_policy)? { + FileConflitOperation::Cancel => Ok(None), + FileConflitOperation::Overwrite => { + remove_file_or_dir(path)?; + Ok(Some(path.to_path_buf())) + } + FileConflitOperation::Rename => { + let renamed_path = rename_for_available_filename(path); + Ok(Some(renamed_path)) + } + } + } else { + Ok(Some(path.to_path_buf())) } - - remove_file_or_dir(path)?; - - Ok(true) } pub fn remove_file_or_dir(path: &Path) -> crate::Result<()> { @@ -43,6 +53,41 @@ pub fn remove_file_or_dir(path: &Path) -> crate::Result<()> { Ok(()) } +/// Create a new path renaming the "filename" from &Path for a available name in the same directory +pub fn rename_for_available_filename(path: &Path) -> PathBuf { + let mut renamed_path = rename_or_increment_filename(path); + while renamed_path.exists() { + renamed_path = rename_or_increment_filename(&renamed_path); + } + renamed_path +} + +/// Create a new path renaming the "filename" from &Path to `filename_1` +/// if its name already ends with `_` and some number, then it increments the number +/// Example: +/// - `file.txt` -> `file_1.txt` +/// - `file_1.txt` -> `file_2.txt` +pub fn rename_or_increment_filename(path: &Path) -> PathBuf { + let parent = path.parent().unwrap_or_else(|| Path::new("")); + let filename = path.file_stem().and_then(|s| s.to_str()).unwrap_or(""); + let extension = path.extension().and_then(|s| s.to_str()).unwrap_or(""); + + let new_filename = match filename.rsplit_once('_') { + Some((base, number_str)) if number_str.chars().all(char::is_numeric) => { + let number = number_str.parse::().unwrap_or(0); + format!("{}_{}", base, number + 1) + } + _ => format!("{}_1", filename), + }; + + let mut new_path = parent.join(new_filename); + if !extension.is_empty() { + new_path.set_extension(extension); + } + + new_path +} + /// Creates a directory at the path, if there is nothing there. pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> { if !path.exists() { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c153689..444cf1f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -18,10 +18,13 @@ pub use self::{ EscapedPathDisplay, }, fs::{ - cd_into_same_dir_as, clear_path, create_dir_if_non_existent, is_path_stdin, remove_file_or_dir, - try_infer_extension, + cd_into_same_dir_as, create_dir_if_non_existent, is_path_stdin, remove_file_or_dir, + rename_for_available_filename, resolve_path_conflict, try_infer_extension, + }, + question::{ + ask_to_create_file, user_wants_to_continue, user_wants_to_overwrite, FileConflitOperation, QuestionAction, + QuestionPolicy, }, - question::{ask_to_create_file, user_wants_to_continue, user_wants_to_overwrite, QuestionAction, QuestionPolicy}, utf8::{get_invalid_utf8_paths, is_invalid_utf8}, }; diff --git a/src/utils/question.rs b/src/utils/question.rs index ee36e74..27eb7bb 100644 --- a/src/utils/question.rs +++ b/src/utils/question.rs @@ -37,31 +37,69 @@ pub enum QuestionAction { Decompression, } +#[derive(Default)] +/// Determines which action to do when there is a file conflict +pub enum FileConflitOperation { + #[default] + /// Cancel the operation + Cancel, + /// Overwrite the existing file with the new one + Overwrite, + /// Rename the file + /// It'll be put "_1" at the end of the filename or "_2","_3","_4".. if already exists + Rename, +} + /// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite. -pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result { +pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result { + use FileConflitOperation as Op; + match question_policy { - QuestionPolicy::AlwaysYes => Ok(true), - QuestionPolicy::AlwaysNo => Ok(false), - QuestionPolicy::Ask => { - let path = path_to_str(strip_cur_dir(path)); - let path = Some(&*path); - let placeholder = Some("FILE"); - Confirmation::new("Do you want to overwrite 'FILE'?", placeholder).ask(path) - } + QuestionPolicy::AlwaysYes => Ok(Op::Overwrite), + QuestionPolicy::AlwaysNo => Ok(Op::Cancel), + QuestionPolicy::Ask => ask_file_conflict_operation(path), } } +/// Ask the user if they want to overwrite or rename the &Path +pub fn ask_file_conflict_operation(path: &Path) -> Result { + use FileConflitOperation as Op; + + let path = path_to_str(strip_cur_dir(path)); + + ChoicePrompt::new( + format!("Do you want to overwrite {path}?"), + [ + ("yes", Op::Overwrite, *colors::GREEN), + ("no", Op::Cancel, *colors::RED), + ("rename", Op::Rename, *colors::BLUE), + ], + ) + .ask() +} + /// Create the file if it doesn't exist and if it does then ask to overwrite it. /// If the user doesn't want to overwrite then we return [`Ok(None)`] pub fn ask_to_create_file(path: &Path, question_policy: QuestionPolicy) -> Result> { match fs::OpenOptions::new().write(true).create_new(true).open(path) { Ok(w) => Ok(Some(w)), Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { - if user_wants_to_overwrite(path, question_policy)? { - utils::remove_file_or_dir(path)?; - Ok(Some(fs::File::create(path)?)) - } else { - Ok(None) + let action = match question_policy { + QuestionPolicy::AlwaysYes => FileConflitOperation::Overwrite, + QuestionPolicy::AlwaysNo => FileConflitOperation::Cancel, + QuestionPolicy::Ask => ask_file_conflict_operation(path)?, + }; + + match action { + FileConflitOperation::Overwrite => { + utils::remove_file_or_dir(path)?; + Ok(Some(fs::File::create(path)?)) + } + FileConflitOperation::Cancel => Ok(None), + FileConflitOperation::Rename => { + let renamed_file_path = utils::rename_for_available_filename(path); + Ok(Some(fs::File::create(renamed_file_path)?)) + } } } Err(e) => Err(Error::from(e)), @@ -90,6 +128,108 @@ pub fn user_wants_to_continue( } } +/// Choise dialog for end user with [option1/option2/...] question. +/// Each option is a [Choice] entity, holding a value "T" returned when that option is selected +pub struct ChoicePrompt<'a, T: Default> { + /// The message to be displayed before the options + /// e.g.: "Do you want to overwrite 'FILE'?" + pub prompt: String, + + pub choises: Vec>, +} + +/// A single choice showed as a option to user in a [ChoicePrompt] +/// It holds a label and a color to display to user and a real value to be returned +pub struct Choice<'a, T: Default> { + label: &'a str, + value: T, + color: &'a str, +} + +impl<'a, T: Default> ChoicePrompt<'a, T> { + /// Creates a new Confirmation. + pub fn new(prompt: impl Into, choises: impl IntoIterator) -> Self { + Self { + prompt: prompt.into(), + choises: choises + .into_iter() + .map(|(label, value, color)| Choice { label, value, color }) + .collect(), + } + } + + /// Creates user message and receives a input to be compared with choises "label" + /// and returning the real value of the choise selected + pub fn ask(mut self) -> crate::Result { + let message = self.prompt; + + #[cfg(not(feature = "allow_piped_choice"))] + if !stdin().is_terminal() { + eprintln!("{}", message); + eprintln!("Pass --yes to proceed"); + return Ok(T::default()); + } + + let _locks = lock_and_flush_output_stdio()?; + let mut stdin_lock = stdin().lock(); + + // Ask the same question to end while no valid answers are given + loop { + let choice_prompt = if is_running_in_accessible_mode() { + self.choises + .iter() + .map(|choise| format!("{}{}{}", choise.color, choise.label, *colors::RESET)) + .collect::>() + .join("/") + } else { + let choises = self + .choises + .iter() + .map(|choise| { + format!( + "{}{}{}", + choise.color, + choise + .label + .chars() + .nth(0) + .expect("dev error, should be reported, we checked this won't happen"), + *colors::RESET + ) + }) + .collect::>() + .join("/"); + + format!("[{}]", choises) + }; + + eprintln!("{} {}", message, choice_prompt); + + let mut answer = String::new(); + let bytes_read = stdin_lock.read_line(&mut answer)?; + + if bytes_read == 0 { + let error = FinalError::with_title("Unexpected EOF when asking question.") + .detail("When asking the user:") + .detail(format!(" \"{message}\"")) + .detail("Expected one of the options as answer, but found EOF instead.") + .hint("If using Ouch in scripting, consider using `--yes` and `--no`."); + + return Err(error.into()); + } + + answer.make_ascii_lowercase(); + let answer = answer.trim(); + + let chosen_index = self.choises.iter().position(|choise| choise.label.starts_with(answer)); + + if let Some(i) = chosen_index { + return Ok(self.choises.remove(i).value); + } + } + } +} + /// Confirmation dialog for end user with [Y/n] question. /// /// If the placeholder is found in the prompt text, it will be replaced to form the final message. @@ -120,6 +260,7 @@ impl<'a> Confirmation<'a> { (Some(placeholder), Some(subs)) => Cow::Owned(self.prompt.replace(placeholder, subs)), }; + #[cfg(not(feature = "allow_piped_choice"))] if !stdin().is_terminal() { eprintln!("{}", message); eprintln!("Pass --yes to proceed"); diff --git a/tests/integration.rs b/tests/integration.rs index 7241cd1..0ff7e1d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -194,6 +194,175 @@ fn multiple_files( assert_same_directory(before, after, !matches!(ext, DirectoryExtension::Zip)); } +#[proptest(cases = 25)] +fn multiple_files_with_conflict_and_choice_to_overwrite( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, + #[strategy(0u8..3)] depth: u8, +) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + + let before = &dir.join("before"); + let before_dir = &before.join("dir"); + fs::create_dir_all(before_dir).unwrap(); + create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + + let after = &dir.join("after"); + let after_dir = &after.join("dir"); + fs::create_dir_all(after_dir).unwrap(); + create_random_files(after_dir, depth, &mut SmallRng::from_entropy()); + + let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + ouch!("-A", "c", before_dir, archive); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(after) + .arg("--yes") + .assert() + .success(); + + assert_same_directory(before, after, false); +} + +#[proptest(cases = 25)] +fn multiple_files_with_conflict_and_choice_to_not_overwrite( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, + #[strategy(0u8..3)] depth: u8, +) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + + let before = &dir.join("before"); + let before_dir = &before.join("dir"); + fs::create_dir_all(before_dir).unwrap(); + create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + + let after = &dir.join("after"); + let after_dir = &after.join("dir"); + fs::create_dir_all(after_dir).unwrap(); + + let after_backup = &dir.join("after_backup"); + let after_backup_dir = &after_backup.join("dir"); + fs::create_dir_all(after_backup_dir).unwrap(); + + // Create a file with the same name as one of the files in the after directory + fs::write(after_dir.join("something.txt"), "Some content").unwrap(); + fs::copy(after_dir.join("something.txt"), after_backup_dir.join("something.txt")).unwrap(); + + let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + ouch!("-A", "c", before_dir, archive); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(after) + .arg("--no") + .assert() + .success(); + + assert_same_directory(after, after_backup, false); +} + +#[cfg(feature = "allow_piped_choice")] +#[proptest(cases = 25)] +fn multiple_files_with_conflict_and_choice_to_rename( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, + #[strategy(0u8..3)] depth: u8, +) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + + let before = &dir.join("before"); + let before_dir = &before.join("dir"); + fs::create_dir_all(before_dir).unwrap(); + create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + + let after = &dir.join("after"); + let after_dir = &after.join("dir"); + fs::create_dir_all(after_dir).unwrap(); + create_random_files(after_dir, depth, &mut SmallRng::from_entropy()); + + let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + ouch!("-A", "c", before_dir, archive); + + let after_renamed_dir = &after.join("dir_1"); + assert_eq!(false, after_renamed_dir.exists()); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(after) + .write_stdin("r") + .assert() + .success(); + + assert_same_directory(before_dir, after_renamed_dir, false); +} + +#[cfg(feature = "allow_piped_choice")] +#[proptest(cases = 25)] +fn multiple_files_with_conflict_and_choice_to_rename_with_already_a_renamed( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, + #[strategy(0u8..3)] depth: u8, +) { + let dir = tempdir().unwrap(); + let dir = dir.path(); + + let before = &dir.join("before"); + let before_dir = &before.join("dir"); + fs::create_dir_all(before_dir).unwrap(); + create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + + let after = &dir.join("after"); + let after_dir = &after.join("dir"); + fs::create_dir_all(after_dir).unwrap(); + create_random_files(after_dir, depth, &mut SmallRng::from_entropy()); + + let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + ouch!("-A", "c", before_dir, archive); + + let already_renamed_dir = &after.join("dir_1"); + fs::create_dir_all(already_renamed_dir).unwrap(); + create_random_files(already_renamed_dir, depth, &mut SmallRng::from_entropy()); + + let after_real_renamed_dir = &after.join("dir_2"); + assert_eq!(false, after_real_renamed_dir.exists()); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(after) + .write_stdin("r") + .assert() + .success(); + + assert_same_directory(before_dir, after_real_renamed_dir, false); + + let after_another_real_renamed_dir = &after.join("dir_3"); + assert_eq!(false, after_another_real_renamed_dir.exists()); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(after) + .write_stdin("r") + .assert() + .success(); + + assert_same_directory(before_dir, after_another_real_renamed_dir, false); +} + #[cfg(feature = "unrar")] #[test] fn unpack_rar() -> Result<(), Box> { From 82c551ddef444e67c4565571be46c7405984c9dd Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:41:41 -0300 Subject: [PATCH 072/101] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab5fda3..4d9b661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Categories Used: - Add `bzip3` support [\#522](https://github.com/ouch-org/ouch/pull/522) ([freijon](https://github.com/freijon)) - Add `--remove` flag for decompression subcommand to remove files after successful decompression [\#757](https://github.com/ouch-org/ouch/pull/757) ([ttys3](https://github.com/ttys3)) - Add `br` (Brotli) support [\#765](https://github.com/ouch-org/ouch/pull/765) ([killercup](https://github.com/killercup)) +- Add rename option in overwrite menu [\#779](https://github.com/ouch-org/ouch/pull/779) ([talis-fb](https://github.com/talis-fb)) ### Bug Fixes From 4f9a786e578b4aa7be578ef30bdcbf44a8ede09f Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Mon, 31 Mar 2025 08:56:25 -0300 Subject: [PATCH 073/101] feat: add flag to check when use specify --dir --- src/commands/decompress.rs | 49 ++++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 2 ++ 2 files changed, 51 insertions(+) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index b14dc3c..9e5da10 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -31,6 +31,7 @@ pub struct DecompressOptions<'a> { pub formats: Vec, pub output_dir: &'a Path, pub output_file_path: PathBuf, + pub is_output_dir_explicit: bool, pub question_policy: QuestionPolicy, pub quiet: bool, pub password: Option<&'a [u8]>, @@ -73,6 +74,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, + options.is_output_dir_explicit, )? { files } else { @@ -150,6 +152,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, + options.is_output_dir_explicit, )? { files } else { @@ -183,6 +186,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, + options.is_output_dir_explicit, )? { files } else { @@ -214,6 +218,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, + options.is_output_dir_explicit, )? { files } else { @@ -255,6 +260,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, + options.is_output_dir_explicit, )? { files } else { @@ -284,6 +290,48 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { Ok(()) } +fn execute_decompression( + unpack_fn: impl FnOnce(&Path) -> crate::Result, + output_dir: &Path, + output_file_path: &Path, + question_policy: QuestionPolicy, + is_output_dir_explicit: bool, +) -> crate::Result> { + if is_output_dir_explicit { + return unpack(unpack_fn, output_dir, question_policy); + } else { + return smart_unpack( + unpack_fn, + output_dir, + output_file_path, + question_policy, + ); + } +} + +fn unpack( + unpack_fn: impl FnOnce(&Path) -> crate::Result, + output_dir: &Path, + question_policy: QuestionPolicy, +) -> crate::Result> { + let has_files = output_dir.exists() && output_dir.read_dir().map(|dir| dir.count() > 0).unwrap_or(false); + + let output_dir_cleaned = if has_files { + let output_file_path = utils::resolve_path_conflict(&output_dir, question_policy)?.unwrap(); + output_file_path + } else { + output_dir.to_owned() + }; + + if !output_dir_cleaned.exists() { + fs::create_dir(&output_dir_cleaned)?; + } + + let files = unpack_fn(&output_dir_cleaned)?; + + Ok(ControlFlow::Continue(files)) +} + /// Unpacks an archive with some heuristics /// - If the archive contains only one file, it will be extracted to the `output_dir` /// - If the archive contains multiple files, it will be extracted to a subdirectory of the @@ -295,6 +343,7 @@ fn smart_unpack( output_dir: &Path, output_file_path: &Path, question_policy: QuestionPolicy, + is_output_dir_explicit: bool, ) -> crate::Result> { assert!(output_dir.exists()); let temp_dir = tempfile::Builder::new().prefix(".tmp-ouch-").tempdir_in(output_dir)?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0bcb9b3..a0aaa26 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -176,6 +176,7 @@ pub fn run( // 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_explicit = output_dir.is_some(); let output_dir = if let Some(dir) = output_dir { utils::create_dir_if_non_existent(&dir)?; dir @@ -199,6 +200,7 @@ pub fn run( formats, output_dir: &output_dir, output_file_path, + is_output_dir_explicit, question_policy, quiet: args.quiet, password: args.password.as_deref().map(|str| { From f65444d2fbd87b0766a780aa58dc9d2c75131918 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Mon, 31 Mar 2025 09:07:29 -0300 Subject: [PATCH 074/101] feat: add conditional smart_unpack when --dir is provided --- src/commands/decompress.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 9e5da10..b89c6f2 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -69,7 +69,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { Box::new(fs::File::open(options.input_file_path)?) }; let zip_archive = zip::ZipArchive::new(reader)?; - let files_unpacked = if let ControlFlow::Continue(files) = smart_unpack( + let files_unpacked = if let ControlFlow::Continue(files) = execute_decompression( |output_dir| crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet), options.output_dir, &options.output_file_path, @@ -147,7 +147,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { 1 } Tar => { - if let ControlFlow::Continue(files) = smart_unpack( + if let ControlFlow::Continue(files) = execute_decompression( |output_dir| crate::archive::tar::unpack_archive(reader, output_dir, options.quiet), options.output_dir, &options.output_file_path, @@ -179,7 +179,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { io::copy(&mut reader, &mut vec)?; let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?; - if let ControlFlow::Continue(files) = smart_unpack( + if let ControlFlow::Continue(files) = execute_decompression( |output_dir| { crate::archive::zip::unpack_archive(zip_archive, output_dir, options.password, options.quiet) }, @@ -213,7 +213,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { }) }; - if let ControlFlow::Continue(files) = smart_unpack( + if let ControlFlow::Continue(files) = execute_decompression( unpack_fn, options.output_dir, &options.output_file_path, @@ -248,7 +248,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { let mut vec = vec![]; io::copy(&mut reader, &mut vec)?; - if let ControlFlow::Continue(files) = smart_unpack( + if let ControlFlow::Continue(files) = execute_decompression( |output_dir| { crate::archive::sevenz::decompress_sevenz( io::Cursor::new(vec), @@ -317,8 +317,10 @@ fn unpack( let has_files = output_dir.exists() && output_dir.read_dir().map(|dir| dir.count() > 0).unwrap_or(false); let output_dir_cleaned = if has_files { - let output_file_path = utils::resolve_path_conflict(&output_dir, question_policy)?.unwrap(); - output_file_path + match utils::resolve_path_conflict(&output_dir, question_policy)? { + Some(path) => path, + None => return Ok(ControlFlow::Break(())), + } } else { output_dir.to_owned() }; @@ -343,7 +345,6 @@ fn smart_unpack( output_dir: &Path, output_file_path: &Path, question_policy: QuestionPolicy, - is_output_dir_explicit: bool, ) -> crate::Result> { assert!(output_dir.exists()); let temp_dir = tempfile::Builder::new().prefix(".tmp-ouch-").tempdir_in(output_dir)?; From 3258cbef5b30c2c56f764a5018840d6856527c75 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Mon, 31 Mar 2025 09:32:15 -0300 Subject: [PATCH 075/101] refactor: improve execute_decompression legibility --- src/commands/decompress.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index b89c6f2..d08836a 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -298,31 +298,30 @@ fn execute_decompression( is_output_dir_explicit: bool, ) -> crate::Result> { if is_output_dir_explicit { - return unpack(unpack_fn, output_dir, question_policy); + unpack(unpack_fn, output_dir, question_policy) } else { - return smart_unpack( - unpack_fn, - output_dir, - output_file_path, - question_policy, - ); + smart_unpack(unpack_fn, output_dir, output_file_path, question_policy) } } +/// Unpacks an archive creating the output directory, this function will create the output_dir +/// directory or replace it if it already exists. The `output_dir` needs to be empty +/// - If `output_dir` does not exist OR is a empty directory, it will unpack there +/// - If `output_dir` exist OR is a directory not empty, the user will be asked what to do fn unpack( unpack_fn: impl FnOnce(&Path) -> crate::Result, output_dir: &Path, question_policy: QuestionPolicy, ) -> crate::Result> { - let has_files = output_dir.exists() && output_dir.read_dir().map(|dir| dir.count() > 0).unwrap_or(false); + let is_valid_output_dir = !output_dir.exists() || (output_dir.is_dir() && output_dir.read_dir()?.count() > 0); - let output_dir_cleaned = if has_files { + let output_dir_cleaned = if is_valid_output_dir { + output_dir.to_owned() + } else { match utils::resolve_path_conflict(&output_dir, question_policy)? { Some(path) => path, None => return Ok(ControlFlow::Break(())), } - } else { - output_dir.to_owned() }; if !output_dir_cleaned.exists() { From bb22cbb7381cdd8a77df4bed54648bef2590b891 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Mon, 31 Mar 2025 09:34:40 -0300 Subject: [PATCH 076/101] fix: check valid empty directory --- src/commands/decompress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index d08836a..95d8029 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -313,7 +313,7 @@ fn unpack( output_dir: &Path, question_policy: QuestionPolicy, ) -> crate::Result> { - let is_valid_output_dir = !output_dir.exists() || (output_dir.is_dir() && output_dir.read_dir()?.count() > 0); + let is_valid_output_dir = !output_dir.exists() || (output_dir.is_dir() && output_dir.read_dir()?.count() == 0); let output_dir_cleaned = if is_valid_output_dir { output_dir.to_owned() From 8c478a3f9dc473b34240a0c1fe4cb8d1286361e6 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:16:36 -0300 Subject: [PATCH 077/101] test: fix renamed test and implement to check disabled smart_unpack --- tests/integration.rs | 134 +++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 42 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 0ff7e1d..1c40641 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,7 +1,11 @@ #[macro_use] mod utils; -use std::{iter::once, path::PathBuf}; +use std::{ + io::Write, + iter::once, + path::{Path, PathBuf}, +}; use fs_err as fs; use parse_display::Display; @@ -84,6 +88,24 @@ fn create_random_files(dir: impl Into, depth: u8, rng: &mut SmallRng) { } } +/// Create n random files on directory dir +fn create_n_random_files(n: usize, dir: impl Into, rng: &mut SmallRng) { + let dir: &PathBuf = &dir.into(); + + for _ in 0..n { + write_random_content( + &mut tempfile::Builder::new() + .prefix("file") + .tempfile_in(dir) + .unwrap() + .keep() + .unwrap() + .0, + rng, + ); + } +} + /// Compress and decompress a single empty file #[proptest(cases = 200)] fn single_empty_file(ext: Extension, #[any(size_range(0..8).lift())] exts: Vec) { @@ -274,37 +296,35 @@ fn multiple_files_with_conflict_and_choice_to_not_overwrite( fn multiple_files_with_conflict_and_choice_to_rename( ext: DirectoryExtension, #[any(size_range(0..1).lift())] extra_extensions: Vec, - #[strategy(0u8..3)] depth: u8, ) { - let dir = tempdir().unwrap(); - let dir = dir.path(); + let temp_dir = tempdir().unwrap(); + let root_path = temp_dir.path(); - let before = &dir.join("before"); - let before_dir = &before.join("dir"); - fs::create_dir_all(before_dir).unwrap(); - create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + let src_files_path = root_path.join("src_files"); + fs::create_dir_all(&src_files_path).unwrap(); + create_n_random_files(5, &src_files_path, &mut SmallRng::from_entropy()); - let after = &dir.join("after"); - let after_dir = &after.join("dir"); - fs::create_dir_all(after_dir).unwrap(); - create_random_files(after_dir, depth, &mut SmallRng::from_entropy()); + // Make destiny already filled to force a conflict + let dest_files_path = root_path.join("dest_files"); + fs::create_dir_all(&dest_files_path).unwrap(); + create_n_random_files(5, &dest_files_path, &mut SmallRng::from_entropy()); - let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); - ouch!("-A", "c", before_dir, archive); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + ouch!("-A", "c", &src_files_path, archive); - let after_renamed_dir = &after.join("dir_1"); - assert_eq!(false, after_renamed_dir.exists()); + let dest_files_path_renamed = &root_path.join("dest_files_1"); + assert_eq!(false, dest_files_path_renamed.exists()); crate::utils::cargo_bin() .arg("decompress") .arg(archive) .arg("-d") - .arg(after) + .arg(&dest_files_path) .write_stdin("r") .assert() .success(); - assert_same_directory(before_dir, after_renamed_dir, false); + assert_same_directory(src_files_path, dest_files_path_renamed.join("src_files"), false); } #[cfg(feature = "allow_piped_choice")] @@ -312,55 +332,85 @@ fn multiple_files_with_conflict_and_choice_to_rename( fn multiple_files_with_conflict_and_choice_to_rename_with_already_a_renamed( ext: DirectoryExtension, #[any(size_range(0..1).lift())] extra_extensions: Vec, - #[strategy(0u8..3)] depth: u8, ) { - let dir = tempdir().unwrap(); - let dir = dir.path(); + let temp_dir = tempdir().unwrap(); + let root_path = temp_dir.path(); - let before = &dir.join("before"); - let before_dir = &before.join("dir"); - fs::create_dir_all(before_dir).unwrap(); - create_random_files(before_dir, depth, &mut SmallRng::from_entropy()); + let src_files_path = root_path.join("src_files"); + fs::create_dir_all(&src_files_path).unwrap(); + create_n_random_files(5, &src_files_path, &mut SmallRng::from_entropy()); - let after = &dir.join("after"); - let after_dir = &after.join("dir"); - fs::create_dir_all(after_dir).unwrap(); - create_random_files(after_dir, depth, &mut SmallRng::from_entropy()); + // Make destiny already filled and destiny with '_1' + let dest_files_path = root_path.join("dest_files"); + fs::create_dir_all(&dest_files_path).unwrap(); + create_n_random_files(5, &dest_files_path, &mut SmallRng::from_entropy()); - let archive = &dir.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); - ouch!("-A", "c", before_dir, archive); + let dest_files_path_1 = root_path.join("dest_files_1"); + fs::create_dir_all(&dest_files_path_1).unwrap(); + create_n_random_files(5, &dest_files_path_1, &mut SmallRng::from_entropy()); - let already_renamed_dir = &after.join("dir_1"); - fs::create_dir_all(already_renamed_dir).unwrap(); - create_random_files(already_renamed_dir, depth, &mut SmallRng::from_entropy()); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + ouch!("-A", "c", &src_files_path, archive); - let after_real_renamed_dir = &after.join("dir_2"); - assert_eq!(false, after_real_renamed_dir.exists()); + let dest_files_path_renamed = &root_path.join("dest_files_2"); + assert_eq!(false, dest_files_path_renamed.exists()); crate::utils::cargo_bin() .arg("decompress") .arg(archive) .arg("-d") - .arg(after) + .arg(&dest_files_path) .write_stdin("r") .assert() .success(); - assert_same_directory(before_dir, after_real_renamed_dir, false); + assert_same_directory(src_files_path, dest_files_path_renamed.join("src_files"), false); +} - let after_another_real_renamed_dir = &after.join("dir_3"); - assert_eq!(false, after_another_real_renamed_dir.exists()); +#[cfg(feature = "allow_piped_choice")] +#[proptest(cases = 25)] +fn multiple_files_with_disabled_smart_unpack_by_dir( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, +) { + 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", "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(); + file.write("Some content".as_bytes()).unwrap(); + path + }) + .collect::>(); + + let dest_files_path = root_path.join("dest_files"); + fs::create_dir_all(&dest_files_path).unwrap(); + + 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(); crate::utils::cargo_bin() .arg("decompress") .arg(archive) .arg("-d") - .arg(after) + .arg(&dest_files_path) .write_stdin("r") .assert() .success(); - assert_same_directory(before_dir, after_another_real_renamed_dir, false); + assert_same_directory(src_files_path, dest_files_path, false); } #[cfg(feature = "unrar")] From 184bafc0fa712c5122eb1c1d5e86cdb38b9ac7af Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:18:19 -0300 Subject: [PATCH 078/101] test: format --- tests/integration.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 1c40641..2ec749a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,11 +1,7 @@ #[macro_use] mod utils; -use std::{ - io::Write, - iter::once, - path::{Path, PathBuf}, -}; +use std::{iter::once, path::PathBuf}; use fs_err as fs; use parse_display::Display; From 081642724efb1b82b187db3fd2d061885d1ab5df Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:19:46 -0300 Subject: [PATCH 079/101] cargo clippy --- src/commands/decompress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 95d8029..628aee6 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -318,7 +318,7 @@ fn unpack( let output_dir_cleaned = if is_valid_output_dir { output_dir.to_owned() } else { - match utils::resolve_path_conflict(&output_dir, question_policy)? { + match utils::resolve_path_conflict(output_dir, question_policy)? { Some(path) => path, None => return Ok(ControlFlow::Break(())), } From 35a3f3627ce3f264129ee61bd417efb3d834b3f4 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:21:14 -0300 Subject: [PATCH 080/101] fix: remove unnecessary allow_piped_choice check config on test --- tests/integration.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 2ec749a..7a5e549 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,7 +1,7 @@ #[macro_use] mod utils; -use std::{iter::once, path::PathBuf}; +use std::{io::Write, iter::once, path::PathBuf}; use fs_err as fs; use parse_display::Display; @@ -363,7 +363,6 @@ 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); } -#[cfg(feature = "allow_piped_choice")] #[proptest(cases = 25)] fn multiple_files_with_disabled_smart_unpack_by_dir( ext: DirectoryExtension, @@ -380,7 +379,7 @@ fn multiple_files_with_disabled_smart_unpack_by_dir( .map(|f| src_files_path.join(f)) .map(|path| { let mut file = fs::File::create(&path).unwrap(); - file.write("Some content".as_bytes()).unwrap(); + file.write_all("Some content".as_bytes()).unwrap(); path }) .collect::>(); From 8c8d00cab8666204ece68b30314582112c833d0b Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:39:37 -0300 Subject: [PATCH 081/101] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d9b661..4788a9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Categories Used: - Support decompression over stdin [\#692](https://github.com/ouch-org/ouch/pull/692) ([rcorre](https://github.com/rcorre)) - Make `--format` more forgiving with the formatting of the provided format [\#519](https://github.com/ouch-org/ouch/pull/519) ([marcospb19](https://github.com/marcospb19)) - Use buffered writer for list output [\#764](https://github.com/ouch-org/ouch/pull/764) ([killercup](https://github.com/killercup)) +- Disable smart unpack when `--dir` flag is provided in decompress command [\#782](https://github.com/ouch-org/ouch/pull/782) ([talis-fb](https://github.com/talis-fb)) ## [0.5.1](https://github.com/ouch-org/ouch/compare/0.5.0...0.5.1) From 3bf6aaa810327793b39eb068b10d8d474266a843 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:57:35 -0300 Subject: [PATCH 082/101] chore: improvements --- src/commands/decompress.rs | 18 +++++++++--------- src/commands/mod.rs | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 628aee6..706b6f0 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -31,7 +31,7 @@ pub struct DecompressOptions<'a> { pub formats: Vec, pub output_dir: &'a Path, pub output_file_path: PathBuf, - pub is_output_dir_explicit: bool, + pub is_output_dir_provided: bool, pub question_policy: QuestionPolicy, pub quiet: bool, pub password: Option<&'a [u8]>, @@ -74,7 +74,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, - options.is_output_dir_explicit, + options.is_output_dir_provided, )? { files } else { @@ -152,7 +152,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, - options.is_output_dir_explicit, + options.is_output_dir_provided, )? { files } else { @@ -186,7 +186,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, - options.is_output_dir_explicit, + options.is_output_dir_provided, )? { files } else { @@ -218,7 +218,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, - options.is_output_dir_explicit, + options.is_output_dir_provided, )? { files } else { @@ -260,7 +260,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { options.output_dir, &options.output_file_path, options.question_policy, - options.is_output_dir_explicit, + options.is_output_dir_provided, )? { files } else { @@ -295,9 +295,9 @@ fn execute_decompression( output_dir: &Path, output_file_path: &Path, question_policy: QuestionPolicy, - is_output_dir_explicit: bool, + is_output_dir_provided: bool, ) -> crate::Result> { - if is_output_dir_explicit { + if is_output_dir_provided { unpack(unpack_fn, output_dir, question_policy) } else { smart_unpack(unpack_fn, output_dir, output_file_path, question_policy) @@ -313,7 +313,7 @@ fn unpack( output_dir: &Path, question_policy: QuestionPolicy, ) -> crate::Result> { - let is_valid_output_dir = !output_dir.exists() || (output_dir.is_dir() && output_dir.read_dir()?.count() == 0); + let is_valid_output_dir = !output_dir.exists() || (output_dir.is_dir() && output_dir.read_dir()?.next().is_none()); let output_dir_cleaned = if is_valid_output_dir { output_dir.to_owned() diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a0aaa26..60a2866 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -176,7 +176,7 @@ pub fn run( // 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_explicit = output_dir.is_some(); + 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,7 +200,7 @@ pub fn run( formats, output_dir: &output_dir, output_file_path, - is_output_dir_explicit, + is_output_dir_provided, question_policy, quiet: args.quiet, password: args.password.as_deref().map(|str| { From 61dab2af29ddccb716c4b58cf4f7024202b48098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Sun, 6 Apr 2025 14:02:47 -0300 Subject: [PATCH 083/101] CI: fix releases --- .github/workflows/draft-release-automatic-trigger.yml | 2 +- .github/workflows/draft-release-manual-trigger.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml index 2ebf967..affcac7 100644 --- a/.github/workflows/draft-release-automatic-trigger.yml +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -15,7 +15,7 @@ jobs: automated-draft-release: runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - needs: build-artifacts-and-run-tests + needs: call-workflow-build-artifacts-and-run-tests steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/draft-release-manual-trigger.yml b/.github/workflows/draft-release-manual-trigger.yml index d97ec72..f4e3014 100644 --- a/.github/workflows/draft-release-manual-trigger.yml +++ b/.github/workflows/draft-release-manual-trigger.yml @@ -18,7 +18,7 @@ jobs: uses: dawidd6/action-download-artifact@v6 with: path: artifacts - workflow: build-and-test.yml + workflow: build-artifacts-and-run-tests.yml run_id: ${{ github.event.inputs.run_id }} - name: Package release assets From c6cbf6e15762b1ecbefeaeca956f893a50795fdf Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:15:22 -0300 Subject: [PATCH 084/101] perf: replace `.count()` from iterators to more performative operations --- src/archive/rar.rs | 2 +- src/archive/tar.rs | 2 +- src/archive/zip.rs | 2 +- src/commands/decompress.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/archive/rar.rs b/src/archive/rar.rs index c223192..ec2d1e6 100644 --- a/src/archive/rar.rs +++ b/src/archive/rar.rs @@ -18,7 +18,7 @@ pub fn unpack_archive( password: Option<&[u8]>, quiet: bool, ) -> crate::Result { - assert!(output_folder.read_dir().expect("dir exists").count() == 0); + assert!(output_folder.read_dir().expect("dir exists").next().is_none()); let archive = match password { Some(password) => Archive::with_password(archive_path, password), diff --git a/src/archive/tar.rs b/src/archive/tar.rs index fda05eb..3416078 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -24,7 +24,7 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `into`. /// Assumes that output_folder is empty pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) -> crate::Result { - assert!(output_folder.read_dir().expect("dir exists").count() == 0); + assert!(output_folder.read_dir().expect("dir exists").next().is_none()); let mut archive = tar::Archive::new(reader); let mut files_unpacked = 0; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 9995e07..f525d3b 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -37,7 +37,7 @@ pub fn unpack_archive( where R: Read + Seek, { - assert!(output_folder.read_dir().expect("dir exists").count() == 0); + assert!(output_folder.read_dir().expect("dir exists").next().is_none()); let mut unpacked_files = 0; diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 706b6f0..981376f 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -356,7 +356,7 @@ fn smart_unpack( let files = unpack_fn(temp_dir_path)?; - let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.count() == 1; + let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.take(2).count() == 1; let (previous_path, mut new_path) = if root_contains_only_one_element { // Only one file in the root directory, so we can just move it to the output directory From c584170a248583f0454093612ee91b47ac773114 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:53:31 -0300 Subject: [PATCH 085/101] tweak: replace '.tmp-ouch-' prefix with 'tmp-ouch-' for smart unpack (#788) --- CHANGELOG.md | 2 +- src/commands/decompress.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4788a9f..b0c3c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Categories Used: ### Tweaks - CI refactor [\#578](https://github.com/ouch-org/ouch/pull/578) ([cyqsimon](https://github.com/cyqsimon)) -- Use a more unique name for temporary decompression path [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) +- Use a prefix `tmp-ouch-` for temporary decompression path name to avoid conflicts [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) & [\#788](https://github.com/ouch-org/ouch/pull/788) ([talis-fb](https://github.com/talis-fb)) - Run clippy for tests too [\#738](https://github.com/ouch-org/ouch/pull/738) ([marcospb19](https://github.com/marcospb19)) ### Improvements diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 981376f..036d1d2 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -346,7 +346,7 @@ fn smart_unpack( question_policy: QuestionPolicy, ) -> crate::Result> { assert!(output_dir.exists()); - let temp_dir = tempfile::Builder::new().prefix(".tmp-ouch-").tempdir_in(output_dir)?; + let temp_dir = tempfile::Builder::new().prefix("tmp-ouch-").tempdir_in(output_dir)?; let temp_dir_path = temp_dir.path(); info_accessible(format!( From 21e7fdf3d668df41f31fbfeea701159cfbd459fa Mon Sep 17 00:00:00 2001 From: MisileLab Date: Mon, 14 Apr 2025 05:23:42 +0900 Subject: [PATCH 086/101] Fix typo in CHANGELOG.md (#790) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c3c24..27643f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,7 @@ Categories Used: ### New Features - Add support for listing and decompressing `.rar` archives [\#529](https://github.com/ouch-org/ouch/pull/529) ([lmkra](https://github.com/lmkra)) -- Add support for 7z [\#555](https://github.com/ouch-org/ouch/pull/555) ([Flat](https://github.com/flat) & [MissileLab](https://github.com/MisileLab)) +- Add support for 7z [\#555](https://github.com/ouch-org/ouch/pull/555) ([Flat](https://github.com/flat) & [MisileLab](https://github.com/MisileLab)) ### Bug Fixes From 7b082b59c550c66ca0a14e3f241e199f52c2b6a4 Mon Sep 17 00:00:00 2001 From: Mihail Malo Date: Mon, 14 Apr 2025 19:24:27 +0300 Subject: [PATCH 087/101] Sort results.md (#791) I was so confused I barely escaped with my life --- benchmarks/results.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/results.md b/benchmarks/results.md index 13e7cc2..3e6287a 100644 --- a/benchmarks/results.md +++ b/benchmarks/results.md @@ -20,8 +20,8 @@ | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | |:---|---:|---:|---:|---:| -| `zip output.zip -r compiler` | 581.3 ± 9.1 | 573.2 | 600.9 | 1.06 ± 0.02 | | `ouch compress compiler output.zip` | 549.7 ± 4.3 | 543.6 | 558.6 | 1.00 | +| `zip output.zip -r compiler` | 581.3 ± 9.1 | 573.2 | 600.9 | 1.06 ± 0.02 | | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | |:---|---:|---:|---:|---:| From b9b1e11303f1d12d7120354e24f78865bdff253b Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 17 Apr 2025 04:43:46 +0800 Subject: [PATCH 088/101] Store symlinks by default and add `--follow-symlinks` to toggle it (#789) --- CHANGELOG.md | 1 + src/archive/tar.rs | 32 +++++++++++++- src/archive/zip.rs | 39 +++++++++++++++-- src/cli/args.rs | 8 ++++ src/cli/mod.rs | 2 +- src/commands/compress.rs | 11 ++++- src/commands/mod.rs | 2 + tests/integration.rs | 95 +++++++++++++++++++++++++++++++++++++++- tests/utils.rs | 2 +- 9 files changed, 184 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27643f5..ba482f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Categories Used: - Add `--remove` flag for decompression subcommand to remove files after successful decompression [\#757](https://github.com/ouch-org/ouch/pull/757) ([ttys3](https://github.com/ttys3)) - Add `br` (Brotli) support [\#765](https://github.com/ouch-org/ouch/pull/765) ([killercup](https://github.com/killercup)) - Add rename option in overwrite menu [\#779](https://github.com/ouch-org/ouch/pull/779) ([talis-fb](https://github.com/talis-fb)) +- Store symlinks by default and add `--follow-symlinks` to store the target files [\#789](https://github.com/ouch-org/ouch/pull/789) ([tommady](https://github.com/tommady)) ### Bug Fixes diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 3416078..c0d962d 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -31,7 +31,24 @@ pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) for file in archive.entries()? { let mut file = file?; - file.unpack_in(output_folder)?; + match file.header().entry_type() { + tar::EntryType::Symlink => { + let relative_path = file.path()?.to_path_buf(); + let full_path = output_folder.join(&relative_path); + let target = file + .link_name()? + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing symlink target"))?; + + #[cfg(unix)] + std::os::unix::fs::symlink(&target, &full_path)?; + #[cfg(windows)] + std::os::windows::fs::symlink_file(&target, &full_path)?; + } + tar::EntryType::Regular | tar::EntryType::Directory => { + file.unpack_in(output_folder)?; + } + _ => continue, + } // This is printed for every file in the archive and has little // importance for most users, but would generate lots of @@ -87,6 +104,7 @@ pub fn build_archive_from_paths( writer: W, file_visibility_policy: FileVisibilityPolicy, quiet: bool, + follow_symlinks: bool, ) -> crate::Result where W: Write, @@ -127,6 +145,18 @@ where if path.is_dir() { builder.append_dir(path, path)?; + } else if path.is_symlink() && !follow_symlinks { + let target_path = path.read_link()?; + + let mut header = tar::Header::new_gnu(); + header.set_entry_type(tar::EntryType::Symlink); + header.set_size(0); + + builder.append_link(&mut header, path, &target_path).map_err(|err| { + FinalError::with_title("Could not create archive") + .detail("Unexpected error while trying to read link") + .detail(format!("Error: {err}.")) + })?; } else { let mut file = match fs::File::open(path) { Ok(f) => f, diff --git a/src/archive/zip.rs b/src/archive/zip.rs index f525d3b..d593477 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -85,8 +85,23 @@ where )); } - let mut output_file = fs::File::create(file_path)?; - io::copy(&mut file, &mut output_file)?; + let mode = file.unix_mode().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "Cannot extract file's mode") + })?; + let is_symlink = (mode & 0o170000) == 0o120000; + + if is_symlink { + let mut target = String::new(); + file.read_to_string(&mut target)?; + + #[cfg(unix)] + std::os::unix::fs::symlink(&target, file_path)?; + #[cfg(windows)] + std::os::windows::fs::symlink_file(&target, file_path)?; + } else { + let mut output_file = fs::File::create(file_path)?; + io::copy(&mut file, &mut output_file)?; + } set_last_modified_time(&file, file_path)?; } @@ -155,6 +170,7 @@ pub fn build_archive_from_paths( writer: W, file_visibility_policy: FileVisibilityPolicy, quiet: bool, + follow_symlinks: bool, ) -> crate::Result where W: Write + Seek, @@ -223,7 +239,7 @@ where }; #[cfg(unix)] - let options = options.unix_permissions(metadata.permissions().mode()); + let mode = metadata.permissions().mode(); let entry_name = path.to_str().ok_or_else(|| { FinalError::with_title("Zip requires that all directories names are valid UTF-8") @@ -232,6 +248,21 @@ where if metadata.is_dir() { writer.add_directory(entry_name, options)?; + } else if path.is_symlink() && !follow_symlinks { + let target_path = path.read_link()?; + let target_name = target_path.to_str().ok_or_else(|| { + FinalError::with_title("Zip requires that all directories names are valid UTF-8") + .detail(format!("File at '{target_path:?}' has a non-UTF-8 name")) + })?; + + // This approach writes the symlink target path as the content of the symlink entry. + // We detect symlinks during extraction by checking for the Unix symlink mode (0o120000) in the entry's permissions. + #[cfg(unix)] + let symlink_options = options.unix_permissions(0o120000 | (mode & 0o777)); + #[cfg(windows)] + let symlink_options = options.unix_permissions(0o120777); + + writer.add_symlink(entry_name, target_name, symlink_options)?; } else { #[cfg(not(unix))] let options = if is_executable::is_executable(path) { @@ -242,6 +273,8 @@ where let mut file = fs::File::open(path)?; + #[cfg(unix)] + let options = options.unix_permissions(mode); // Updated last modified time let last_modified_time = options.last_modified_time(get_last_modified_time(&file)); diff --git a/src/cli/args.rs b/src/cli/args.rs index 5701f4e..c72d5d3 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -81,6 +81,10 @@ pub enum Subcommand { /// conflicts with --level and --fast #[arg(long, group = "compression-level")] slow: bool, + + /// Archive target files instead of storing symlinks (supported by `tar` and `zip`) + #[arg(long, short = 'S')] + follow_symlinks: bool, }, /// Decompresses one or more files, optionally into another folder #[command(visible_alias = "d")] @@ -201,6 +205,7 @@ mod tests { level: None, fast: false, slow: false, + follow_symlinks: false, }, ..mock_cli_args() } @@ -214,6 +219,7 @@ mod tests { level: None, fast: false, slow: false, + follow_symlinks: false, }, ..mock_cli_args() } @@ -227,6 +233,7 @@ mod tests { level: None, fast: false, slow: false, + follow_symlinks: false, }, ..mock_cli_args() } @@ -251,6 +258,7 @@ mod tests { level: None, fast: false, slow: false, + follow_symlinks: false, }, format: Some("tar.gz".into()), ..mock_cli_args() diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ce6b5d5..185fae6 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -54,7 +54,7 @@ fn canonicalize_files(files: &[impl AsRef]) -> io::Result> { files .iter() .map(|f| { - if is_path_stdin(f.as_ref()) { + if is_path_stdin(f.as_ref()) || f.as_ref().is_symlink() { Ok(f.as_ref().to_path_buf()) } else { fs::canonicalize(f) diff --git a/src/commands/compress.rs b/src/commands/compress.rs index 59987b8..b6f7042 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -31,6 +31,7 @@ pub fn compress_files( output_file: fs::File, output_path: &Path, quiet: bool, + follow_symlinks: bool, question_policy: QuestionPolicy, file_visibility_policy: FileVisibilityPolicy, level: Option, @@ -108,7 +109,14 @@ pub fn compress_files( io::copy(&mut reader, &mut writer)?; } Tar => { - archive::tar::build_archive_from_paths(&files, output_path, &mut writer, file_visibility_policy, quiet)?; + archive::tar::build_archive_from_paths( + &files, + output_path, + &mut writer, + file_visibility_policy, + quiet, + follow_symlinks, + )?; writer.flush()?; } Zip => { @@ -131,6 +139,7 @@ pub fn compress_files( &mut vec_buffer, file_visibility_policy, quiet, + follow_symlinks, )?; vec_buffer.rewind()?; io::copy(&mut vec_buffer, &mut writer)?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 60a2866..3e8718b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -67,6 +67,7 @@ pub fn run( level, fast, slow, + follow_symlinks, } => { // After cleaning, if there are no input files left, exit if files.is_empty() { @@ -109,6 +110,7 @@ pub fn run( output_file, &output_path, args.quiet, + follow_symlinks, question_policy, file_visibility_policy, level, diff --git a/tests/integration.rs b/tests/integration.rs index 7a5e549..1a984a5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,7 +1,11 @@ #[macro_use] mod utils; -use std::{io::Write, iter::once, path::PathBuf}; +use std::{ + io::Write, + iter::once, + path::{Path, PathBuf}, +}; use fs_err as fs; use parse_display::Display; @@ -467,3 +471,92 @@ fn unpack_rar_stdin() -> Result<(), Box> { Ok(()) } + +#[proptest(cases = 25)] +fn symlink_pack_and_unpack( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, +) { + if matches!(ext, DirectoryExtension::SevenZ) { + // Skip 7z because the 7z format does not support symlinks + return Ok(()); + } + + let temp_dir = tempdir()?; + let root_path = temp_dir.path(); + + let src_files_path = root_path.join("src_files"); + fs::create_dir_all(&src_files_path)?; + + 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(); + file.write_all("Some content".as_bytes()).unwrap(); + path + }) + .collect::>(); + + let dest_files_path = root_path.join("dest_files"); + fs::create_dir_all(&dest_files_path)?; + + let symlink_path = src_files_path.join(Path::new("symlink")); + #[cfg(unix)] + std::os::unix::fs::symlink(&files_path[0], &symlink_path)?; + #[cfg(windows)] + std::os::windows::fs::symlink_file(&files_path[0], &symlink_path)?; + + files_path.push(symlink_path); + + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + + crate::utils::cargo_bin() + .arg("compress") + .args(files_path.clone()) + .arg(archive) + .assert() + .success(); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(&dest_files_path) + .assert() + .success(); + + assert_same_directory(&src_files_path, &dest_files_path, false); + // check the symlink stand still + for f in dest_files_path.as_path().read_dir()? { + let f = f?; + if f.file_name() == "symlink" { + assert!(f.file_type()?.is_symlink()) + } + } + + fs::remove_file(archive)?; + fs::remove_dir_all(&dest_files_path)?; + + crate::utils::cargo_bin() + .arg("compress") + .arg("--follow-symlinks") + .args(files_path) + .arg(archive) + .assert() + .success(); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(&dest_files_path) + .assert() + .success(); + + // check there is no symlinks + for f in dest_files_path.as_path().read_dir()? { + let f = f?; + assert!(!f.file_type().unwrap().is_symlink()) + } +} diff --git a/tests/utils.rs b/tests/utils.rs index 3648cef..4d2a84f 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -88,7 +88,7 @@ pub fn assert_same_directory(x: impl Into, y: impl Into, prese if ft_x.is_dir() && ft_y.is_dir() { assert_same_directory(x.path(), y.path(), preserve_permissions); - } else if ft_x.is_file() && ft_y.is_file() { + } else if (ft_x.is_file() && ft_y.is_file()) || (ft_x.is_symlink() && ft_y.is_symlink()) { assert_eq!(meta_x.len(), meta_y.len()); assert_eq!(fs::read(x.path()).unwrap(), fs::read(y.path()).unwrap()); } else { From fdab666bf832cfbed2607eb930e61d6629ced000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Wed, 16 Apr 2025 17:48:04 -0300 Subject: [PATCH 089/101] Enable all tests in CI (#787) Some tests are behind the feature `allow_piped_choice`. By adding that to the feature list we include these tests. --- .github/workflows/build-artifacts-and-run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml index cb3bf30..39df8af 100644 --- a/.github/workflows/build-artifacts-and-run-tests.yml +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -98,7 +98,7 @@ jobs: id: concat-features shell: bash run: | - FEATURES=() + FEATURES=(allow_piped_choice) if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi From 08416c7a22d4a6ce1e637313d549ae16ffcd1563 Mon Sep 17 00:00:00 2001 From: tommady Date: Fri, 18 Apr 2025 00:42:35 +0800 Subject: [PATCH 090/101] change crate from sevenz-rust to sevenz-rust2 (#796) --- CHANGELOG.md | 1 + Cargo.lock | 189 ++++++++++++++++++++++++++++-------------- Cargo.toml | 2 +- src/archive/sevenz.rs | 18 ++-- src/error.rs | 4 +- 5 files changed, 138 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba482f4..d553bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Categories Used: - CI refactor [\#578](https://github.com/ouch-org/ouch/pull/578) ([cyqsimon](https://github.com/cyqsimon)) - Use a prefix `tmp-ouch-` for temporary decompression path name to avoid conflicts [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) & [\#788](https://github.com/ouch-org/ouch/pull/788) ([talis-fb](https://github.com/talis-fb)) - Run clippy for tests too [\#738](https://github.com/ouch-org/ouch/pull/738) ([marcospb19](https://github.com/marcospb19)) +- Sevenz-rust is unmaintained, switch to sevenz-rust2 [\#796](https://github.com/ouch-org/ouch/pull/796) ([tommady](https://github.com/tommady)) ### Improvements diff --git a/Cargo.lock b/Cargo.lock index 1524148..f87245c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,30 +154,15 @@ dependencies = [ "which", ] -[[package]] -name = "bit-set" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" -dependencies = [ - "bit-vec 0.7.0", -] - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" - [[package]] name = "bit-vec" version = "0.8.0" @@ -481,21 +466,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" version = "1.4.2" @@ -542,9 +512,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -848,6 +818,30 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "jiff" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -968,10 +962,10 @@ dependencies = [ ] [[package]] -name = "lzma-rust" -version = "0.1.7" +name = "lzma-rust2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661" +checksum = "561d131e2d9b07641ac55bc35de5ae4ac3e783fdeebd3c4c1dd3b6a7b920051a" dependencies = [ "byteorder", ] @@ -1029,11 +1023,13 @@ dependencies = [ [[package]] name = "nt-time" -version = "0.8.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5" +checksum = "f5e71108c089b161344bacb1227dd2124fee63ed1792fdd8308e6689197c2754" dependencies = [ "chrono", + "jiff", + "rand 0.9.0", "time", ] @@ -1097,11 +1093,11 @@ dependencies = [ "once_cell", "parse-display", "proptest", - "rand", + "rand 0.8.5", "rayon", "regex", "same-file", - "sevenz-rust", + "sevenz-rust2", "snap", "tar", "tempfile", @@ -1145,7 +1141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1193,6 +1189,21 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1205,7 +1216,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -1250,13 +1261,13 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set", + "bit-vec", "bitflags 2.8.0", "lazy_static", "num-traits", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -1286,8 +1297,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", ] [[package]] @@ -1297,7 +1319,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1309,13 +1341,22 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + [[package]] name = "rand_xorshift" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1455,21 +1496,21 @@ dependencies = [ ] [[package]] -name = "sevenz-rust" -version = "0.6.1" +name = "sevenz-rust2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef" +checksum = "dd27d0f7d5e54cab609728bf243f870469dcc845b052285e7e47ef097c04ec6a" dependencies = [ "aes", - "bit-set 0.6.0", + "bit-set", "byteorder", "cbc", - "crc", + "crc32fast", "filetime_creation", + "getrandom 0.3.1", "js-sys", - "lzma-rust", + "lzma-rust2", "nt-time", - "rand", "sha2", "wasm-bindgen", ] @@ -1651,9 +1692,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "num-conv", @@ -1665,15 +1706,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -2013,7 +2054,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -2027,6 +2077,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index a9f3ba8..6ce7258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ num_cpus = "1.16.0" once_cell = "1.20.2" rayon = "1.10.0" same-file = "1.0.6" -sevenz-rust = { version = "0.6.1", features = ["compress", "aes256"] } +sevenz-rust2 = { version = "0.13.1", features = ["compress", "aes256"] } snap = "1.1.1" tar = "0.4.42" tempfile = "3.10.1" diff --git a/src/archive/sevenz.rs b/src/archive/sevenz.rs index 8898f5f..7c14f0e 100644 --- a/src/archive/sevenz.rs +++ b/src/archive/sevenz.rs @@ -9,7 +9,7 @@ use std::{ use bstr::ByteSlice; use fs_err as fs; use same_file::Handle; -use sevenz_rust::SevenZArchiveEntry; +use sevenz_rust2::SevenZArchiveEntry; use crate::{ error::{Error, FinalError, Result}, @@ -31,7 +31,7 @@ pub fn compress_sevenz( where W: Write + Seek, { - let mut writer = sevenz_rust::SevenZWriter::new(writer)?; + let mut writer = sevenz_rust2::SevenZWriter::new(writer)?; let output_handle = Handle::from_path(output_path); for filename in files { @@ -81,7 +81,7 @@ where .detail(format!("File at '{path:?}' has a non-UTF-8 name")) })?; - let entry = sevenz_rust::SevenZArchiveEntry::from_path(path, entry_name.to_owned()); + let entry = sevenz_rust2::SevenZArchiveEntry::from_path(path, entry_name.to_owned()); let entry_data = if metadata.is_dir() { None } else { @@ -156,15 +156,15 @@ where }; match password { - Some(password) => sevenz_rust::decompress_with_extract_fn_and_password( + Some(password) => sevenz_rust2::decompress_with_extract_fn_and_password( reader, output_path, - sevenz_rust::Password::from(password.to_str().map_err(|err| Error::InvalidPassword { + sevenz_rust2::Password::from(password.to_str().map_err(|err| Error::InvalidPassword { reason: err.to_string(), })?), entry_extract_fn, )?, - None => sevenz_rust::decompress_with_extract_fn(reader, output_path, entry_extract_fn)?, + None => sevenz_rust2::decompress_with_extract_fn(reader, output_path, entry_extract_fn)?, } Ok(count) @@ -197,14 +197,14 @@ pub fn list_archive( }) } }; - sevenz_rust::decompress_with_extract_fn_and_password( + sevenz_rust2::decompress_with_extract_fn_and_password( reader, ".", - sevenz_rust::Password::from(password), + sevenz_rust2::Password::from(password), entry_extract_fn, )?; } - None => sevenz_rust::decompress_with_extract_fn(reader, ".", entry_extract_fn)?, + None => sevenz_rust2::decompress_with_extract_fn(reader, ".", entry_extract_fn)?, } Ok(files.into_iter()) diff --git a/src/error.rs b/src/error.rs index 6b2e92d..3f5bc3e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -235,8 +235,8 @@ impl From for Error { } } -impl From for Error { - fn from(err: sevenz_rust::Error) -> Self { +impl From for Error { + fn from(err: sevenz_rust2::Error) -> Self { Self::SevenzipError { reason: err.to_string(), } From 267ce7672ea6f6d7ec872779efeecadb94e56db4 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:56:02 -0300 Subject: [PATCH 091/101] feat: ignore `.git/` when `-g/--gitignore` is set (#795) --- CHANGELOG.md | 1 + src/utils/file_visibility.rs | 13 +++++++--- tests/integration.rs | 50 +++++++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d553bec..f295155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Categories Used: - CI refactor [\#578](https://github.com/ouch-org/ouch/pull/578) ([cyqsimon](https://github.com/cyqsimon)) - Use a prefix `tmp-ouch-` for temporary decompression path name to avoid conflicts [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) & [\#788](https://github.com/ouch-org/ouch/pull/788) ([talis-fb](https://github.com/talis-fb)) +- Ignore `.git/` when `-g/--gitignore` is set [\#507](https://github.com/ouch-org/ouch/pull/507) ([talis-fb](https://github.com/talis-fb)) - Run clippy for tests too [\#738](https://github.com/ouch-org/ouch/pull/738) ([marcospb19](https://github.com/marcospb19)) - Sevenz-rust is unmaintained, switch to sevenz-rust2 [\#796](https://github.com/ouch-org/ouch/pull/796) ([tommady](https://github.com/tommady)) diff --git a/src/utils/file_visibility.rs b/src/utils/file_visibility.rs index d8abd40..fc478b2 100644 --- a/src/utils/file_visibility.rs +++ b/src/utils/file_visibility.rs @@ -69,11 +69,18 @@ impl FileVisibilityPolicy { /// Walks through a directory using [`ignore::Walk`] pub fn build_walker(&self, path: impl AsRef) -> ignore::Walk { - ignore::WalkBuilder::new(path) + let mut builder = ignore::WalkBuilder::new(path); + + builder .git_exclude(self.read_git_exclude) .git_ignore(self.read_git_ignore) .ignore(self.read_ignore) - .hidden(self.read_hidden) - .build() + .hidden(self.read_hidden); + + if self.read_git_ignore { + builder.filter_entry(|p| p.path().file_name().is_some_and(|name| name != ".git")); + } + + builder.build() } } diff --git a/tests/integration.rs b/tests/integration.rs index 1a984a5..52413a7 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -381,10 +381,9 @@ fn multiple_files_with_disabled_smart_unpack_by_dir( let 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::>(); @@ -560,3 +559,48 @@ fn symlink_pack_and_unpack( assert!(!f.file_type().unwrap().is_symlink()) } } + +#[test] +fn no_git_folder_after_decompression_with_gitignore_flag_active() { + use std::process::Command; + + let dir = tempdir().unwrap(); + let dir_path = dir.path(); + + let before = dir_path.join("before"); + + let decompressed = dir_path.join("decompressed"); + + // Create directory and a dummy file + fs::create_dir(&before).unwrap(); + fs::write(before.join("hello.txt"), b"Hello, world!").unwrap(); + + // Run `git init` inside it + Command::new("git") + .arg("init") + .current_dir(&before) + .output() + .expect("failed to run git init"); + + assert!(before.join(".git").exists(), ".git folder should exist after git init"); + + // Compress it + let archive = dir_path.join("archive.zip"); + ouch!("c", &before, &archive, "--gitignore"); + + // Decompress it + ouch!("d", &archive, "-d", &decompressed); + + // Find the subdirectory inside decompressed (e.g., "before") + let decompressed_subdir = fs::read_dir(&decompressed) + .unwrap() + .find_map(Result::ok) + .map(|entry| entry.path()) + .expect("Expected one directory inside decompressed"); + + // Assert that the decompressed folder does not include `.git/` + assert!( + !decompressed_subdir.join(".git").exists(), + ".git folder should not exist after decompression" + ); +} From ab5dd00b86bb96f6898f336e1a3bddf15791c349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Fri, 18 Apr 2025 03:20:31 -0300 Subject: [PATCH 092/101] Fix release CI (#797) --- .github/workflows/all-tests-slow.yml | 2 +- .../build-artifacts-and-run-tests.yml | 34 ++++++---- .../draft-release-automatic-trigger.yml | 8 ++- .../draft-release-manual-trigger.yml | 32 --------- .github/workflows/pr-workflow.yml | 2 +- Cargo.toml | 4 +- build.rs | 10 +-- scripts/package-release-assets.sh | 67 +++++++++++++------ 8 files changed, 81 insertions(+), 78 deletions(-) delete mode 100644 .github/workflows/draft-release-manual-trigger.yml diff --git a/.github/workflows/all-tests-slow.yml b/.github/workflows/all-tests-slow.yml index 7d090ac..dbf90cf 100644 --- a/.github/workflows/all-tests-slow.yml +++ b/.github/workflows/all-tests-slow.yml @@ -14,4 +14,4 @@ jobs: uses: ./.github/workflows/build-artifacts-and-run-tests.yml with: matrix_all_combinations: true - upload_artifacts: false + artifact_upload_mode: none diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml index 39df8af..5a851f7 100644 --- a/.github/workflows/build-artifacts-and-run-tests.yml +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -10,20 +10,23 @@ on: type: boolean required: true default: true - upload_artifacts: - description: "if built artifacts should be uploaded" - type: boolean + artifact_upload_mode: + description: "Control what artifacts to upload: 'none' for no uploads, 'with_default_features' to upload artifacts with default features (for releases), or 'all' for all feature combinations." + type: choice + options: + - none + - with_default_features + - all required: true - default: true workflow_call: inputs: matrix_all_combinations: description: "if matrix should have all combinations of targets and features" type: boolean required: true - upload_artifacts: - description: "if built artifacts should be uploaded" - type: boolean + artifact_upload_mode: + description: "Control which artifacts to upload: 'none' for no uploads, 'with_default_features' to upload only artifacts with default features (use_zlib+use_zstd_thin+unrar), or 'all' to upload all feature combinations." + type: string required: true jobs: @@ -102,12 +105,16 @@ jobs: if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi + # Output plus-separated list for artifact names + IFS='+' + echo "FEATURES_PLUS=${FEATURES[*]}" >> $GITHUB_OUTPUT + # Output comma-separated list for cargo flags IFS=',' - echo "FEATURES=${FEATURES[*]}" >> $GITHUB_OUTPUT + echo "FEATURES_COMMA=${FEATURES[*]}" >> $GITHUB_OUTPUT - name: Set up extra cargo flags env: - FEATURES: ${{steps.concat-features.outputs.FEATURES}} + FEATURES: ${{steps.concat-features.outputs.FEATURES_COMMA}} shell: bash run: | FLAGS="--no-default-features" @@ -133,13 +140,16 @@ jobs: run: | ${{ env.CARGO }} +stable build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS env: - OUCH_ARTIFACTS_FOLDER: artifacts + OUCH_ARTIFACTS_FOLDER: man-page-and-completions-artifacts - name: Upload release artifacts - if: ${{ inputs.upload_artifacts }} + if: | + ${{ inputs.artifact_upload_mode != 'none' && + (inputs.artifact_upload_mode == 'all' || + (matrix.feature-unrar && matrix.feature-use-zlib && matrix.feature-use-zstd-thin)) }} uses: actions/upload-artifact@v4 with: - name: ouch-${{ matrix.target }}-${{ steps.concat-features.outputs.FEATURES }} + name: ouch-${{ matrix.target }}${{ steps.concat-features.outputs.FEATURES_PLUS != '' && format('-{0}', steps.concat-features.outputs.FEATURES_PLUS) || '' }} path: | target/${{ matrix.target }}/release/ouch target/${{ matrix.target }}/release/ouch.exe diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml index affcac7..fd45837 100644 --- a/.github/workflows/draft-release-automatic-trigger.yml +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -10,7 +10,7 @@ jobs: uses: ./.github/workflows/build-artifacts-and-run-tests.yml with: matrix_all_combinations: true - upload_artifacts: true + artifact_upload_mode: with_default_features automated-draft-release: runs-on: ubuntu-latest @@ -23,7 +23,9 @@ jobs: - name: Download artifacts uses: dawidd6/action-download-artifact@v6 with: - path: artifacts + path: downloaded_artifacts + workflow: ./.github/workflows/build-artifacts-and-run-tests.yml + name: ouch-* - name: Package release assets run: scripts/package-release-assets.sh @@ -32,4 +34,4 @@ jobs: uses: softprops/action-gh-release@v2 with: draft: true - files: release/ouch-* + files: output_assets/ouch-* diff --git a/.github/workflows/draft-release-manual-trigger.yml b/.github/workflows/draft-release-manual-trigger.yml deleted file mode 100644 index f4e3014..0000000 --- a/.github/workflows/draft-release-manual-trigger.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Manual trigger draft release - -on: - workflow_dispatch: - inputs: - run_id: - description: Run id of the action run to pull artifacts from - required: true - -jobs: - create-draft-release-from-manual-trigger: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Download artifacts - uses: dawidd6/action-download-artifact@v6 - with: - path: artifacts - workflow: build-artifacts-and-run-tests.yml - run_id: ${{ github.event.inputs.run_id }} - - - name: Package release assets - run: scripts/package-release-assets.sh - - - name: Create release - uses: softprops/action-gh-release@v2 - with: - draft: true - name: manual release ${{ github.event.inputs.run_id }} - files: release/ouch-* diff --git a/.github/workflows/pr-workflow.yml b/.github/workflows/pr-workflow.yml index e804c0c..6ef3cfd 100644 --- a/.github/workflows/pr-workflow.yml +++ b/.github/workflows/pr-workflow.yml @@ -32,4 +32,4 @@ jobs: uses: ./.github/workflows/build-artifacts-and-run-tests.yml with: matrix_all_combinations: false - upload_artifacts: false + artifact_upload_mode: none diff --git a/Cargo.toml b/Cargo.toml index 6ce7258..309d28e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ouch" version = "0.5.1" authors = [ - "João M. Bezerra ", + "João Marcos ", "Vinícius Rodrigues Miguel ", ] edition = "2021" @@ -70,7 +70,7 @@ regex = "1.10.4" test-strategy = "0.4.0" [features] -default = ["use_zlib", "use_zstd_thin", "unrar"] +default = ["unrar", "use_zlib", "use_zstd_thin"] use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-zlib"] use_zstd_thin = ["zstd/thin"] allow_piped_choice = [] diff --git a/build.rs b/build.rs index c7a1f91..c023757 100644 --- a/build.rs +++ b/build.rs @@ -5,18 +5,12 @@ /// Set `OUCH_ARTIFACTS_FOLDER` to the name of the destination folder: /// /// ```sh -/// OUCH_ARTIFACTS_FOLDER=my-folder cargo build +/// OUCH_ARTIFACTS_FOLDER=man-page-and-completions-artifacts cargo build /// ``` /// -/// All completion files will be generated inside of the folder "my-folder". +/// All completion files will be generated inside of the folder "man-page-and-completions-artifacts". /// /// If the folder does not exist, it will be created. -/// -/// We recommend you naming this folder "artifacts" for the sake of consistency. -/// -/// ```sh -/// OUCH_ARTIFACTS_FOLDER=artifacts cargo build -/// ``` use std::{ env, fs::{create_dir_all, File}, diff --git a/scripts/package-release-assets.sh b/scripts/package-release-assets.sh index c2e1ba9..69aac6f 100755 --- a/scripts/package-release-assets.sh +++ b/scripts/package-release-assets.sh @@ -1,28 +1,57 @@ #!/usr/bin/env bash - set -e -mkdir release -cd artifacts +mkdir -p output_assets +cd downloaded_artifacts -for dir in ouch-*; do - cp -r "$dir/artifacts" "$dir/completions" - mkdir "$dir/man" - mv "$dir"/completions/*.1 "$dir/man" +TARGETS=( + "aarch64-pc-windows-msvc" + "aarch64-unknown-linux-gnu" + "aarch64-unknown-linux-musl" + "armv7-unknown-linux-gnueabihf" + "armv7-unknown-linux-musleabihf" + "x86_64-apple-darwin" + "x86_64-pc-windows-gnu" + "x86_64-pc-windows-msvc" + "x86_64-unknown-linux-gnu" + "x86_64-unknown-linux-musl" +) +DEFAULT_FEATURES="unrar+use_zlib+use_zstd_thin" - cp ../{README.md,LICENSE,CHANGELOG.md} "$dir" - rm -r "$dir/artifacts" +for target in "${TARGETS[@]}"; do + input_dir="ouch-${target}-${DEFAULT_FEATURES}" - if [[ "$dir" = *.exe ]]; then - target=${dir%.exe} - mv "$dir/target/${target/ouch-/}/release/ouch.exe" "$dir" - rm -r "$dir/target" - mv "$dir" "$target" - zip -r "../release/$target.zip" "$target" + if [ ! -d "$input_dir" ]; then + echo "ERROR: Could not find artifact directory for $target with default features ($input_dir)" + exit 1 + fi + + echo "Processing $input_dir" + + cp ../{README.md,LICENSE,CHANGELOG.md} "$input_dir" + mkdir -p "$input_dir/man" + mkdir -p "$input_dir/completions" + + mv "$input_dir"/man-page-and-completions-artifacts/*.1 "$input_dir/man" + mv "$input_dir"/man-page-and-completions-artifacts/* "$input_dir/completions" + rm -r "$input_dir/man-page-and-completions-artifacts" + + output_name="ouch-${target}" + + if [[ "$target" == *"-windows-"* ]]; then + mv "$input_dir/target/$target/release/ouch.exe" "$input_dir" + rm -rf "$input_dir/target" + + zip -r "../output_assets/${output_name}.zip" "$input_dir" + echo "Created output_assets/${output_name}.zip" else - mv "$dir/target/${dir/ouch-/}/release/ouch" "$dir" - rm -r "$dir/target" - chmod +x "$dir/ouch" - tar czf "../release/$dir.tar.gz" "$dir" + mv "$input_dir/target/$target/release/ouch" "$input_dir" + rm -rf "$input_dir/target" + chmod +x "$input_dir/ouch" + + tar czf "../output_assets/${output_name}.tar.gz" "$input_dir" + echo "Created output_assets/${output_name}.tar.gz" fi done + +echo "Done." From 4961a2c4788e41c443c93070f6e445ad476cd162 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Fri, 18 Apr 2025 12:24:02 -0300 Subject: [PATCH 093/101] tweak: Align file sizes at left to make output clearer (#792) --- CHANGELOG.md | 1 + src/archive/rar.rs | 6 +-- src/archive/sevenz.rs | 4 +- src/archive/tar.rs | 4 +- src/archive/zip.rs | 4 +- src/utils/formatting.rs | 40 +++++++++---------- ..._ui_test_ok_decompress_multiple_files.snap | 13 ++++++ tests/ui.rs | 25 +++++++++++- 8 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 tests/snapshots/ui__ui_test_ok_decompress_multiple_files.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index f295155..7de9463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Categories Used: - Make `--format` more forgiving with the formatting of the provided format [\#519](https://github.com/ouch-org/ouch/pull/519) ([marcospb19](https://github.com/marcospb19)) - Use buffered writer for list output [\#764](https://github.com/ouch-org/ouch/pull/764) ([killercup](https://github.com/killercup)) - Disable smart unpack when `--dir` flag is provided in decompress command [\#782](https://github.com/ouch-org/ouch/pull/782) ([talis-fb](https://github.com/talis-fb)) +- Align file sizes at left for each extracted file to make output clearer [\#792](https://github.com/ouch-org/ouch/pull/792) ([talis-fb](https://github.com/talis-fb)) ## [0.5.1](https://github.com/ouch-org/ouch/compare/0.5.0...0.5.1) diff --git a/src/archive/rar.rs b/src/archive/rar.rs index ec2d1e6..37894a0 100644 --- a/src/archive/rar.rs +++ b/src/archive/rar.rs @@ -7,7 +7,7 @@ use unrar::Archive; use crate::{ error::{Error, Result}, list::FileInArchive, - utils::logger::info, + utils::{logger::info, Bytes}, }; /// Unpacks the archive given by `archive_path` into the folder given by `output_folder`. @@ -33,9 +33,9 @@ pub fn unpack_archive( archive = if entry.is_file() { if !quiet { info(format!( - "{} extracted. ({})", + "extracted ({}) {}", + Bytes::new(entry.unpacked_size), entry.filename.display(), - entry.unpacked_size )); } unpacked += 1; diff --git a/src/archive/sevenz.rs b/src/archive/sevenz.rs index 7c14f0e..2618209 100644 --- a/src/archive/sevenz.rs +++ b/src/archive/sevenz.rs @@ -127,9 +127,9 @@ where } else { if !quiet { info(format!( - "{:?} extracted. ({})", + "extracted ({}) {:?}", + Bytes::new(entry.size()), file_path.display(), - Bytes::new(entry.size()) )); } diff --git a/src/archive/tar.rs b/src/archive/tar.rs index c0d962d..513d36c 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -56,9 +56,9 @@ pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) // and so on if !quiet { info(format!( - "{:?} extracted. ({})", - utils::strip_cur_dir(&output_folder.join(file.path()?)), + "extracted ({}) {:?}", Bytes::new(file.size()), + utils::strip_cur_dir(&output_folder.join(file.path()?)), )); files_unpacked += 1; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index d593477..55de2f4 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -79,9 +79,9 @@ where // same reason is in _is_dir: long, often not needed text if !quiet { info(format!( - "{:?} extracted. ({})", + "extracted ({}) {:?}", + Bytes::new(file.size()), file_path.display(), - Bytes::new(file.size()) )); } diff --git a/src/utils/formatting.rs b/src/utils/formatting.rs index 9ebef96..3b82a09 100644 --- a/src/utils/formatting.rs +++ b/src/utils/formatting.rs @@ -105,11 +105,11 @@ impl Bytes { impl std::fmt::Display for Bytes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let &Self(num) = self; + let num = self.0; debug_assert!(num >= 0.0); if num < 1_f64 { - return write!(f, "{} B", num); + return write!(f, "{:>6.2} B", num); } let delimiter = 1000_f64; @@ -117,9 +117,9 @@ impl std::fmt::Display for Bytes { write!( f, - "{:.2} {}B", + "{:>6.2} {:>2}B", num / delimiter.powi(exponent), - Bytes::UNIT_PREFIXES[exponent as usize] + Bytes::UNIT_PREFIXES[exponent as usize], ) } } @@ -138,33 +138,33 @@ mod tests { let mb = kb * 1000; let gb = mb * 1000; - assert_eq!("0 B", format_bytes(0)); // This is weird - assert_eq!("1.00 B", format_bytes(b)); - assert_eq!("999.00 B", format_bytes(b * 999)); - assert_eq!("12.00 MiB", format_bytes(mb * 12)); + assert_eq!(" 0.00 B", format_bytes(0)); // This is weird + assert_eq!(" 1.00 B", format_bytes(b)); + assert_eq!("999.00 B", format_bytes(b * 999)); + assert_eq!(" 12.00 MiB", format_bytes(mb * 12)); assert_eq!("123.00 MiB", format_bytes(mb * 123)); - assert_eq!("5.50 MiB", format_bytes(mb * 5 + kb * 500)); - assert_eq!("7.54 GiB", format_bytes(gb * 7 + 540 * mb)); - assert_eq!("1.20 TiB", format_bytes(gb * 1200)); + assert_eq!(" 5.50 MiB", format_bytes(mb * 5 + kb * 500)); + assert_eq!(" 7.54 GiB", format_bytes(gb * 7 + 540 * mb)); + assert_eq!(" 1.20 TiB", format_bytes(gb * 1200)); // bytes - assert_eq!("234.00 B", format_bytes(234)); - assert_eq!("999.00 B", format_bytes(999)); + assert_eq!("234.00 B", format_bytes(234)); + assert_eq!("999.00 B", format_bytes(999)); // kilobytes - assert_eq!("2.23 kiB", format_bytes(2234)); - assert_eq!("62.50 kiB", format_bytes(62500)); + assert_eq!(" 2.23 kiB", format_bytes(2234)); + assert_eq!(" 62.50 kiB", format_bytes(62500)); assert_eq!("329.99 kiB", format_bytes(329990)); // megabytes - assert_eq!("2.75 MiB", format_bytes(2750000)); - assert_eq!("55.00 MiB", format_bytes(55000000)); + assert_eq!(" 2.75 MiB", format_bytes(2750000)); + assert_eq!(" 55.00 MiB", format_bytes(55000000)); assert_eq!("987.65 MiB", format_bytes(987654321)); // gigabytes - assert_eq!("5.28 GiB", format_bytes(5280000000)); - assert_eq!("95.20 GiB", format_bytes(95200000000)); + assert_eq!(" 5.28 GiB", format_bytes(5280000000)); + assert_eq!(" 95.20 GiB", format_bytes(95200000000)); assert_eq!("302.00 GiB", format_bytes(302000000000)); assert_eq!("302.99 GiB", format_bytes(302990000000)); // Weird aproximation cases: assert_eq!("999.90 GiB", format_bytes(999900000000)); - assert_eq!("1.00 TiB", format_bytes(999990000000)); + assert_eq!(" 1.00 TiB", format_bytes(999990000000)); } } diff --git a/tests/snapshots/ui__ui_test_ok_decompress_multiple_files.snap b/tests/snapshots/ui__ui_test_ok_decompress_multiple_files.snap new file mode 100644 index 0000000..4240b95 --- /dev/null +++ b/tests/snapshots/ui__ui_test_ok_decompress_multiple_files.snap @@ -0,0 +1,13 @@ +--- +source: tests/ui.rs +expression: stdout_lines +--- +{ + "", + "[INFO] Files unpacked: 4", + "[INFO] Successfully decompressed archive in /outputs", + "[INFO] extracted ( 0.00 B) \"outputs/inputs\"", + "[INFO] extracted ( 0.00 B) \"outputs/inputs/input\"", + "[INFO] extracted ( 0.00 B) \"outputs/inputs/input2\"", + "[INFO] extracted ( 0.00 B) \"outputs/inputs/input3\"", +} diff --git a/tests/ui.rs b/tests/ui.rs index e9d3508..9492d1a 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -6,7 +6,7 @@ #[macro_use] mod utils; -use std::{ffi::OsStr, io, path::Path, process::Output}; +use std::{collections::BTreeSet, ffi::OsStr, io, path::Path, process::Output}; use insta::assert_snapshot as ui; use regex::Regex; @@ -142,6 +142,29 @@ fn ui_test_ok_decompress() { ui!(run_ouch("ouch decompress output.zst", dir)); } +#[cfg(target_os = "linux")] +#[test] +fn ui_test_ok_decompress_multiple_files() { + let (_dropper, dir) = testdir().unwrap(); + + let inputs_dir = dir.join("inputs"); + std::fs::create_dir(&inputs_dir).unwrap(); + + let outputs_dir = dir.join("outputs"); + std::fs::create_dir(&outputs_dir).unwrap(); + + // prepare + create_files_in(&inputs_dir, &["input", "input2", "input3"]); + + let compress_command = format!("ouch compress {} output.tar.zst", inputs_dir.to_str().unwrap()); + run_ouch(&compress_command, dir); + + let decompress_command = format!("ouch decompress output.tar.zst --dir {}", outputs_dir.to_str().unwrap()); + let stdout = run_ouch(&decompress_command, dir); + let stdout_lines = stdout.split('\n').collect::>(); + insta::assert_debug_snapshot!(stdout_lines); +} + #[test] fn ui_test_usage_help_flag() { insta::with_settings!({filters => vec![ From 739dfa9507d4c0d913e5b0203da0cecb4ce58bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Fri, 18 Apr 2025 03:46:39 -0300 Subject: [PATCH 094/101] Bump version to 0.6.0 --- .github/workflows/build-artifacts-and-run-tests.yml | 8 ++++---- .github/workflows/draft-release-automatic-trigger.yml | 5 ++--- CHANGELOG.md | 9 ++++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- scripts/package-release-assets.sh | 9 +++++++-- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml index 5a851f7..3b84f9d 100644 --- a/.github/workflows/build-artifacts-and-run-tests.yml +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -102,9 +102,9 @@ jobs: shell: bash run: | FEATURES=(allow_piped_choice) + if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi - if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi # Output plus-separated list for artifact names IFS='+' echo "FEATURES_PLUS=${FEATURES[*]}" >> $GITHUB_OUTPUT @@ -127,7 +127,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: - key: "${{ matrix.target }}-${{ matrix.feature-unrar }}-${{ matrix.feature-use-zstd-thin }}-${{ matrix.feature-unrar }}" + key: "${{ matrix.target }}-${{ matrix.feature-unrar }}-${{ matrix.feature-use-zlib }}-${{ matrix.feature-use-zstd-thin }}" - name: Test on stable # there's no way to run tests for ARM64 Windows for now @@ -136,7 +136,7 @@ jobs: ${{ env.CARGO }} +stable test --profile fast --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS - name: Build release artifacts (binary and completions) - if: ${{ inputs.upload_artifacts }} + if: ${{ inputs.artifact_upload_mode != 'none' }} run: | ${{ env.CARGO }} +stable build --release --target ${{ matrix.target }} $EXTRA_CARGO_FLAGS env: @@ -153,4 +153,4 @@ jobs: path: | target/${{ matrix.target }}/release/ouch target/${{ matrix.target }}/release/ouch.exe - artifacts/ + man-page-and-completions-artifacts/ diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml index fd45837..24b45fc 100644 --- a/.github/workflows/draft-release-automatic-trigger.yml +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -21,11 +21,10 @@ jobs: uses: actions/checkout@v4 - name: Download artifacts - uses: dawidd6/action-download-artifact@v6 + uses: actions/download-artifact@v4 with: path: downloaded_artifacts - workflow: ./.github/workflows/build-artifacts-and-run-tests.yml - name: ouch-* + pattern: ouch-* - name: Package release assets run: scripts/package-release-assets.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 7de9463..dd50ea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,14 @@ Categories Used: **Bullet points in chronological order by PR** -## [Unreleased](https://github.com/ouch-org/ouch/compare/0.5.1...HEAD) +## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.0...HEAD) + +### New Features +### Improvements +### Bug Fixes +### Tweaks + +## [0.6.0](https://github.com/ouch-org/ouch/compare/0.5.1...0.6.0) ### New Features diff --git a/Cargo.lock b/Cargo.lock index f87245c..59bd16a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,7 +1066,7 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "ouch" -version = "0.5.1" +version = "0.6.0" dependencies = [ "assert_cmd", "atty", diff --git a/Cargo.toml b/Cargo.toml index 309d28e..1fe8b82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouch" -version = "0.5.1" +version = "0.6.0" authors = [ "João Marcos ", "Vinícius Rodrigues Miguel ", diff --git a/scripts/package-release-assets.sh b/scripts/package-release-assets.sh index 69aac6f..84d635e 100755 --- a/scripts/package-release-assets.sh +++ b/scripts/package-release-assets.sh @@ -1,8 +1,12 @@ #!/usr/bin/env bash set -e -mkdir -p output_assets +mkdir output_assets +echo "created folder 'output_assets/'" +ls -lA -w 1 cd downloaded_artifacts +echo "entered 'downloaded_artifacts/'" +ls -lA -w 1 TARGETS=( "aarch64-pc-windows-msvc" @@ -16,7 +20,8 @@ TARGETS=( "x86_64-unknown-linux-gnu" "x86_64-unknown-linux-musl" ) -DEFAULT_FEATURES="unrar+use_zlib+use_zstd_thin" +# Temporary, we'll remove allow_piped_choice later +DEFAULT_FEATURES="allow_piped_choice+unrar+use_zlib+use_zstd_thin" for target in "${TARGETS[@]}"; do input_dir="ouch-${target}-${DEFAULT_FEATURES}" From 0b122fa05c70a5a8a4ae5ecc9dce96de67528e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Sun, 20 Apr 2025 13:09:48 -0300 Subject: [PATCH 095/101] Fix `.zip` crash when file mode isn't present (#804) --- CHANGELOG.md | 3 +++ src/archive/zip.rs | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd50ea8..e622b9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ Categories Used: ### New Features ### Improvements ### Bug Fixes + +- Fix .zip crash when file mode isn't present [\#804](https://github.com/ouch-org/ouch/pull/804) ([marcospb19](https://github.com/marcospb19)) + ### Tweaks ## [0.6.0](https://github.com/ouch-org/ouch/compare/0.5.1...0.6.0) diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 55de2f4..9beac49 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -85,10 +85,8 @@ where )); } - let mode = file.unix_mode().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "Cannot extract file's mode") - })?; - let is_symlink = (mode & 0o170000) == 0o120000; + let mode = file.unix_mode(); + let is_symlink = mode.is_some_and(|mode| mode & 0o170000 == 0o120000); if is_symlink { let mut target = String::new(); From c3ff0e963ffa00361677b0fdcd869764e8d06740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Sun, 20 Apr 2025 13:12:34 -0300 Subject: [PATCH 096/101] Releases: restore previous directory structure (#805) --- scripts/package-release-assets.sh | 46 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) mode change 100755 => 100644 scripts/package-release-assets.sh diff --git a/scripts/package-release-assets.sh b/scripts/package-release-assets.sh old mode 100755 new mode 100644 index 84d635e..65d1255 --- a/scripts/package-release-assets.sh +++ b/scripts/package-release-assets.sh @@ -8,7 +8,7 @@ cd downloaded_artifacts echo "entered 'downloaded_artifacts/'" ls -lA -w 1 -TARGETS=( +PLATFORMS=( "aarch64-pc-windows-msvc" "aarch64-unknown-linux-gnu" "aarch64-unknown-linux-musl" @@ -20,41 +20,41 @@ TARGETS=( "x86_64-unknown-linux-gnu" "x86_64-unknown-linux-musl" ) -# Temporary, we'll remove allow_piped_choice later +# TODO: remove allow_piped_choice later DEFAULT_FEATURES="allow_piped_choice+unrar+use_zlib+use_zstd_thin" -for target in "${TARGETS[@]}"; do - input_dir="ouch-${target}-${DEFAULT_FEATURES}" +for platform in "${PLATFORMS[@]}"; do + path="ouch-${platform}" - if [ ! -d "$input_dir" ]; then - echo "ERROR: Could not find artifact directory for $target with default features ($input_dir)" + if [ ! -d "$path" ]; then + echo "ERROR: Could not find artifact directory for $platform with default features ($path)" exit 1 fi - echo "Processing $input_dir" + # remove the suffix + mv "ouch-${platform}-${DEFAULT_FEATURES}" "$path" + echo "Processing $path" - cp ../{README.md,LICENSE,CHANGELOG.md} "$input_dir" - mkdir -p "$input_dir/man" - mkdir -p "$input_dir/completions" + cp ../{README.md,LICENSE,CHANGELOG.md} "$path" + mkdir -p "$path/man" + mkdir -p "$path/completions" - mv "$input_dir"/man-page-and-completions-artifacts/*.1 "$input_dir/man" - mv "$input_dir"/man-page-and-completions-artifacts/* "$input_dir/completions" - rm -r "$input_dir/man-page-and-completions-artifacts" + mv "$path"/man-page-and-completions-artifacts/*.1 "$path/man" + mv "$path"/man-page-and-completions-artifacts/* "$path/completions" + rm -r "$path/man-page-and-completions-artifacts" - output_name="ouch-${target}" + if [[ "$platform" == *"-windows-"* ]]; then + mv "$path/target/$platform/release/ouch.exe" "$path" + rm -rf "$path/target" - if [[ "$target" == *"-windows-"* ]]; then - mv "$input_dir/target/$target/release/ouch.exe" "$input_dir" - rm -rf "$input_dir/target" - - zip -r "../output_assets/${output_name}.zip" "$input_dir" + zip -r "../output_assets/${output_name}.zip" "$path" echo "Created output_assets/${output_name}.zip" else - mv "$input_dir/target/$target/release/ouch" "$input_dir" - rm -rf "$input_dir/target" - chmod +x "$input_dir/ouch" + mv "$path/target/$platform/release/ouch" "$path" + rm -rf "$path/target" + chmod +x "$path/ouch" - tar czf "../output_assets/${output_name}.tar.gz" "$input_dir" + tar czf "../output_assets/${output_name}.tar.gz" "$path" echo "Created output_assets/${output_name}.tar.gz" fi done From add1793d75af50cd737afe11fa83033f83f7d5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Sun, 20 Apr 2025 13:47:54 -0300 Subject: [PATCH 097/101] Update draft-release-automatic-trigger.yml --- .github/workflows/draft-release-automatic-trigger.yml | 2 +- scripts/package-release-assets.sh | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 scripts/package-release-assets.sh diff --git a/.github/workflows/draft-release-automatic-trigger.yml b/.github/workflows/draft-release-automatic-trigger.yml index 24b45fc..c43d0eb 100644 --- a/.github/workflows/draft-release-automatic-trigger.yml +++ b/.github/workflows/draft-release-automatic-trigger.yml @@ -3,7 +3,7 @@ name: Automatic trigger draft release on: push: tags: - - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" jobs: call-workflow-build-artifacts-and-run-tests: diff --git a/scripts/package-release-assets.sh b/scripts/package-release-assets.sh old mode 100644 new mode 100755 From 2b9da1e4414870fa0c87b0bd711a17d165777c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos?= Date: Sun, 20 Apr 2025 16:21:31 -0300 Subject: [PATCH 098/101] Bump version to 0.6.1 --- CHANGELOG.md | 7 ++++--- Cargo.lock | 2 +- Cargo.toml | 2 +- scripts/package-release-assets.sh | 16 +++++++--------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e622b9c..a80d90c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,16 +18,17 @@ Categories Used: **Bullet points in chronological order by PR** -## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.0...HEAD) +## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.1...HEAD) ### New Features ### Improvements ### Bug Fixes +### Tweaks + +## [0.6.1](https://github.com/ouch-org/ouch/compare/0.6.0...0.6.1) - Fix .zip crash when file mode isn't present [\#804](https://github.com/ouch-org/ouch/pull/804) ([marcospb19](https://github.com/marcospb19)) -### Tweaks - ## [0.6.0](https://github.com/ouch-org/ouch/compare/0.5.1...0.6.0) ### New Features diff --git a/Cargo.lock b/Cargo.lock index 59bd16a..c95299d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,7 +1066,7 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "ouch" -version = "0.6.0" +version = "0.6.1" dependencies = [ "assert_cmd", "atty", diff --git a/Cargo.toml b/Cargo.toml index 1fe8b82..835e55f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouch" -version = "0.6.0" +version = "0.6.1" authors = [ "João Marcos ", "Vinícius Rodrigues Miguel ", diff --git a/scripts/package-release-assets.sh b/scripts/package-release-assets.sh index 65d1255..621f28e 100755 --- a/scripts/package-release-assets.sh +++ b/scripts/package-release-assets.sh @@ -25,15 +25,13 @@ DEFAULT_FEATURES="allow_piped_choice+unrar+use_zlib+use_zstd_thin" for platform in "${PLATFORMS[@]}"; do path="ouch-${platform}" + echo "Processing $path" - if [ ! -d "$path" ]; then + if [ ! -d "${path}-${DEFAULT_FEATURES}" ]; then echo "ERROR: Could not find artifact directory for $platform with default features ($path)" exit 1 fi - - # remove the suffix - mv "ouch-${platform}-${DEFAULT_FEATURES}" "$path" - echo "Processing $path" + mv "${path}-${DEFAULT_FEATURES}" "$path" # remove the annoying suffix cp ../{README.md,LICENSE,CHANGELOG.md} "$path" mkdir -p "$path/man" @@ -47,15 +45,15 @@ for platform in "${PLATFORMS[@]}"; do mv "$path/target/$platform/release/ouch.exe" "$path" rm -rf "$path/target" - zip -r "../output_assets/${output_name}.zip" "$path" - echo "Created output_assets/${output_name}.zip" + zip -r "../output_assets/${path}.zip" "$path" + echo "Created output_assets/${path}.zip" else mv "$path/target/$platform/release/ouch" "$path" rm -rf "$path/target" chmod +x "$path/ouch" - tar czf "../output_assets/${output_name}.tar.gz" "$path" - echo "Created output_assets/${output_name}.tar.gz" + tar czf "../output_assets/${path}.tar.gz" "$path" + echo "Created output_assets/${path}.tar.gz" fi done From c97bb6a2d6ef6c4860a092b5520e70b99dd2c520 Mon Sep 17 00:00:00 2001 From: Talison Fabio <54823205+talis-fb@users.noreply.github.com> Date: Mon, 28 Apr 2025 11:03:50 -0300 Subject: [PATCH 099/101] feat: Add flag '--no-smart-unpack' to disable smart unpack (#809) --- CHANGELOG.md | 1 + src/cli/args.rs | 8 ++ src/commands/decompress.rs | 21 +++- src/commands/mod.rs | 8 +- tests/integration.rs | 200 ++++++++++++++++++++++++++++++++++++- 5 files changed, 229 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a80d90c..97d8057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/cli/args.rs b/src/cli/args.rs index c72d5d3..b28156c 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -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() } diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 036d1d2..5c264ef 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -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> { - 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 diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 3e8718b..dadfd95 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -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| { diff --git a/tests/integration.rs b/tests/integration.rs index 52413a7..ea82326 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -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, +) { + 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::>(); + + 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, +) { + 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::>(); + + 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, +) { + 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::>(); + + 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, +) { + 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::>(); + + 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::>(); From 1ff1932e3d27f972109c4673aa14df3d711db1b9 Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 1 May 2025 15:20:33 +0800 Subject: [PATCH 100/101] Merge folders in decompression (#798) Signed-off-by: tommady --- CHANGELOG.md | 3 ++ src/archive/rar.rs | 2 - src/archive/tar.rs | 1 - src/archive/zip.rs | 2 - src/commands/decompress.rs | 10 ++-- src/commands/mod.rs | 10 ++-- src/utils/fs.rs | 11 +++-- src/utils/question.rs | 52 +++++++++++++++------ tests/integration.rs | 94 ++++++++++++++++++++++++++++++++------ 9 files changed, 140 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d8057..734e684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,10 @@ Categories Used: ## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.1...HEAD) ### New Features + +- 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)) + ### Improvements ### Bug Fixes ### Tweaks diff --git a/src/archive/rar.rs b/src/archive/rar.rs index 37894a0..0846dae 100644 --- a/src/archive/rar.rs +++ b/src/archive/rar.rs @@ -18,8 +18,6 @@ pub fn unpack_archive( password: Option<&[u8]>, quiet: bool, ) -> crate::Result { - assert!(output_folder.read_dir().expect("dir exists").next().is_none()); - let archive = match password { Some(password) => Archive::with_password(archive_path, password), None => Archive::new(archive_path), diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 513d36c..4f457fe 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -24,7 +24,6 @@ use crate::{ /// Unpacks the archive given by `archive` into the folder given by `into`. /// Assumes that output_folder is empty pub fn unpack_archive(reader: Box, output_folder: &Path, quiet: bool) -> crate::Result { - assert!(output_folder.read_dir().expect("dir exists").next().is_none()); let mut archive = tar::Archive::new(reader); let mut files_unpacked = 0; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 9beac49..28546a1 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -37,8 +37,6 @@ pub fn unpack_archive( where R: Read + Seek, { - assert!(output_folder.read_dir().expect("dir exists").next().is_none()); - let mut unpacked_files = 0; for idx in 0..archive.len() { diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 5c264ef..36c8658 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -139,7 +139,11 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { reader = chain_reader_decoder(&first_extension, reader)?; - let mut writer = match utils::ask_to_create_file(&options.output_file_path, options.question_policy)? { + let mut writer = match utils::ask_to_create_file( + &options.output_file_path, + options.question_policy, + QuestionAction::Decompression, + )? { Some(file) => file, None => return Ok(()), }; @@ -331,7 +335,7 @@ fn unpack( let output_dir_cleaned = if is_valid_output_dir { output_dir.to_owned() } else { - match utils::resolve_path_conflict(output_dir, question_policy)? { + match utils::resolve_path_conflict(output_dir, question_policy, QuestionAction::Decompression)? { Some(path) => path, None => return Ok(ControlFlow::Break(())), } @@ -387,7 +391,7 @@ fn smart_unpack( // Before moving, need to check if a file with the same name already exists // If it does, need to ask the user what to do - new_path = match utils::resolve_path_conflict(&new_path, question_policy)? { + new_path = match utils::resolve_path_conflict(&new_path, question_policy, QuestionAction::Decompression)? { Some(path) => path, None => return Ok(ControlFlow::Break(())), }; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index dadfd95..c3363d1 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -20,6 +20,7 @@ use crate::{ list::ListOptions, utils::{ self, colors::*, is_path_stdin, logger::info_accessible, path_to_str, EscapedPathDisplay, FileVisibilityPolicy, + QuestionAction, }, CliArgs, QuestionPolicy, }; @@ -91,10 +92,11 @@ pub fn run( )?; check::check_archive_formats_position(&formats, &output_path)?; - let output_file = match utils::ask_to_create_file(&output_path, question_policy)? { - Some(writer) => writer, - None => return Ok(()), - }; + let output_file = + match utils::ask_to_create_file(&output_path, question_policy, QuestionAction::Compression)? { + Some(writer) => writer, + None => return Ok(()), + }; let level = if fast { Some(1) // Lowest level of compression diff --git a/src/utils/fs.rs b/src/utils/fs.rs index c6ffb8a..eb40625 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -11,7 +11,7 @@ use fs_err as fs; use super::{question::FileConflitOperation, user_wants_to_overwrite}; use crate::{ extension::Extension, - utils::{logger::info_accessible, EscapedPathDisplay}, + utils::{logger::info_accessible, EscapedPathDisplay, QuestionAction}, QuestionPolicy, }; @@ -26,9 +26,13 @@ pub fn is_path_stdin(path: &Path) -> bool { /// * `Ok(None)` means the user wants to cancel the operation /// * `Ok(Some(path))` returns a valid PathBuf without any another file or directory with the same name /// * `Err(_)` is an error -pub fn resolve_path_conflict(path: &Path, question_policy: QuestionPolicy) -> crate::Result> { +pub fn resolve_path_conflict( + path: &Path, + question_policy: QuestionPolicy, + question_action: QuestionAction, +) -> crate::Result> { if path.exists() { - match user_wants_to_overwrite(path, question_policy)? { + match user_wants_to_overwrite(path, question_policy, question_action)? { FileConflitOperation::Cancel => Ok(None), FileConflitOperation::Overwrite => { remove_file_or_dir(path)?; @@ -38,6 +42,7 @@ pub fn resolve_path_conflict(path: &Path, question_policy: QuestionPolicy) -> cr let renamed_path = rename_for_available_filename(path); Ok(Some(renamed_path)) } + FileConflitOperation::Merge => Ok(Some(path.to_path_buf())), } } else { Ok(Some(path.to_path_buf())) diff --git a/src/utils/question.rs b/src/utils/question.rs index 27eb7bb..4b4c97e 100644 --- a/src/utils/question.rs +++ b/src/utils/question.rs @@ -48,49 +48,71 @@ pub enum FileConflitOperation { /// Rename the file /// It'll be put "_1" at the end of the filename or "_2","_3","_4".. if already exists Rename, + /// Merge conflicting folders + Merge, } /// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite. -pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result { +pub fn user_wants_to_overwrite( + path: &Path, + question_policy: QuestionPolicy, + question_action: QuestionAction, +) -> crate::Result { use FileConflitOperation as Op; match question_policy { QuestionPolicy::AlwaysYes => Ok(Op::Overwrite), QuestionPolicy::AlwaysNo => Ok(Op::Cancel), - QuestionPolicy::Ask => ask_file_conflict_operation(path), + QuestionPolicy::Ask => ask_file_conflict_operation(path, question_action), } } /// Ask the user if they want to overwrite or rename the &Path -pub fn ask_file_conflict_operation(path: &Path) -> Result { +pub fn ask_file_conflict_operation(path: &Path, question_action: QuestionAction) -> Result { use FileConflitOperation as Op; let path = path_to_str(strip_cur_dir(path)); - - ChoicePrompt::new( - format!("Do you want to overwrite {path}?"), - [ - ("yes", Op::Overwrite, *colors::GREEN), - ("no", Op::Cancel, *colors::RED), - ("rename", Op::Rename, *colors::BLUE), - ], - ) - .ask() + match question_action { + QuestionAction::Compression => ChoicePrompt::new( + format!("Do you want to overwrite {path}?"), + [ + ("yes", Op::Overwrite, *colors::GREEN), + ("no", Op::Cancel, *colors::RED), + ("rename", Op::Rename, *colors::BLUE), + ], + ) + .ask(), + QuestionAction::Decompression => ChoicePrompt::new( + format!("Do you want to overwrite {path}?"), + [ + ("yes", Op::Overwrite, *colors::GREEN), + ("no", Op::Cancel, *colors::RED), + ("rename", Op::Rename, *colors::BLUE), + ("merge", Op::Merge, *colors::ORANGE), + ], + ) + .ask(), + } } /// Create the file if it doesn't exist and if it does then ask to overwrite it. /// If the user doesn't want to overwrite then we return [`Ok(None)`] -pub fn ask_to_create_file(path: &Path, question_policy: QuestionPolicy) -> Result> { +pub fn ask_to_create_file( + path: &Path, + question_policy: QuestionPolicy, + question_action: QuestionAction, +) -> Result> { match fs::OpenOptions::new().write(true).create_new(true).open(path) { Ok(w) => Ok(Some(w)), Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { let action = match question_policy { QuestionPolicy::AlwaysYes => FileConflitOperation::Overwrite, QuestionPolicy::AlwaysNo => FileConflitOperation::Cancel, - QuestionPolicy::Ask => ask_file_conflict_operation(path)?, + QuestionPolicy::Ask => ask_file_conflict_operation(path, question_action)?, }; match action { + FileConflitOperation::Merge => Ok(Some(fs::File::create(path)?)), FileConflitOperation::Overwrite => { utils::remove_file_or_dir(path)?; Ok(Some(fs::File::create(path)?)) diff --git a/tests/integration.rs b/tests/integration.rs index ea82326..f85e254 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -59,7 +59,7 @@ enum Extension { } /// Converts a list of extension structs to string -fn merge_extensions(ext: impl ToString, exts: Vec) -> String { +fn merge_extensions(ext: impl ToString, exts: &Vec) -> String { once(ext.to_string()) .chain(exts.into_iter().map(|x| x.to_string())) .collect::>() @@ -114,7 +114,7 @@ fn single_empty_file(ext: Extension, #[any(size_range(0..8).lift())] exts: Vec>(); - let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); crate::utils::cargo_bin() .arg("compress") @@ -438,7 +438,7 @@ fn smart_unpack_with_multiple_files( .map(|entry| entry.unwrap().path()) .collect::>(); - let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); let output_path = root_path.join("archive"); assert!(!output_path.exists()); @@ -487,7 +487,7 @@ fn no_smart_unpack_with_single_file( .map(|entry| entry.unwrap().path()) .collect::>(); - let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); let output_path = root_path.join("archive"); assert!(!output_path.exists()); @@ -537,7 +537,7 @@ fn no_smart_unpack_with_multiple_files( .map(|entry| entry.unwrap().path()) .collect::>(); - let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); let output_path = root_path.join("archive"); assert!(!output_path.exists()); @@ -585,7 +585,7 @@ fn multiple_files_with_disabled_smart_unpack_by_dir( let dest_files_path = root_path.join("dest_files"); fs::create_dir_all(&dest_files_path).unwrap(); - let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); crate::utils::cargo_bin() .arg("compress") @@ -702,7 +702,7 @@ fn symlink_pack_and_unpack( files_path.push(symlink_path); - let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, extra_extensions))); + let archive = &root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); crate::utils::cargo_bin() .arg("compress") @@ -798,3 +798,67 @@ fn no_git_folder_after_decompression_with_gitignore_flag_active() { ".git folder should not exist after decompression" ); } + +#[cfg(feature = "allow_piped_choice")] +#[proptest(cases = 25)] +fn unpack_multiple_sources_into_the_same_destination_with_merge( + ext: DirectoryExtension, + #[any(size_range(0..1).lift())] extra_extensions: Vec, +) { + let temp_dir = tempdir()?; + let root_path = temp_dir.path(); + let source_path = root_path + .join(format!("example_{}", merge_extensions(&ext, &extra_extensions))) + .join("sub_a") + .join("sub_b") + .join("sub_c"); + + fs::create_dir_all(&source_path)?; + let archive = root_path.join(format!("archive.{}", merge_extensions(&ext, &extra_extensions))); + crate::utils::cargo_bin() + .arg("compress") + .args([ + fs::File::create(source_path.join("file1.txt"))?.path(), + fs::File::create(source_path.join("file2.txt"))?.path(), + fs::File::create(source_path.join("file3.txt"))?.path(), + ]) + .arg(&archive) + .assert() + .success(); + + fs::remove_dir_all(&source_path)?; + fs::create_dir_all(&source_path)?; + let archive1 = root_path.join(format!("archive1.{}", merge_extensions(&ext, &extra_extensions))); + crate::utils::cargo_bin() + .arg("compress") + .args([ + fs::File::create(source_path.join("file3.txt"))?.path(), + fs::File::create(source_path.join("file4.txt"))?.path(), + fs::File::create(source_path.join("file5.txt"))?.path(), + ]) + .arg(&archive1) + .assert() + .success(); + + let out_path = root_path.join(format!("out_{}", merge_extensions(&ext, &extra_extensions))); + fs::create_dir_all(&out_path)?; + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive) + .arg("-d") + .arg(&out_path) + .assert() + .success(); + + crate::utils::cargo_bin() + .arg("decompress") + .arg(archive1) + .arg("-d") + .arg(&out_path) + .write_stdin("m") + .assert() + .success(); + + assert_eq!(5, out_path.as_path().read_dir()?.count()); +} From 07967927dd7ccb74c03e8d5481694c8c5fe0022f Mon Sep 17 00:00:00 2001 From: Amyspark Date: Sat, 3 May 2025 20:43:59 -0300 Subject: [PATCH 101/101] feat: Make bzip3 optout (#814) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Marcos --- .../workflows/build-artifacts-and-run-tests.yml | 14 +++++++++----- CHANGELOG.md | 2 ++ Cargo.toml | 4 ++-- scripts/package-release-assets.sh | 2 +- src/archive/bzip3_stub.rs | 7 +++++++ src/archive/mod.rs | 2 ++ src/commands/compress.rs | 14 ++++++++++---- src/commands/decompress.rs | 10 +++++++++- src/commands/list.rs | 12 +++++++++--- src/error.rs | 1 + src/extension.rs | 2 +- src/utils/fs.rs | 2 +- tests/integration.rs | 7 +++++-- tests/ui.rs | 1 - 14 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 src/archive/bzip3_stub.rs diff --git a/.github/workflows/build-artifacts-and-run-tests.yml b/.github/workflows/build-artifacts-and-run-tests.yml index 3b84f9d..3b53cff 100644 --- a/.github/workflows/build-artifacts-and-run-tests.yml +++ b/.github/workflows/build-artifacts-and-run-tests.yml @@ -25,7 +25,7 @@ on: type: boolean required: true artifact_upload_mode: - description: "Control which artifacts to upload: 'none' for no uploads, 'with_default_features' to upload only artifacts with default features (use_zlib+use_zstd_thin+unrar), or 'all' to upload all feature combinations." + description: "Control which artifacts to upload: 'none' for no uploads, 'with_default_features' to upload only artifacts with default features (use_zlib+use_zstd_thin+unrar+bzip3), or 'all' to upload all feature combinations." type: string required: true @@ -37,7 +37,8 @@ jobs: strategy: fail-fast: false matrix: - feature-unrar: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[false]')}} + feature-unrar: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[true]')}} + feature-bzip3: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[true]')}} feature-use-zlib: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[false]')}} feature-use-zstd-thin: ${{ inputs.matrix_all_combinations && fromJSON('[true, false]') || fromJSON('[false]')}} target: @@ -76,12 +77,14 @@ jobs: - target: armv7-unknown-linux-musleabihf use-cross: true # features (unless `matrix_all_combinations` is true, we only run these on linux-gnu) - - feature-unrar: true + - feature-unrar: false target: x86_64-unknown-linux-gnu - feature-use-zlib: true target: x86_64-unknown-linux-gnu - feature-use-zstd-thin: true target: x86_64-unknown-linux-gnu + - feature-bzip3: false + target: x86_64-unknown-linux-gnu steps: - name: Checkout @@ -105,6 +108,7 @@ jobs: if [[ "${{ matrix.feature-unrar }}" == true ]]; then FEATURES+=(unrar); fi if [[ "${{ matrix.feature-use-zlib }}" == true ]]; then FEATURES+=(use_zlib); fi if [[ "${{ matrix.feature-use-zstd-thin }}" == true ]]; then FEATURES+=(use_zstd_thin); fi + if [[ "${{ matrix.feature-bzip3 }}" == true ]]; then FEATURES+=(bzip3); fi # Output plus-separated list for artifact names IFS='+' echo "FEATURES_PLUS=${FEATURES[*]}" >> $GITHUB_OUTPUT @@ -127,7 +131,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: - key: "${{ matrix.target }}-${{ matrix.feature-unrar }}-${{ matrix.feature-use-zlib }}-${{ matrix.feature-use-zstd-thin }}" + key: "${{ matrix.target }}-${{ matrix.feature-unrar }}-${{ matrix.feature-use-zlib }}-${{ matrix.feature-use-zstd-thin }}-${{ matrix.feature-bzip3 }}" - name: Test on stable # there's no way to run tests for ARM64 Windows for now @@ -146,7 +150,7 @@ jobs: if: | ${{ inputs.artifact_upload_mode != 'none' && (inputs.artifact_upload_mode == 'all' || - (matrix.feature-unrar && matrix.feature-use-zlib && matrix.feature-use-zstd-thin)) }} + (matrix.feature-unrar && matrix.feature-use-zlib && matrix.feature-use-zstd-thin && matrix.feature-bzip3)) }} uses: actions/upload-artifact@v4 with: name: ouch-${{ matrix.target }}${{ steps.concat-features.outputs.FEATURES_PLUS != '' && format('-{0}', steps.concat-features.outputs.FEATURES_PLUS) || '' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 734e684..432b703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ Categories Used: ### Bug Fixes ### Tweaks +- Make `.bz3` opt-out [\#814](https://github.com/ouch-org/ouch/pull/814) ([amyspark](https://github.com/amyspark)) + ## [0.6.1](https://github.com/ouch-org/ouch/compare/0.6.0...0.6.1) - Fix .zip crash when file mode isn't present [\#804](https://github.com/ouch-org/ouch/pull/804) ([marcospb19](https://github.com/marcospb19)) diff --git a/Cargo.toml b/Cargo.toml index 835e55f..3577a2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ brotli = "7.0.0" bstr = { version = "1.10.0", default-features = false, features = ["std"] } bytesize = "1.3.0" bzip2 = "0.4.4" -bzip3 = { version = "0.9.0", features = ["bundled"] } +bzip3 = { version = "0.9.0", features = ["bundled"] , optional = true } clap = { version = "4.5.20", features = ["derive", "env"] } filetime_creation = "0.2" flate2 = { version = "1.0.30", default-features = false } @@ -70,7 +70,7 @@ regex = "1.10.4" test-strategy = "0.4.0" [features] -default = ["unrar", "use_zlib", "use_zstd_thin"] +default = ["unrar", "use_zlib", "use_zstd_thin", "bzip3"] use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-zlib"] use_zstd_thin = ["zstd/thin"] allow_piped_choice = [] diff --git a/scripts/package-release-assets.sh b/scripts/package-release-assets.sh index 621f28e..9ae9f02 100755 --- a/scripts/package-release-assets.sh +++ b/scripts/package-release-assets.sh @@ -21,7 +21,7 @@ PLATFORMS=( "x86_64-unknown-linux-musl" ) # TODO: remove allow_piped_choice later -DEFAULT_FEATURES="allow_piped_choice+unrar+use_zlib+use_zstd_thin" +DEFAULT_FEATURES="allow_piped_choice+unrar+use_zlib+use_zstd_thin+bzip3" for platform in "${PLATFORMS[@]}"; do path="ouch-${platform}" diff --git a/src/archive/bzip3_stub.rs b/src/archive/bzip3_stub.rs new file mode 100644 index 0000000..e1db197 --- /dev/null +++ b/src/archive/bzip3_stub.rs @@ -0,0 +1,7 @@ +use crate::Error; + +pub fn no_support() -> Error { + Error::UnsupportedFormat { + reason: "BZip3 support is disabled for this build, possibly due to missing bindgen-cli dependency.".into(), + } +} diff --git a/src/archive/mod.rs b/src/archive/mod.rs index 4cfacc0..6412a8a 100644 --- a/src/archive/mod.rs +++ b/src/archive/mod.rs @@ -1,5 +1,7 @@ //! Archive compression algorithms +#[cfg(not(feature = "bzip3"))] +pub mod bzip3_stub; #[cfg(feature = "unrar")] pub mod rar; #[cfg(not(feature = "unrar"))] diff --git a/src/commands/compress.rs b/src/commands/compress.rs index b6f7042..15880bd 100644 --- a/src/commands/compress.rs +++ b/src/commands/compress.rs @@ -57,10 +57,16 @@ pub fn compress_files( encoder, level.map_or_else(Default::default, |l| bzip2::Compression::new((l as u32).clamp(1, 9))), )), - Bzip3 => Box::new( - // Use block size of 16 MiB - bzip3::write::Bz3Encoder::new(encoder, 16 * 2_usize.pow(20))?, - ), + Bzip3 => { + #[cfg(not(feature = "bzip3"))] + return Err(archive::bzip3_stub::no_support()); + + #[cfg(feature = "bzip3")] + Box::new( + // Use block size of 16 MiB + bzip3::write::Bz3Encoder::new(encoder, 16 * 2_usize.pow(20))?, + ) + } Lz4 => Box::new(lz4_flex::frame::FrameEncoder::new(encoder).auto_finish()), Lzma => Box::new(xz2::write::XzEncoder::new( encoder, diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index 36c8658..a108c16 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -6,6 +6,8 @@ use std::{ use fs_err as fs; +#[cfg(not(feature = "bzip3"))] +use crate::archive; use crate::{ commands::{warn_user_about_loading_sevenz_in_memory, warn_user_about_loading_zip_in_memory}, extension::{ @@ -118,7 +120,13 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> { 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)?), + Bzip3 => { + #[cfg(not(feature = "bzip3"))] + return Err(archive::bzip3_stub::no_support()); + + #[cfg(feature = "bzip3")] + Box::new(bzip3::read::Bz3Decoder::new(decoder)?) + } 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)), diff --git a/src/commands/list.rs b/src/commands/list.rs index a64c55c..b3405b4 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -6,7 +6,7 @@ use std::{ use fs_err as fs; use crate::{ - archive::sevenz, + archive, commands::warn_user_about_loading_zip_in_memory, extension::CompressionFormat::{self, *}, list::{self, FileInArchive, ListOptions}, @@ -50,7 +50,13 @@ 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()), + Bzip3 => { + #[cfg(not(feature = "bzip3"))] + return Err(archive::bzip3_stub::no_support()); + + #[cfg(feature = "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 +117,7 @@ pub fn list_archive_contents( } } - Box::new(sevenz::list_archive(archive_path, password)?) + Box::new(archive::sevenz::list_archive(archive_path, password)?) } Gzip | Bzip | Bzip3 | Lz4 | Lzma | Snappy | Zstd | Brotli => { 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/error.rs b/src/error.rs index 3f5bc3e..b3c33df 100644 --- a/src/error.rs +++ b/src/error.rs @@ -200,6 +200,7 @@ impl From for Error { } } +#[cfg(feature = "bzip3")] impl From for Error { fn from(err: bzip3::Error) -> Self { use bzip3::Error as Bz3Error; diff --git a/src/extension.rs b/src/extension.rs index 7250a57..55cdc58 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -286,7 +286,7 @@ mod tests { #[test] /// Test extension parsing for input/output files fn test_separate_known_extensions_from_name() { - let _handler = spawn_logger_thread(); + spawn_logger_thread(); assert_eq!( separate_known_extensions_from_name("file".as_ref()), ("file".as_ref(), vec![]) diff --git a/src/utils/fs.rs b/src/utils/fs.rs index eb40625..f65f152 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -133,7 +133,7 @@ pub fn try_infer_extension(path: &Path) -> Option { buf.starts_with(&[0x42, 0x5A, 0x68]) } fn is_bz3(buf: &[u8]) -> bool { - buf.starts_with(bzip3::MAGIC_NUMBER) + buf.starts_with(b"BZ3v1") } fn is_xz(buf: &[u8]) -> bool { buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]) diff --git a/tests/integration.rs b/tests/integration.rs index f85e254..7536b15 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -25,6 +25,7 @@ enum DirectoryExtension { Tar, Tbz, Tbz2, + #[cfg(feature = "bzip3")] Tbz3, Tgz, Tlz4, @@ -41,6 +42,7 @@ enum DirectoryExtension { enum FileExtension { Bz, Bz2, + #[cfg(feature = "bzip3")] Bz3, Gz, Lz4, @@ -59,9 +61,9 @@ enum Extension { } /// Converts a list of extension structs to string -fn merge_extensions(ext: impl ToString, exts: &Vec) -> String { +fn merge_extensions(ext: impl ToString, exts: &[FileExtension]) -> String { once(ext.to_string()) - .chain(exts.into_iter().map(|x| x.to_string())) + .chain(exts.iter().map(|x| x.to_string())) .collect::>() .join(".") } @@ -89,6 +91,7 @@ fn create_random_files(dir: impl Into, depth: u8, rng: &mut SmallRng) { } /// Create n random files on directory dir +#[cfg_attr(not(feature = "allow_piped_choice"), allow(dead_code))] fn create_n_random_files(n: usize, dir: impl Into, rng: &mut SmallRng) { let dir: &PathBuf = &dir.into(); diff --git a/tests/ui.rs b/tests/ui.rs index 9492d1a..6df0794 100644 --- a/tests/ui.rs +++ b/tests/ui.rs @@ -2,7 +2,6 @@ /// /// See CONTRIBUTING.md for a brief guide on how to use [`insta`] for these tests. /// [`insta`]: https://docs.rs/insta - #[macro_use] mod utils;