this repo has no description
0
fork

Configure Feed

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

at 37ff052fe16568b67a2f714d63b733c3d84db33d 760 lines 32 kB view raw
1pub mod backends; 2pub mod progress; 3pub mod test_utils; 4pub mod utils; 5 6use backends::*; 7use clap::{Parser, Subcommand}; 8use is_terminal::IsTerminal; 9use std::io; 10use std::path::{Path, PathBuf}; 11use std::{io, vec}; 12use utils::*; 13 14/// A compression multi-tool 15#[derive(Parser, Debug)] 16#[command(author, version, about, long_about = None)] 17struct CmprssArgs { 18 /// Format 19 #[command(subcommand)] 20 format: Option<Format>, 21 22 // Base arguments for the non-subcommand behavior 23 #[clap(flatten)] 24 pub base_args: CommonArgs, 25} 26#[derive(Subcommand, Debug)] 27enum Format { 28 /// tar archive format 29 Tar(TarArgs), 30 31 /// gzip compression 32 #[clap(visible_alias = "gz")] 33 Gzip(GzipArgs), 34 35 /// xz compression 36 Xz(XzArgs), 37 38 /// bzip2 compression 39 #[clap(visible_alias = "bz2")] 40 Bzip2(Bzip2Args), 41 42 /// zip archive format 43 Zip(ZipArgs), 44 45 /// zstd compression 46 #[clap(visible_alias = "zst")] 47 Zstd(ZstdArgs), 48 49 /// lz4 compression 50 Lz4(Lz4Args), 51} 52 53/// Get the input filename or return a default file 54/// This file will be used to generate the output filename 55fn get_input_filename(input: &CmprssInput) -> Result<&Path, io::Error> { 56 match input { 57 CmprssInput::Path(paths) => match paths.first() { 58 Some(path) => Ok(path), 59 None => Err(io::Error::new( 60 io::ErrorKind::Other, 61 "error: no input specified", 62 )), 63 }, 64 CmprssInput::Pipe(_) => Ok(Path::new("archive")), 65 CmprssInput::Reader(_) => Ok(Path::new("piped_data")), 66 } 67} 68 69#[derive(Debug, PartialEq, Clone, Copy)] 70enum Action { 71 Compress, 72 Extract, 73 Unknown, 74} 75 76/// Defines a single compress/extract action to take. 77#[derive(Debug)] 78struct Job { 79 compressor: Box<dyn Compressor>, 80 input: CmprssInput, 81 output: CmprssOutput, 82 action: Action, 83} 84 85/// Get a compressor from a filename 86fn get_compressor_from_filename(filename: &Path) -> Option<Box<dyn Compressor>> { 87 // Prioritize checking for multi-level formats first 88 if let Some(filename_str) = filename.to_str() { 89 let parts: Vec<&str> = filename_str.split('.').collect(); 90 // A potential multi-level format like "archive.tar.gz" will have at least 3 parts 91 if parts.len() >= 3 { 92 // Get all available single compressors for matching extensions 93 let single_compressors: Vec<Box<dyn Compressor>> = vec![ 94 Box::<Tar>::default(), 95 Box::<Gzip>::default(), 96 Box::<Xz>::default(), 97 Box::<Bzip2>::default(), 98 Box::<Zip>::default(), 99 Box::<Zstd>::default(), 100 Box::<Lz4>::default(), 101 ]; 102 103 // Get extensions in reverse order (from right to left, e.g., "gz", then "tar") 104 let mut extensions: Vec<String> = Vec::new(); 105 for i in 1..parts.len() { 106 // Iterate from the last extension backwards 107 // Stop before including the base filename part if it's just "filename.gz" (parts.len() would be 2) 108 // This loop is for parts.len() >= 3, ensuring we look at actual extensions 109 if parts.len() - i > 0 { 110 // Ensure we don't go out of bounds for the base filename part 111 extensions.push(parts[parts.len() - i].to_string()); 112 } else { 113 break; // Should not happen if parts.len() >=3 and i starts at 1 114 } 115 } 116 117 let mut compressor_types: Vec<String> = Vec::new(); 118 for ext_part in &extensions { 119 // e.g., ext_part is "gz", then "tar" 120 let mut found_match = false; 121 for sc in &single_compressors { 122 if sc.extension() == ext_part || sc.name() == ext_part { 123 compressor_types.push(sc.name().to_string()); 124 found_match = true; 125 break; 126 } 127 } 128 if !found_match { 129 // If any extension part is not recognized, this is not a valid multi-level chain we know. 130 // Clear types and break, so we can fall back to simple single extension check. 131 compressor_types.clear(); 132 break; 133 } 134 } 135 136 // If we successfully identified a chain of known compressor types: 137 // compressor_types would be e.g. ["gzip", "tar"] (outermost to innermost) 138 if !compressor_types.is_empty() { 139 // MultiLevelCompressor::from_names expects innermost to outermost. 140 compressor_types.reverse(); // e.g., ["tar", "gzip"] 141 return Some(create_multi_level_compressor(&compressor_types)); 142 } 143 // If compressor_types is empty here, it means the multi-level parse failed (e.g. "file.foo.bar" with unknown foo/bar) 144 // or an unknown extension was found in the chain. We'll fall through to single extension check. 145 } 146 } 147 148 // Fallback: If not a recognized multi-level format, or if fewer than 3 parts (e.g. "file.gz"), 149 // try matching a single known compressor extension. 150 let single_compressors: Vec<Box<dyn Compressor>> = vec![ 151 Box::<Tar>::default(), 152 Box::<Gzip>::default(), 153 Box::<Xz>::default(), 154 Box::<Bzip2>::default(), 155 Box::<Zip>::default(), 156 Box::<Zstd>::default(), 157 Box::<Lz4>::default(), 158 ]; 159 160 // Check if file extension matches any known format 161 // This is now a fallback. 162 // Ensure this doesn't misinterpret "foo.tar.gz" as just "gz" if multi-level check failed for some reason 163 // A more robust check here might be to see if filename *only* ends with .ext and not .something_else.ext 164 // For now, the standard check is: 165 if let Some(filename_str) = filename.to_str() { 166 for sc in single_compressors { 167 // A simple "ends_with" can be problematic for "file.tar.gz" vs "file.gz" 168 // We need to be more specific. The extension should be exactly the compressor's extension. 169 let expected_extension = format!(".{}", sc.extension()); 170 if filename_str.ends_with(&expected_extension) { 171 // Further check: ensure it's not something like ".tar.gz" being matched by ".gz" 172 // if we want to be super sure, but the multi-level check should catch .tar.gz first. 173 // A simple way: if it ends with ".tar.gz", Gzip (gz) should not match here IF Tar (tar) also exists. 174 // The current structure relies on multi-level being caught first. 175 // If multi-level parsing failed, then we check single extensions. 176 // Example: "archive.gz" -> Gzip 177 // Example: "archive.tar" -> Tar 178 // Example: "archive.unknown.gz" -> Multi-level fails, then Gzip matches. 179 return Some(sc); 180 } 181 } 182 } 183 None 184} 185 186/// Create a MultiLevelCompressor from a list of compressor types 187fn create_multi_level_compressor(compressor_types: &[String]) -> Box<dyn Compressor> { 188 // Create a MultiLevelCompressor from the list of compressor types 189 match MultiLevelCompressor::from_names(compressor_types) { 190 Ok(multi) => Box::new(multi), 191 Err(_) => { 192 // Fallback to the first compressor if there's an error 193 match compressor_types[0].as_str() { 194 "tar" => Box::<Tar>::default(), 195 "gzip" | "gz" => Box::<Gzip>::default(), 196 "xz" => Box::<Xz>::default(), 197 "bzip2" | "bz2" => Box::<Bzip2>::default(), 198 "zip" => Box::<Zip>::default(), 199 "zstd" | "zst" => Box::<Zstd>::default(), 200 "lz4" => Box::<Lz4>::default(), 201 _ => Box::<Tar>::default(), // Default to tar if unknown 202 } 203 } 204 } 205} 206 207/// Convert an input path into a Path 208fn get_path(input: &str) -> Option<PathBuf> { 209 let path = PathBuf::from(input); 210 if !path.try_exists().unwrap_or(false) { 211 return None; 212 } 213 Some(path) 214} 215 216/// Guess compressor/action from the two filenames 217/// The compressor may already be given 218fn guess_from_filenames( 219 input: &[PathBuf], 220 output: &Path, 221 compressor: Option<Box<dyn Compressor>>, 222) -> (Option<Box<dyn Compressor>>, Action) { 223 if input.len() != 1 { 224 if let Some(guessed_compressor) = get_compressor_from_filename(output) { 225 return (Some(guessed_compressor), Action::Compress); 226 } 227 228 // Check if output is a directory - this is likely an extraction 229 if output.is_dir() { 230 // Try to determine compressor from the input file's extension(s) 231 if let Some(input_path) = input.first() { 232 if let Some(guessed_compressor) = get_compressor_from_filename(input_path) { 233 return (Some(guessed_compressor), Action::Extract); 234 } 235 } 236 } 237 238 // In theory we could be extracting multiple files to a directory 239 // We'll fail somewhere else if that's not the case 240 return (compressor, Action::Extract); 241 } 242 let input = input.first().unwrap(); 243 244 let guessed_compressor = get_compressor_from_filename(output); 245 let guessed_extractor = get_compressor_from_filename(input); 246 let guessed_compressor_name = if let Some(c) = &guessed_compressor { 247 c.name() 248 } else { 249 "" 250 }; 251 let guessed_extractor_name = if let Some(e) = &guessed_extractor { 252 e.name() 253 } else { 254 "" 255 }; 256 257 if let Some(c) = &compressor { 258 if guessed_compressor_name == c.name() { 259 return (compressor, Action::Compress); 260 } else if guessed_extractor_name == c.name() { 261 return (compressor, Action::Extract); 262 } else { 263 // Default to compressing 264 return (compressor, Action::Compress); 265 } 266 } 267 268 match (guessed_compressor, guessed_extractor) { 269 (None, None) => (None, Action::Unknown), 270 (Some(c), None) => (Some(c), Action::Compress), 271 (None, Some(e)) => (Some(e), Action::Extract), 272 (Some(c), Some(e)) => { 273 if c.name() == e.name() { 274 // Same format for input and output, can't decide 275 if output.is_dir() { 276 // If output is a directory, we're probably extracting 277 return (Some(e), Action::Extract); 278 } 279 return (Some(c), Action::Unknown); 280 } 281 282 // Compare the input and output extensions to see if one has an extra extension 283 let input_file = input.file_name().unwrap().to_str().unwrap(); 284 let input_ext = input.extension().unwrap_or_default(); 285 let output_file = output.file_name().unwrap().to_str().unwrap(); 286 let output_ext = output.extension().unwrap_or_default(); 287 let guessed_output = input_file.to_string() + "." + output_ext.to_str().unwrap(); 288 let guessed_input = output_file.to_string() + "." + input_ext.to_str().unwrap(); 289 290 if guessed_output == output_file { 291 (Some(c), Action::Compress) 292 } else if guessed_input == input_file { 293 (Some(e), Action::Extract) 294 } else if output.is_dir() { 295 // If output is a directory, we're probably extracting 296 (Some(e), Action::Extract) 297 } else { 298 (None, Action::Unknown) 299 } 300 } 301 } 302} 303 304/// Parse the common args and determine the details of the job requested 305fn get_job( 306 compressor: Option<Box<dyn Compressor>>, 307 common_args: &CommonArgs, 308) -> Result<Job, io::Error> { 309 let mut compressor = compressor; 310 let mut action = { 311 if common_args.compress { 312 Action::Compress 313 } else if common_args.extract || common_args.decompress { 314 Action::Extract 315 } else { 316 Action::Unknown 317 } 318 }; 319 320 let mut inputs = Vec::new(); 321 if let Some(in_file) = &common_args.input { 322 match get_path(in_file) { 323 Some(path) => inputs.push(path), 324 None => { 325 return Err(io::Error::new( 326 io::ErrorKind::Other, 327 "Specified input path does not exist", 328 )); 329 } 330 } 331 } 332 333 let mut output = match &common_args.output { 334 Some(output) => { 335 let path = Path::new(output); 336 if path.try_exists()? && !path.is_dir() { 337 // Output path exists, bail out 338 return Err(io::Error::new( 339 io::ErrorKind::Other, 340 "Specified output path already exists", 341 )); 342 } 343 Some(path) 344 } 345 None => None, 346 }; 347 348 // Process the io_list, check if there is an output first 349 let mut io_list = common_args.io_list.clone(); 350 if output.is_none() { 351 if let Some(possible_output) = common_args.io_list.last() { 352 let path = Path::new(possible_output); 353 if !path.try_exists()? { 354 // Use the given path if it doesn't exist 355 output = Some(path); 356 io_list.pop(); 357 } else if path.is_dir() { 358 match action { 359 Action::Compress => { 360 // A directory can potentially be a target output location or 361 // an input, for now assume it is an input. 362 } 363 Action::Extract => { 364 // Can extract to a directory, and it wouldn't make any sense as an input 365 output = Some(path); 366 io_list.pop(); 367 } 368 _ => { 369 // TODO: don't know if this is an input or output, assume we're compressing this directory 370 // This does cause problems for inferencing "cat archive.tar | cmprss tar ." 371 // Probably need to add some special casing 372 } 373 }; 374 } else { 375 // TODO: check for scenarios where we want to append to an existing archive 376 } 377 } 378 } 379 380 // Validate the specified inputs 381 // Everything in the io_list should be an input 382 for input in &io_list { 383 if let Some(path) = get_path(input) { 384 inputs.push(path); 385 } else { 386 return Err(io::Error::new( 387 io::ErrorKind::Other, 388 "Specified input path does not exist", 389 )); 390 } 391 } 392 393 // Fallback to stdin/stdout if we're missing files 394 let cmprss_input = match inputs.is_empty() { 395 true => { 396 if !std::io::stdin().is_terminal() 397 && !&common_args.ignore_pipes 398 && !&common_args.ignore_stdin 399 { 400 CmprssInput::Pipe(std::io::stdin()) 401 } else { 402 return Err(io::Error::new(io::ErrorKind::Other, "No specified input")); 403 } 404 } 405 false => CmprssInput::Path(inputs), 406 }; 407 408 let cmprss_output = match output { 409 Some(path) => CmprssOutput::Path(path.to_path_buf()), 410 None => { 411 if !std::io::stdout().is_terminal() 412 && !&common_args.ignore_pipes 413 && !&common_args.ignore_stdout 414 { 415 CmprssOutput::Pipe(std::io::stdout()) 416 } else { 417 match action { 418 Action::Compress => { 419 if compressor.is_none() { 420 return Err(io::Error::new( 421 io::ErrorKind::Other, 422 "Must specify a compressor", 423 )); 424 } 425 CmprssOutput::Path(PathBuf::from( 426 compressor 427 .as_ref() 428 .unwrap() 429 .default_compressed_filename(get_input_filename(&cmprss_input)?), 430 )) 431 } 432 Action::Extract => { 433 if compressor.is_none() { 434 compressor = 435 get_compressor_from_filename(get_input_filename(&cmprss_input)?); 436 if compressor.is_none() { 437 return Err(io::Error::new( 438 io::ErrorKind::Other, 439 "Must specify a compressor", 440 )); 441 } 442 } 443 CmprssOutput::Path(PathBuf::from( 444 compressor 445 .as_ref() 446 .unwrap() 447 .default_extracted_filename(get_input_filename(&cmprss_input)?), 448 )) 449 } 450 Action::Unknown => { 451 if compressor.is_none() { 452 // Can still work if the input is an archive 453 compressor = 454 get_compressor_from_filename(get_input_filename(&cmprss_input)?); 455 if compressor.is_none() { 456 return Err(io::Error::new( 457 io::ErrorKind::Other, 458 "Must specify a compressor", 459 )); 460 } 461 action = Action::Extract; 462 CmprssOutput::Path(PathBuf::from( 463 compressor 464 .as_ref() 465 .unwrap() 466 .default_extracted_filename(get_input_filename(&cmprss_input)?), 467 )) 468 } else { 469 // We know the compressor, does the input have the same extension? 470 if let Some(compressor_from_input) = 471 get_compressor_from_filename(get_input_filename(&cmprss_input)?) 472 { 473 if compressor.as_ref().unwrap().name() 474 == compressor_from_input.name() 475 { 476 action = Action::Extract; 477 CmprssOutput::Path(PathBuf::from( 478 compressor.as_ref().unwrap().default_extracted_filename( 479 get_input_filename(&cmprss_input)?, 480 ), 481 )) 482 } else { 483 action = Action::Compress; 484 CmprssOutput::Path(PathBuf::from( 485 compressor.as_ref().unwrap().default_compressed_filename( 486 get_input_filename(&cmprss_input)?, 487 ), 488 )) 489 } 490 } else { 491 action = Action::Compress; 492 CmprssOutput::Path(PathBuf::from( 493 compressor.as_ref().unwrap().default_compressed_filename( 494 get_input_filename(&cmprss_input)?, 495 ), 496 )) 497 } 498 } 499 } 500 } 501 } 502 } 503 }; 504 505 // If we don't have the compressor/action, we can attempt to infer 506 if compressor.is_none() || action == Action::Unknown { 507 match action { 508 Action::Compress => { 509 // Look at the output name 510 if let CmprssOutput::Path(path) = &cmprss_output { 511 compressor = get_compressor_from_filename(path); 512 } 513 } 514 Action::Extract => { 515 // Look at the input name 516 if let CmprssInput::Path(paths) = &cmprss_input { 517 if paths.len() != 1 { 518 // When extracting, we expect a single input file 519 return Err(io::Error::new( 520 io::ErrorKind::Other, 521 "Expected a single archive to extract", 522 )); 523 } 524 compressor = get_compressor_from_filename(paths.first().unwrap()); 525 526 // If we still couldn't guess the compressor, try harder with multi-level extraction 527 if compressor.is_none() && paths.len() == 1 { 528 if let Some(filename_str) = paths.first().unwrap().to_str() { 529 // Try to parse multi-level formats (e.g., tar.gz) 530 let parts: Vec<&str> = filename_str.split('.').collect(); 531 if parts.len() >= 3 { 532 // Get all available compressors 533 let compressors: Vec<Box<dyn Compressor>> = vec![ 534 Box::<Tar>::default(), 535 Box::<Gzip>::default(), 536 Box::<Xz>::default(), 537 Box::<Bzip2>::default(), 538 Box::<Zip>::default(), 539 Box::<Zstd>::default(), 540 Box::<Lz4>::default(), 541 ]; 542 543 // Get extensions in reverse order (from right to left) 544 let mut extensions: Vec<String> = Vec::new(); 545 for i in 1..parts.len() { 546 extensions.push(parts[parts.len() - i].to_string()); 547 } 548 549 // Try to find a compressor for each extension 550 let mut compressor_types: Vec<String> = Vec::new(); 551 for ext in &extensions { 552 for compressor in &compressors { 553 if compressor.extension() == ext || compressor.name() == ext 554 { 555 compressor_types.push(compressor.name().to_string()); 556 break; 557 } 558 } 559 } 560 561 // If we found compressor types, create a MultiLevelCompressor 562 if !compressor_types.is_empty() { 563 compressor = 564 Some(create_multi_level_compressor(&compressor_types)); 565 } 566 } 567 } 568 } 569 } 570 } 571 Action::Unknown => match (&cmprss_input, &cmprss_output) { 572 (CmprssInput::Path(paths), CmprssOutput::Path(path)) => { 573 // Special case: if output is a directory, assume we're extracting 574 if path.is_dir() && paths.len() == 1 { 575 // For extraction to directory, try to determine compressor from input file 576 compressor = get_compressor_from_filename(paths.first().unwrap()); 577 action = Action::Extract; 578 579 // If no compressor was found, try harder with multi-level detection 580 if compressor.is_none() { 581 if let Some(filename_str) = paths.first().unwrap().to_str() { 582 // Try to parse multi-level formats (e.g., tar.gz) 583 let parts: Vec<&str> = filename_str.split('.').collect(); 584 if parts.len() >= 3 { 585 // Get all available compressors 586 let compressors: Vec<Box<dyn Compressor>> = vec![ 587 Box::<Tar>::default(), 588 Box::<Gzip>::default(), 589 Box::<Xz>::default(), 590 Box::<Bzip2>::default(), 591 Box::<Zip>::default(), 592 Box::<Zstd>::default(), 593 Box::<Lz4>::default(), 594 ]; 595 596 // Get extensions in reverse order (from right to left) 597 let mut extensions: Vec<String> = Vec::new(); 598 for i in 1..parts.len() { 599 extensions.push(parts[parts.len() - i].to_string()); 600 } 601 602 // Try to find a compressor for each extension 603 let mut compressor_types: Vec<String> = Vec::new(); 604 for ext in &extensions { 605 for compressor in &compressors { 606 if compressor.extension() == ext 607 || compressor.name() == ext 608 { 609 compressor_types 610 .push(compressor.name().to_string()); 611 break; 612 } 613 } 614 } 615 616 // If we found compressor types, create a MultiLevelCompressor 617 if !compressor_types.is_empty() { 618 compressor = 619 Some(create_multi_level_compressor(&compressor_types)); 620 } 621 } 622 } 623 624 // If we still couldn't determine compressor, fail with a clear message 625 if compressor.is_none() { 626 return Err(io::Error::new( 627 io::ErrorKind::Other, 628 format!( 629 "Couldn't determine how to extract {:?}", 630 paths.first().unwrap() 631 ), 632 )); 633 } 634 } 635 } else { 636 let (guessed_compressor, guessed_action) = 637 guess_from_filenames(paths, path, compressor); 638 compressor = guessed_compressor; 639 action = guessed_action; 640 } 641 } 642 (CmprssInput::Path(paths), CmprssOutput::Pipe(_)) => { 643 if compressor.is_none() { 644 if paths.len() != 1 { 645 return Err(io::Error::new( 646 io::ErrorKind::Other, 647 "Expected a single input file for piping to stdout", 648 )); 649 } 650 compressor = get_compressor_from_filename(paths.first().unwrap()); 651 if compressor.is_some() { 652 action = Action::Extract; 653 } else { 654 return Err(io::Error::new( 655 io::ErrorKind::Other, 656 "Can't guess compressor to use", 657 )); 658 } 659 } else if let Some(c) = get_compressor_from_filename(paths.first().unwrap()) { 660 if compressor.as_ref().unwrap().name() == c.name() { 661 action = Action::Extract; 662 } else { 663 action = Action::Compress; 664 } 665 } else { 666 action = Action::Compress; 667 } 668 } 669 (CmprssInput::Pipe(_), CmprssOutput::Path(path)) => { 670 if compressor.is_none() { 671 compressor = get_compressor_from_filename(path); 672 if compressor.is_some() { 673 action = Action::Compress; 674 } else { 675 return Err(io::Error::new( 676 io::ErrorKind::Other, 677 "Can't guess compressor to use", 678 )); 679 } 680 } else if compressor.as_ref().unwrap().name() 681 == get_compressor_from_filename(path).unwrap().name() 682 { 683 action = Action::Compress; 684 } else { 685 action = Action::Extract; 686 } 687 } 688 (CmprssInput::Pipe(_), CmprssOutput::Pipe(_)) => { 689 action = Action::Compress; 690 } 691 // Handle all Writer output cases 692 (_, CmprssOutput::Writer(_)) => { 693 // Writer outputs are only supported in multi-level compression 694 // In main.rs we'll assume compression 695 action = Action::Compress; 696 } 697 // Handle all Reader input cases 698 (&CmprssInput::Reader(_), _) => { 699 // For Reader input, we'll assume extraction 700 action = Action::Extract; 701 } 702 }, 703 } 704 } 705 706 if compressor.is_none() { 707 return Err(io::Error::new( 708 io::ErrorKind::Other, 709 "Could not determine compressor to use", 710 )); 711 } 712 if action == Action::Unknown { 713 return Err(io::Error::new( 714 io::ErrorKind::Other, 715 "Could not determine action to take", 716 )); 717 } 718 719 Ok(Job { 720 compressor: compressor.unwrap(), 721 input: cmprss_input, 722 output: cmprss_output, 723 action, 724 }) 725} 726 727fn command(compressor: Option<Box<dyn Compressor>>, args: &CommonArgs) -> Result<(), io::Error> { 728 let job = get_job(compressor, args)?; 729 730 match job.action { 731 Action::Compress => job.compressor.compress(job.input, job.output)?, 732 Action::Extract => job.compressor.extract(job.input, job.output)?, 733 _ => { 734 return Err(io::Error::new( 735 io::ErrorKind::Other, 736 "Unknown action requested", 737 )); 738 } 739 }; 740 741 Ok(()) 742} 743 744fn main() { 745 let args = CmprssArgs::parse(); 746 match args.format { 747 Some(Format::Tar(a)) => command(Some(Box::new(Tar::new(&a))), &a.common_args), 748 Some(Format::Gzip(a)) => command(Some(Box::new(Gzip::new(&a))), &a.common_args), 749 Some(Format::Xz(a)) => command(Some(Box::new(Xz::new(&a))), &a.common_args), 750 Some(Format::Bzip2(a)) => command(Some(Box::new(Bzip2::new(&a))), &a.common_args), 751 Some(Format::Zip(a)) => command(Some(Box::new(Zip::new(&a))), &a.common_args), 752 Some(Format::Zstd(a)) => command(Some(Box::new(Zstd::new(&a))), &a.common_args), 753 Some(Format::Lz4(a)) => command(Some(Box::new(Lz4::new(&a))), &a.common_args), 754 _ => command(None, &args.base_args), 755 } 756 .unwrap_or_else(|e| { 757 eprintln!("ERROR(cmprss): {}", e); 758 std::process::exit(1); 759 }); 760}