this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: adding support for unencrypted zip files

+656 -1
+280
Cargo.lock
··· 9 9 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 10 11 11 [[package]] 12 + name = "aes" 13 + version = "0.8.4" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 16 + dependencies = [ 17 + "cfg-if", 18 + "cipher", 19 + "cpufeatures", 20 + ] 21 + 22 + [[package]] 12 23 name = "aho-corasick" 13 24 version = "1.1.3" 14 25 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 103 114 checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 104 115 105 116 [[package]] 117 + name = "base64ct" 118 + version = "1.6.0" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 121 + 122 + [[package]] 106 123 name = "bitflags" 107 124 version = "1.3.2" 108 125 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 115 132 checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 116 133 117 134 [[package]] 135 + name = "block-buffer" 136 + version = "0.10.4" 137 + source = "registry+https://github.com/rust-lang/crates.io-index" 138 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 139 + dependencies = [ 140 + "generic-array", 141 + ] 142 + 143 + [[package]] 118 144 name = "bstr" 119 145 version = "1.9.1" 120 146 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 124 150 "regex-automata", 125 151 "serde", 126 152 ] 153 + 154 + [[package]] 155 + name = "byteorder" 156 + version = "1.5.0" 157 + source = "registry+https://github.com/rust-lang/crates.io-index" 158 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 127 159 128 160 [[package]] 129 161 name = "bzip2" ··· 151 183 version = "1.0.99" 152 184 source = "registry+https://github.com/rust-lang/crates.io-index" 153 185 checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" 186 + dependencies = [ 187 + "jobserver", 188 + "libc", 189 + "once_cell", 190 + ] 154 191 155 192 [[package]] 156 193 name = "cfg-if" 157 194 version = "1.0.0" 158 195 source = "registry+https://github.com/rust-lang/crates.io-index" 159 196 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 197 + 198 + [[package]] 199 + name = "cipher" 200 + version = "0.4.4" 201 + source = "registry+https://github.com/rust-lang/crates.io-index" 202 + checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 203 + dependencies = [ 204 + "crypto-common", 205 + "inout", 206 + ] 160 207 161 208 [[package]] 162 209 name = "clap" ··· 212 259 "predicates", 213 260 "rand", 214 261 "tar", 262 + "tempfile", 215 263 "xz2", 264 + "zip", 216 265 ] 217 266 218 267 [[package]] ··· 235 284 ] 236 285 237 286 [[package]] 287 + name = "constant_time_eq" 288 + version = "0.1.5" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 291 + 292 + [[package]] 293 + name = "cpufeatures" 294 + version = "0.2.17" 295 + source = "registry+https://github.com/rust-lang/crates.io-index" 296 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 297 + dependencies = [ 298 + "libc", 299 + ] 300 + 301 + [[package]] 238 302 name = "crc32fast" 239 303 version = "1.4.2" 240 304 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 269 333 checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 270 334 271 335 [[package]] 336 + name = "crypto-common" 337 + version = "0.1.6" 338 + source = "registry+https://github.com/rust-lang/crates.io-index" 339 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 340 + dependencies = [ 341 + "generic-array", 342 + "typenum", 343 + ] 344 + 345 + [[package]] 346 + name = "deranged" 347 + version = "0.3.11" 348 + source = "registry+https://github.com/rust-lang/crates.io-index" 349 + checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 350 + dependencies = [ 351 + "powerfmt", 352 + ] 353 + 354 + [[package]] 272 355 name = "difflib" 273 356 version = "0.4.0" 274 357 source = "registry+https://github.com/rust-lang/crates.io-index" 275 358 checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 276 359 277 360 [[package]] 361 + name = "digest" 362 + version = "0.10.7" 363 + source = "registry+https://github.com/rust-lang/crates.io-index" 364 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 365 + dependencies = [ 366 + "block-buffer", 367 + "crypto-common", 368 + "subtle", 369 + ] 370 + 371 + [[package]] 278 372 name = "doc-comment" 279 373 version = "0.3.3" 280 374 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 334 428 ] 335 429 336 430 [[package]] 431 + name = "generic-array" 432 + version = "0.14.7" 433 + source = "registry+https://github.com/rust-lang/crates.io-index" 434 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 435 + dependencies = [ 436 + "typenum", 437 + "version_check", 438 + ] 439 + 440 + [[package]] 337 441 name = "getrandom" 338 442 version = "0.2.15" 339 443 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 381 485 checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 382 486 383 487 [[package]] 488 + name = "hmac" 489 + version = "0.12.1" 490 + source = "registry+https://github.com/rust-lang/crates.io-index" 491 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 492 + dependencies = [ 493 + "digest", 494 + ] 495 + 496 + [[package]] 384 497 name = "ignore" 385 498 version = "0.4.22" 386 499 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 410 523 ] 411 524 412 525 [[package]] 526 + name = "inout" 527 + version = "0.1.4" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 530 + dependencies = [ 531 + "generic-array", 532 + ] 533 + 534 + [[package]] 413 535 name = "instant" 414 536 version = "0.1.13" 415 537 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 436 558 checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 437 559 438 560 [[package]] 561 + name = "jobserver" 562 + version = "0.1.32" 563 + source = "registry+https://github.com/rust-lang/crates.io-index" 564 + checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 565 + dependencies = [ 566 + "libc", 567 + ] 568 + 569 + [[package]] 439 570 name = "lazy_static" 440 571 version = "1.4.0" 441 572 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 492 623 checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 493 624 494 625 [[package]] 626 + name = "num-conv" 627 + version = "0.1.0" 628 + source = "registry+https://github.com/rust-lang/crates.io-index" 629 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 630 + 631 + [[package]] 495 632 name = "num-traits" 496 633 version = "0.2.19" 497 634 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 507 644 checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 508 645 509 646 [[package]] 647 + name = "once_cell" 648 + version = "1.20.3" 649 + source = "registry+https://github.com/rust-lang/crates.io-index" 650 + checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 651 + 652 + [[package]] 653 + name = "password-hash" 654 + version = "0.4.2" 655 + source = "registry+https://github.com/rust-lang/crates.io-index" 656 + checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" 657 + dependencies = [ 658 + "base64ct", 659 + "rand_core", 660 + "subtle", 661 + ] 662 + 663 + [[package]] 664 + name = "pbkdf2" 665 + version = "0.11.0" 666 + source = "registry+https://github.com/rust-lang/crates.io-index" 667 + checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" 668 + dependencies = [ 669 + "digest", 670 + "hmac", 671 + "password-hash", 672 + "sha2", 673 + ] 674 + 675 + [[package]] 510 676 name = "pkg-config" 511 677 version = "0.3.30" 512 678 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 517 683 version = "1.6.0" 518 684 source = "registry+https://github.com/rust-lang/crates.io-index" 519 685 checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 686 + 687 + [[package]] 688 + name = "powerfmt" 689 + version = "0.2.0" 690 + source = "registry+https://github.com/rust-lang/crates.io-index" 691 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 520 692 521 693 [[package]] 522 694 name = "ppv-lite86" ··· 683 855 ] 684 856 685 857 [[package]] 858 + name = "sha1" 859 + version = "0.10.6" 860 + source = "registry+https://github.com/rust-lang/crates.io-index" 861 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 862 + dependencies = [ 863 + "cfg-if", 864 + "cpufeatures", 865 + "digest", 866 + ] 867 + 868 + [[package]] 869 + name = "sha2" 870 + version = "0.10.8" 871 + source = "registry+https://github.com/rust-lang/crates.io-index" 872 + checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 873 + dependencies = [ 874 + "cfg-if", 875 + "cpufeatures", 876 + "digest", 877 + ] 878 + 879 + [[package]] 686 880 name = "strsim" 687 881 version = "0.11.1" 688 882 source = "registry+https://github.com/rust-lang/crates.io-index" 689 883 checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 884 + 885 + [[package]] 886 + name = "subtle" 887 + version = "2.6.1" 888 + source = "registry+https://github.com/rust-lang/crates.io-index" 889 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 690 890 691 891 [[package]] 692 892 name = "syn" ··· 729 929 checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 730 930 731 931 [[package]] 932 + name = "time" 933 + version = "0.3.39" 934 + source = "registry+https://github.com/rust-lang/crates.io-index" 935 + checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" 936 + dependencies = [ 937 + "deranged", 938 + "num-conv", 939 + "powerfmt", 940 + "serde", 941 + "time-core", 942 + ] 943 + 944 + [[package]] 945 + name = "time-core" 946 + version = "0.1.3" 947 + source = "registry+https://github.com/rust-lang/crates.io-index" 948 + checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" 949 + 950 + [[package]] 951 + name = "typenum" 952 + version = "1.18.0" 953 + source = "registry+https://github.com/rust-lang/crates.io-index" 954 + checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 955 + 956 + [[package]] 732 957 name = "unicode-ident" 733 958 version = "1.0.12" 734 959 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 745 970 version = "0.2.2" 746 971 source = "registry+https://github.com/rust-lang/crates.io-index" 747 972 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 973 + 974 + [[package]] 975 + name = "version_check" 976 + version = "0.9.5" 977 + source = "registry+https://github.com/rust-lang/crates.io-index" 978 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 748 979 749 980 [[package]] 750 981 name = "wait-timeout" ··· 872 1103 dependencies = [ 873 1104 "lzma-sys", 874 1105 ] 1106 + 1107 + [[package]] 1108 + name = "zip" 1109 + version = "0.6.6" 1110 + source = "registry+https://github.com/rust-lang/crates.io-index" 1111 + checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" 1112 + dependencies = [ 1113 + "aes", 1114 + "byteorder", 1115 + "bzip2", 1116 + "constant_time_eq", 1117 + "crc32fast", 1118 + "crossbeam-utils", 1119 + "flate2", 1120 + "hmac", 1121 + "pbkdf2", 1122 + "sha1", 1123 + "time", 1124 + "zstd", 1125 + ] 1126 + 1127 + [[package]] 1128 + name = "zstd" 1129 + version = "0.11.2+zstd.1.5.2" 1130 + source = "registry+https://github.com/rust-lang/crates.io-index" 1131 + checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" 1132 + dependencies = [ 1133 + "zstd-safe", 1134 + ] 1135 + 1136 + [[package]] 1137 + name = "zstd-safe" 1138 + version = "5.0.2+zstd.1.5.2" 1139 + source = "registry+https://github.com/rust-lang/crates.io-index" 1140 + checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" 1141 + dependencies = [ 1142 + "libc", 1143 + "zstd-sys", 1144 + ] 1145 + 1146 + [[package]] 1147 + name = "zstd-sys" 1148 + version = "2.0.14+zstd.1.5.7" 1149 + source = "registry+https://github.com/rust-lang/crates.io-index" 1150 + checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" 1151 + dependencies = [ 1152 + "cc", 1153 + "pkg-config", 1154 + ]
+2
Cargo.toml
··· 18 18 is-terminal = "0.4" 19 19 tar = "0.4" 20 20 xz2 = "0.1" 21 + zip = "0.6" 22 + tempfile = "3.10" 21 23 22 24 [dev-dependencies] 23 25 assert_cmd = "2"
+1
README.md
··· 20 20 - gzip 21 21 - tar 22 22 - xz 23 + - zip 23 24 24 25 ## Install 25 26
+7 -1
src/main.rs
··· 4 4 mod tar; 5 5 mod utils; 6 6 mod xz; 7 + mod zip; 7 8 8 9 use bzip2::{Bzip2, Bzip2Args}; 9 10 use clap::{Parser, Subcommand}; ··· 14 15 use tar::{Tar, TarArgs}; 15 16 use utils::*; 16 17 use xz::{Xz, XzArgs}; 18 + use zip::{Zip, ZipArgs}; 17 19 18 20 /// A compression multi-tool 19 21 #[derive(Parser, Debug)] ··· 27 29 #[clap(flatten)] 28 30 pub base_args: CommonArgs, 29 31 } 30 - 31 32 #[derive(Subcommand, Debug)] 32 33 enum Format { 33 34 /// tar archive format ··· 43 44 /// bzip2 compression 44 45 #[clap(visible_alias = "bz2")] 45 46 Bzip2(Bzip2Args), 47 + 48 + /// zip archive format 49 + Zip(ZipArgs), 46 50 } 47 51 48 52 /// Get the input filename or return a default file ··· 86 90 Box::<Gzip>::default(), 87 91 Box::<Xz>::default(), 88 92 Box::<Bzip2>::default(), 93 + Box::<Zip>::default(), 89 94 ]; 90 95 compressors.into_iter().find(|c| c.is_archive(filename)) 91 96 } ··· 495 500 Some(Format::Gzip(a)) => command(Some(Box::new(Gzip::new(&a))), &a.common_args), 496 501 Some(Format::Xz(a)) => command(Some(Box::new(Xz::new(&a))), &a.common_args), 497 502 Some(Format::Bzip2(a)) => command(Some(Box::new(Bzip2::new(&a))), &a.common_args), 503 + Some(Format::Zip(a)) => command(Some(Box::new(Zip::new(&a))), &a.common_args), 498 504 _ => command(None, &args.base_args), 499 505 } 500 506 .unwrap_or_else(|e| {
+237
src/zip.rs
··· 1 + use crate::utils::*; 2 + use clap::Args; 3 + use std::fs::File; 4 + use std::io::{self, Seek, SeekFrom, Write}; 5 + use std::path::Path; 6 + use tempfile::tempfile; 7 + use zip::read::ZipArchive; 8 + use zip::write::FileOptions; 9 + use zip::{CompressionMethod, ZipWriter}; 10 + 11 + #[derive(Args, Debug)] 12 + pub struct ZipArgs { 13 + #[clap(flatten)] 14 + pub common_args: CommonArgs, 15 + } 16 + 17 + #[derive(Default)] 18 + pub struct Zip {} 19 + 20 + impl Zip { 21 + pub fn new(_args: &ZipArgs) -> Zip { 22 + Zip {} 23 + } 24 + 25 + fn compress_to_file<W: Write + Seek>( 26 + &self, 27 + input: CmprssInput, 28 + writer: W, 29 + ) -> Result<(), io::Error> { 30 + let mut zip_writer = ZipWriter::new(writer); 31 + let options = FileOptions::default().compression_method(CompressionMethod::Deflated); 32 + 33 + match input { 34 + CmprssInput::Path(paths) => { 35 + for path in paths { 36 + if path.is_file() { 37 + let name = path.file_name().unwrap().to_string_lossy(); 38 + zip_writer.start_file(name, options)?; 39 + let mut f = File::open(&path)?; 40 + io::copy(&mut f, &mut zip_writer)?; 41 + } else if path.is_dir() { 42 + // Use the directory as the base and add its contents 43 + let base = path.parent().unwrap_or(&path); 44 + add_directory(&mut zip_writer, base, &path)?; 45 + } else { 46 + return cmprss_error("unsupported file type for zip compression"); 47 + } 48 + } 49 + } 50 + CmprssInput::Pipe(mut pipe) => { 51 + // For pipe input, we'll create a single file named "archive" 52 + zip_writer.start_file("archive", options)?; 53 + io::copy(&mut pipe, &mut zip_writer)?; 54 + } 55 + } 56 + 57 + zip_writer.finish()?; 58 + Ok(()) 59 + } 60 + } 61 + 62 + impl Compressor for Zip { 63 + fn name(&self) -> &str { 64 + "zip" 65 + } 66 + 67 + fn default_extracted_filename(&self, in_path: &Path) -> String { 68 + if let Some(stem) = in_path.file_stem() { 69 + stem.to_string_lossy().into_owned() 70 + } else { 71 + ".".to_string() 72 + } 73 + } 74 + 75 + fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 76 + match output { 77 + CmprssOutput::Path(ref path) => { 78 + let file = File::create(path)?; 79 + self.compress_to_file(input, file) 80 + } 81 + CmprssOutput::Pipe(mut pipe) => { 82 + // Create a temporary file to write the zip to 83 + let mut temp_file = tempfile()?; 84 + self.compress_to_file(input, &mut temp_file)?; 85 + 86 + // Reset the file position to the beginning 87 + temp_file.seek(SeekFrom::Start(0))?; 88 + 89 + // Copy the temporary file to the pipe 90 + io::copy(&mut temp_file, &mut pipe)?; 91 + Ok(()) 92 + } 93 + } 94 + } 95 + 96 + fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 97 + match output { 98 + CmprssOutput::Path(ref out_dir) => { 99 + // Create the output directory if it doesn't exist 100 + if !out_dir.exists() { 101 + std::fs::create_dir_all(out_dir)?; 102 + } else if !out_dir.is_dir() { 103 + return cmprss_error("zip extraction output must be a directory"); 104 + } 105 + 106 + match input { 107 + CmprssInput::Path(paths) => { 108 + if paths.len() != 1 { 109 + return cmprss_error("zip extraction expects a single archive file"); 110 + } 111 + let file = File::open(&paths[0])?; 112 + let mut archive = ZipArchive::new(file)?; 113 + archive 114 + .extract(out_dir) 115 + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 116 + } 117 + CmprssInput::Pipe(mut pipe) => { 118 + // Create a temporary file to store the zip content 119 + let mut temp_file = tempfile()?; 120 + 121 + // Copy from pipe to temporary file 122 + io::copy(&mut pipe, &mut temp_file)?; 123 + 124 + // Reset the file position to the beginning 125 + temp_file.seek(SeekFrom::Start(0))?; 126 + 127 + // Extract from the temporary file 128 + let mut archive = ZipArchive::new(temp_file)?; 129 + archive 130 + .extract(out_dir) 131 + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 132 + } 133 + } 134 + } 135 + CmprssOutput::Pipe(_) => cmprss_error("zip extraction to stdout is not supported"), 136 + } 137 + } 138 + } 139 + 140 + fn add_directory<W: Write + Seek>( 141 + zip: &mut ZipWriter<W>, 142 + base: &Path, 143 + path: &Path, 144 + ) -> Result<(), io::Error> { 145 + for entry in std::fs::read_dir(path)? { 146 + let entry = entry?; 147 + let entry_path = entry.path(); 148 + // Get relative path for archive entry 149 + let name = entry_path 150 + .strip_prefix(base) 151 + .unwrap() 152 + .to_string_lossy() 153 + .replace('\\', "/"); 154 + if entry_path.is_file() { 155 + let options = FileOptions::default().compression_method(CompressionMethod::Deflated); 156 + zip.start_file(name, options)?; 157 + let mut f = File::open(&entry_path)?; 158 + io::copy(&mut f, zip)?; 159 + } else if entry_path.is_dir() { 160 + // Ensure directory entry ends with '/' 161 + let dir_name = name.clone() + "/"; 162 + zip.add_directory( 163 + dir_name, 164 + FileOptions::default().compression_method(CompressionMethod::Deflated), 165 + )?; 166 + add_directory(zip, base, &entry_path)?; 167 + } 168 + } 169 + Ok(()) 170 + } 171 + 172 + #[cfg(test)] 173 + mod tests { 174 + use super::*; 175 + use assert_fs::prelude::*; 176 + use predicates::prelude::*; 177 + use std::path::PathBuf; 178 + 179 + #[test] 180 + fn roundtrip_file() -> Result<(), Box<dyn std::error::Error>> { 181 + let compressor = Zip::default(); 182 + let file = assert_fs::NamedTempFile::new("test.txt")?; 183 + file.write_str("test data for zip")?; 184 + let working_dir = assert_fs::TempDir::new()?; 185 + let archive = working_dir.child("archive.zip"); 186 + archive.assert(predicate::path::missing()); 187 + 188 + compressor.compress( 189 + CmprssInput::Path(vec![file.path().to_path_buf()]), 190 + CmprssOutput::Path(archive.path().to_path_buf()), 191 + )?; 192 + archive.assert(predicate::path::is_file()); 193 + 194 + let extract_dir = working_dir.child("out"); 195 + std::fs::create_dir_all(extract_dir.path())?; 196 + compressor.extract( 197 + CmprssInput::Path(vec![archive.path().to_path_buf()]), 198 + CmprssOutput::Path(extract_dir.path().to_path_buf()), 199 + )?; 200 + extract_dir 201 + .child("test.txt") 202 + .assert(predicate::path::eq_file(file.path())); 203 + Ok(()) 204 + } 205 + 206 + #[test] 207 + fn roundtrip_directory() -> Result<(), Box<dyn std::error::Error>> { 208 + let compressor = Zip::default(); 209 + let dir = assert_fs::TempDir::new()?; 210 + let file_path = dir.child("file.txt"); 211 + file_path.write_str("directory test data")?; 212 + let working_dir = assert_fs::TempDir::new()?; 213 + let archive = working_dir.child("dir_archive.zip"); 214 + archive.assert(predicate::path::missing()); 215 + 216 + compressor.compress( 217 + CmprssInput::Path(vec![dir.path().to_path_buf()]), 218 + CmprssOutput::Path(archive.path().to_path_buf()), 219 + )?; 220 + archive.assert(predicate::path::is_file()); 221 + 222 + let extract_dir = working_dir.child("extracted"); 223 + std::fs::create_dir_all(extract_dir.path())?; 224 + compressor.extract( 225 + CmprssInput::Path(vec![archive.path().to_path_buf()]), 226 + CmprssOutput::Path(extract_dir.path().to_path_buf()), 227 + )?; 228 + // When extracting a directory from a zip, the directory name is included in the path 229 + // Since the archive stores the entire directory, the extracted file is contained in the directory 230 + let dir_name: PathBuf = dir.path().file_name().unwrap().into(); 231 + extract_dir 232 + .child(dir_name) 233 + .child("file.txt") 234 + .assert(predicate::path::eq_file(file_path.path())); 235 + Ok(()) 236 + } 237 + }
+129
tests/cli.rs
··· 4 4 use predicates::prelude::*; 5 5 use std::{ 6 6 fs::File, 7 + path::PathBuf, 7 8 process::{Command, Stdio}, 8 9 }; 9 10 ··· 524 525 working_dir 525 526 .child("out.txt") 526 527 .assert(predicate::path::eq_file(file.path())); 528 + 529 + Ok(()) 530 + } 531 + 532 + /// Zip roundtrip with a single file 533 + /// 534 + /// ``` bash 535 + /// cmprss zip test.txt archive.zip 536 + /// cmprss zip --extract archive.zip . 537 + /// ``` 538 + #[test] 539 + fn zip_roundtrip_explicit() -> Result<(), Box<dyn std::error::Error>> { 540 + let file = assert_fs::NamedTempFile::new("test.txt")?; 541 + file.write_str("garbage data for testing")?; 542 + let working_dir = assert_fs::TempDir::new()?; 543 + let archive = working_dir.child("archive.zip"); 544 + archive.assert(predicate::path::missing()); 545 + 546 + let mut compress = Command::cargo_bin("cmprss")?; 547 + compress.arg("zip").arg(file.path()).arg(archive.path()); 548 + compress.assert().success(); 549 + archive.assert(predicate::path::is_file()); 550 + 551 + let mut extract = Command::cargo_bin("cmprss")?; 552 + extract 553 + .arg("zip") 554 + .arg("--extract") 555 + .arg(archive.path()) 556 + .arg(working_dir.path()); 557 + extract.assert().success(); 558 + 559 + // Assert the files are identical 560 + working_dir 561 + .child("test.txt") 562 + .assert(predicate::path::eq_file(file.path())); 563 + 564 + Ok(()) 565 + } 566 + 567 + /// Zip roundtrip with multiple files 568 + /// 569 + /// ``` bash 570 + /// cmprss zip test.txt test2.txt archive.zip 571 + /// cmprss zip --extract archive.zip . 572 + /// ``` 573 + #[test] 574 + fn zip_roundtrip_explicit_two() -> Result<(), Box<dyn std::error::Error>> { 575 + let file = assert_fs::NamedTempFile::new("test.txt")?; 576 + file.write_str("garbage data for testing")?; 577 + let file2 = assert_fs::NamedTempFile::new("test2.txt")?; 578 + file2.write_str("more garbage data for testing")?; 579 + let working_dir = assert_fs::TempDir::new()?; 580 + let archive = working_dir.child("archive.zip"); 581 + archive.assert(predicate::path::missing()); 582 + 583 + let mut compress = Command::cargo_bin("cmprss")?; 584 + compress 585 + .arg("zip") 586 + .arg(file.path()) 587 + .arg(file2.path()) 588 + .arg(archive.path()); 589 + compress.assert().success(); 590 + archive.assert(predicate::path::is_file()); 591 + 592 + let mut extract = Command::cargo_bin("cmprss")?; 593 + extract 594 + .arg("zip") 595 + .arg("--extract") 596 + .arg(archive.path()) 597 + .arg(working_dir.path()); 598 + extract.assert().success(); 599 + 600 + // Assert the files are identical 601 + working_dir 602 + .child("test.txt") 603 + .assert(predicate::path::eq_file(file.path())); 604 + working_dir 605 + .child("test2.txt") 606 + .assert(predicate::path::eq_file(file2.path())); 607 + 608 + Ok(()) 609 + } 610 + 611 + /// Zip roundtrip with a directory 612 + /// 613 + /// ``` bash 614 + /// cmprss zip directory archive.zip 615 + /// cmprss zip --extract archive.zip output_dir 616 + /// ``` 617 + #[test] 618 + fn zip_roundtrip_directory() -> Result<(), Box<dyn std::error::Error>> { 619 + let dir = assert_fs::TempDir::new()?; 620 + let file = dir.child("test.txt"); 621 + file.write_str("garbage data for testing")?; 622 + let file2 = dir.child("test2.txt"); 623 + file2.write_str("more garbage data for testing")?; 624 + 625 + let working_dir = assert_fs::TempDir::new()?; 626 + let archive = working_dir.child("archive.zip"); 627 + archive.assert(predicate::path::missing()); 628 + 629 + let mut compress = Command::cargo_bin("cmprss")?; 630 + compress.arg("zip").arg(dir.path()).arg(archive.path()); 631 + compress.assert().success(); 632 + archive.assert(predicate::path::is_file()); 633 + 634 + let extract_dir = working_dir.child("output"); 635 + std::fs::create_dir_all(extract_dir.path())?; 636 + 637 + let mut extract = Command::cargo_bin("cmprss")?; 638 + extract 639 + .arg("zip") 640 + .arg("--extract") 641 + .arg(archive.path()) 642 + .arg(extract_dir.path()); 643 + extract.assert().success(); 644 + 645 + // Assert the files are identical 646 + // Since the archive stores the entire directory, the extracted file is contained in the directory 647 + let dir_name: PathBuf = dir.path().file_name().unwrap().into(); 648 + extract_dir 649 + .child(&dir_name) 650 + .child("test.txt") 651 + .assert(predicate::path::eq_file(file.path())); 652 + extract_dir 653 + .child(&dir_name) 654 + .child("test2.txt") 655 + .assert(predicate::path::eq_file(file2.path())); 527 656 528 657 Ok(()) 529 658 }