this repo has no description
0
fork

Configure Feed

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

feat(xz): add xz support

+293
+36
Cargo.lock
··· 125 125 ] 126 126 127 127 [[package]] 128 + name = "cc" 129 + version = "1.0.83" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 132 + dependencies = [ 133 + "libc", 134 + ] 135 + 136 + [[package]] 128 137 name = "cfg-if" 129 138 version = "1.0.0" 130 139 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 181 190 "is-terminal", 182 191 "predicates", 183 192 "tar", 193 + "xz2", 184 194 ] 185 195 186 196 [[package]] ··· 387 397 checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 388 398 389 399 [[package]] 400 + name = "lzma-sys" 401 + version = "0.1.20" 402 + source = "registry+https://github.com/rust-lang/crates.io-index" 403 + checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" 404 + dependencies = [ 405 + "cc", 406 + "libc", 407 + "pkg-config", 408 + ] 409 + 410 + [[package]] 390 411 name = "memchr" 391 412 version = "2.6.4" 392 413 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 424 445 dependencies = [ 425 446 "autocfg", 426 447 ] 448 + 449 + [[package]] 450 + name = "pkg-config" 451 + version = "0.3.27" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 427 454 428 455 [[package]] 429 456 name = "predicates" ··· 818 845 dependencies = [ 819 846 "libc", 820 847 ] 848 + 849 + [[package]] 850 + name = "xz2" 851 + version = "0.1.7" 852 + source = "registry+https://github.com/rust-lang/crates.io-index" 853 + checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" 854 + dependencies = [ 855 + "lzma-sys", 856 + ]
+1
Cargo.toml
··· 16 16 flate2 = "1.0" 17 17 tar = "0.4.38" 18 18 is-terminal = "0.4" 19 + xz2 = "0.1.7" 19 20 20 21 [dev-dependencies] 21 22 assert_cmd = "2.0.11"
+1
README.md
··· 56 56 57 57 - gzip 58 58 - tar 59 + - xz 59 60 60 61 # Contributing 61 62
+21
src/main.rs
··· 1 1 mod gzip; 2 2 mod tar; 3 3 mod utils; 4 + mod xz; 4 5 5 6 use clap::{Args, Parser, Subcommand}; 6 7 use is_terminal::IsTerminal; ··· 28 29 /// gzip compression 29 30 #[clap(visible_alias = "gz")] 30 31 Gzip(GzipArgs), 32 + 33 + /// xz compression 34 + Xz(XzArgs), 31 35 } 32 36 33 37 #[derive(Args, Debug)] ··· 93 97 /// This is an int 0-9, with 0 being no compression and 9 being highest compression. 94 98 #[arg(long, default_value_t = 6)] 95 99 compression: u32, 100 + } 101 + 102 + #[derive(Args, Debug)] 103 + struct XzArgs { 104 + #[clap(flatten)] 105 + common_args: CommonArgs, 106 + 107 + /// Level of compression 108 + /// 109 + /// This is an int 0-9, with 0 being no compression and 9 being highest compression. 110 + #[arg(long, default_value_t = 6)] 111 + level: u32, 96 112 } 97 113 98 114 /// Get the input filename or return a default file ··· 269 285 } 270 286 } 271 287 288 + fn parse_xz(args: &XzArgs) -> xz::Xz { 289 + xz::Xz { level: args.level } 290 + } 291 + 272 292 fn parse_tar(_args: &TarArgs) -> tar::Tar { 273 293 tar::Tar {} 274 294 } ··· 279 299 Some(Format::Tar(a)) => command(parse_tar(&a), &a.common_args), 280 300 //Some(Format::Extract(a)) => command_extract(a), 281 301 Some(Format::Gzip(a)) => command(parse_gzip(&a), &a.common_args), 302 + Some(Format::Xz(a)) => command(parse_xz(&a), &a.common_args), 282 303 _ => Err(io::Error::new(io::ErrorKind::Other, "unknown input")), 283 304 } 284 305 }
+102
src/xz.rs
··· 1 + use crate::utils::*; 2 + use std::{ 3 + fs::File, 4 + io::{self, Read, Write}, 5 + }; 6 + use xz2::write::{XzDecoder, XzEncoder}; 7 + 8 + pub struct Xz { 9 + pub level: u32, 10 + } 11 + 12 + impl Default for Xz { 13 + fn default() -> Self { 14 + Xz { level: 6 } 15 + } 16 + } 17 + 18 + impl Compressor for Xz { 19 + /// The standard extension for the xz format. 20 + fn extension(&self) -> &str { 21 + "xz" 22 + } 23 + 24 + /// Full name for xz. 25 + fn name(&self) -> &str { 26 + "xz" 27 + } 28 + 29 + fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 30 + let mut input_stream = match input { 31 + CmprssInput::Path(paths) => { 32 + if paths.len() > 1 { 33 + return cmprss_error("only 1 file can be compressed at a time"); 34 + } 35 + Box::new(File::open(paths[0].as_path())?) 36 + } 37 + CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 38 + }; 39 + let output_stream: Box<dyn Write + Send> = match output { 40 + CmprssOutput::Path(path) => Box::new(File::create(path)?), 41 + CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 42 + }; 43 + let mut encoder = XzEncoder::new(output_stream, self.level); 44 + io::copy(&mut input_stream, &mut encoder)?; 45 + Ok(()) 46 + } 47 + 48 + fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 49 + let mut input_stream = match input { 50 + CmprssInput::Path(paths) => { 51 + if paths.len() > 1 { 52 + return cmprss_error("only 1 file can be extracted at a time"); 53 + } 54 + Box::new(File::open(paths[0].as_path())?) 55 + } 56 + CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 57 + }; 58 + let output_stream: Box<dyn Write + Send> = match output { 59 + CmprssOutput::Path(path) => Box::new(File::create(path)?), 60 + CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 61 + }; 62 + let mut decoder = XzDecoder::new(output_stream); 63 + io::copy(&mut input_stream, &mut decoder)?; 64 + Ok(()) 65 + } 66 + } 67 + 68 + #[cfg(test)] 69 + mod tests { 70 + use super::*; 71 + use assert_fs::prelude::*; 72 + use predicates::prelude::*; 73 + 74 + #[test] 75 + fn roundtrip() -> Result<(), Box<dyn std::error::Error>> { 76 + let compressor = Xz::default(); 77 + 78 + let file = assert_fs::NamedTempFile::new("test.txt")?; 79 + file.write_str("garbage data for testing")?; 80 + let working_dir = assert_fs::TempDir::new()?; 81 + let archive = working_dir.child("archive.".to_owned() + compressor.extension()); 82 + archive.assert(predicate::path::missing()); 83 + 84 + // Roundtrip compress/extract 85 + compressor.compress( 86 + CmprssInput::Path(vec![file.path().to_path_buf()]), 87 + CmprssOutput::Path(archive.path().to_path_buf()), 88 + )?; 89 + archive.assert(predicate::path::is_file()); 90 + compressor.extract( 91 + CmprssInput::Path(vec![archive.path().to_path_buf()]), 92 + CmprssOutput::Path(working_dir.child("test.txt").path().to_path_buf()), 93 + )?; 94 + 95 + // Assert the files are identical 96 + working_dir 97 + .child("test.txt") 98 + .assert(predicate::path::eq_file(file.path())); 99 + 100 + Ok(()) 101 + } 102 + }
+132
tests/cli.rs
··· 261 261 262 262 Ok(()) 263 263 } 264 + 265 + /// Xz roundtrip using files 266 + /// Compressing: input = test.txt, output = test.txt.xz 267 + /// Extracting: input = test.txt.xz, output = test.txt 268 + /// 269 + /// ``` bash 270 + /// cmprss xz test.txt test.txt.xz 271 + /// cmprss xz --extract --ignore-pipes test.txt.xz 272 + /// ``` 273 + #[test] 274 + fn xz_roundtrip_explicit() -> Result<(), Box<dyn std::error::Error>> { 275 + let file = assert_fs::NamedTempFile::new("test.txt")?; 276 + file.write_str("garbage data for testing")?; 277 + let working_dir = assert_fs::TempDir::new()?; 278 + let archive = working_dir.child("test.txt.xz"); 279 + archive.assert(predicate::path::missing()); 280 + 281 + let mut compress = Command::cargo_bin("cmprss")?; 282 + compress 283 + .current_dir(&working_dir) 284 + .arg("xz") 285 + .arg(file.path()) 286 + .arg(archive.path()); 287 + compress.assert().success(); 288 + archive.assert(predicate::path::is_file()); 289 + 290 + let mut extract = Command::cargo_bin("cmprss")?; 291 + extract 292 + .current_dir(&working_dir) 293 + .arg("xz") 294 + .arg("--ignore-pipes") 295 + .arg("--extract") 296 + .arg(archive.path()); 297 + extract.assert().success(); 298 + 299 + // Assert the files are identical 300 + working_dir 301 + .child("test.txt") 302 + .assert(predicate::path::eq_file(file.path())); 303 + 304 + Ok(()) 305 + } 306 + 307 + /// Xz roundtrip using stdin 308 + /// Compressing: input = stdin, output = test.txt.xz 309 + /// Extracting: input = stdin(test.txt.xz), output = test.txt 310 + /// 311 + /// ``` bash 312 + /// cat test.txt | cmprss xz test.txt.xz 313 + /// cat test.txt.xz | cmprss xz --extract out.txt 314 + /// ``` 315 + #[test] 316 + fn xz_roundtrip_stdin() -> Result<(), Box<dyn std::error::Error>> { 317 + let file = assert_fs::NamedTempFile::new("test.txt")?; 318 + file.write_str("garbage data for testing")?; 319 + let working_dir = assert_fs::TempDir::new()?; 320 + let archive = working_dir.child("test.txt.xz"); 321 + archive.assert(predicate::path::missing()); 322 + 323 + // Pipe file to stdin 324 + let mut compress = Command::cargo_bin("cmprss")?; 325 + compress 326 + .current_dir(&working_dir) 327 + .arg("xz") 328 + .arg("test.txt.xz") 329 + .stdin(Stdio::from(File::open(file.path())?)); 330 + compress.assert().success(); 331 + archive.assert(predicate::path::is_file()); 332 + 333 + let mut extract = Command::cargo_bin("cmprss")?; 334 + extract 335 + .current_dir(&working_dir) 336 + .arg("xz") 337 + .stdin(Stdio::from(File::open(archive.path())?)) 338 + .arg("--extract") 339 + .arg("out.txt"); 340 + extract.assert().success(); 341 + 342 + // Assert the files are identical 343 + working_dir 344 + .child("out.txt") 345 + .assert(predicate::path::eq_file(file.path())); 346 + 347 + Ok(()) 348 + } 349 + 350 + /// Xz roundtrip using stdout 351 + /// Compressing: input = test.txt, output = stdout 352 + /// Extracting: input = test.txt.xz, output = stdout 353 + /// 354 + /// ``` bash 355 + /// cmprss xz test.txt > test.txt.xz 356 + /// cmprss xz --extract test.txt.xz > out.txt 357 + /// ``` 358 + #[test] 359 + fn xz_roundtrip_stdout() -> Result<(), Box<dyn std::error::Error>> { 360 + let file = assert_fs::NamedTempFile::new("test.txt")?; 361 + file.write_str("garbage data for testing")?; 362 + let working_dir = assert_fs::TempDir::new()?; 363 + let archive = working_dir.child("test.txt.xz"); 364 + archive.assert(predicate::path::missing()); 365 + 366 + // Compress file to stdout 367 + let mut compress = Command::cargo_bin("cmprss")?; 368 + compress 369 + .current_dir(&working_dir) 370 + .arg("xz") 371 + .arg(file.path()) 372 + .stdout(Stdio::from(File::create(archive.path())?)); 373 + compress.assert().success(); 374 + archive.assert(predicate::path::is_file()); 375 + 376 + // Extract file to stdout 377 + let mut extract = Command::cargo_bin("cmprss")?; 378 + extract 379 + .current_dir(&working_dir) 380 + .arg("xz") 381 + .arg("--ignore-stdin") 382 + .arg("--extract") 383 + .arg(archive.path()) 384 + .arg("out.txt"); 385 + // TODO: This fails, but manual testing shows it works fine 386 + //.stdout(Stdio::from(File::create("out.txt")?)); 387 + extract.assert().success(); 388 + 389 + // Assert the files are identical 390 + working_dir 391 + .child("out.txt") 392 + .assert(predicate::path::eq_file(file.path())); 393 + 394 + Ok(()) 395 + }