this repo has no description
0
fork

Configure Feed

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

fix: improve the compression level helpers

+270 -47
+66 -13
src/bzip2.rs
··· 1 1 use crate::{ 2 2 progress::{copy_with_progress, ProgressArgs}, 3 - utils::*, 3 + utils::{ 4 + cmprss_error, CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor, 5 + LevelArgs, 6 + }, 4 7 }; 5 8 use bzip2::write::{BzDecoder, BzEncoder}; 6 9 use bzip2::Compression; ··· 10 13 io::{self, Read, Write}, 11 14 }; 12 15 16 + /// BZip2-specific compression validator (1-9 range) 17 + #[derive(Debug, Clone, Copy)] 18 + pub struct Bzip2CompressionValidator; 19 + 20 + impl CompressionLevelValidator for Bzip2CompressionValidator { 21 + fn min_level(&self) -> i32 { 22 + 1 23 + } 24 + fn max_level(&self) -> i32 { 25 + 9 26 + } 27 + fn default_level(&self) -> i32 { 28 + 9 29 + } 30 + 31 + fn name_to_level(&self, name: &str) -> Option<i32> { 32 + match name.to_lowercase().as_str() { 33 + "fast" => Some(1), 34 + "best" => Some(9), 35 + _ => None, 36 + } 37 + } 38 + } 39 + 13 40 #[derive(Args, Debug)] 14 41 pub struct Bzip2Args { 15 42 #[clap(flatten)] ··· 18 45 #[clap(flatten)] 19 46 pub progress_args: ProgressArgs, 20 47 21 - /// Level of compression. 22 - /// This is an int 1-9, with 1 being minimal compression and 9 being highest compression. 23 - /// Also supports 'fast', and 'best'. 24 - #[arg(long, default_value = "9")] 25 - pub level: CompressionLevel, 48 + #[clap(flatten)] 49 + pub level_args: LevelArgs, 26 50 } 27 51 28 52 pub struct Bzip2 { 29 - pub level: u32, // 1-9 53 + pub level: i32, // 1-9 30 54 pub progress_args: ProgressArgs, 31 55 } 32 56 33 57 impl Default for Bzip2 { 34 58 fn default() -> Self { 59 + let validator = Bzip2CompressionValidator; 35 60 Bzip2 { 36 - level: 6, 61 + level: validator.default_level(), 37 62 progress_args: ProgressArgs::default(), 38 63 } 39 64 } ··· 41 66 42 67 impl Bzip2 { 43 68 pub fn new(args: &Bzip2Args) -> Self { 69 + let validator = Bzip2CompressionValidator; 70 + let level = validator.validate_and_clamp_level(args.level_args.level.level); 71 + 44 72 Bzip2 { 45 - level: args.level.level, 73 + level, 46 74 progress_args: args.progress_args, 47 75 } 48 76 } ··· 61 89 62 90 /// Compress an input file or pipe to a bz2 archive 63 91 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 64 - if self.level < 1 || self.level > 9 { 65 - return cmprss_error("Invalid compression level. Must be 1-9."); 66 - } 67 92 let mut file_size = None; 68 93 let mut input_stream = match input { 69 94 CmprssInput::Path(paths) => { ··· 83 108 CmprssOutput::Path(path) => Box::new(File::create(path)?), 84 109 CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 85 110 }; 86 - let mut encoder = BzEncoder::new(output_stream, Compression::new(self.level)); 111 + let mut encoder = BzEncoder::new(output_stream, Compression::new(self.level as u32)); 87 112 88 113 // Use the custom output function to handle progress bar updates 89 114 copy_with_progress( ··· 140 165 use super::*; 141 166 use assert_fs::prelude::*; 142 167 use predicates::prelude::*; 168 + 169 + #[test] 170 + fn test_bzip2_compression_validator() { 171 + let validator = Bzip2CompressionValidator; 172 + 173 + // Test range 174 + assert_eq!(validator.min_level(), 1); 175 + assert_eq!(validator.max_level(), 9); 176 + assert_eq!(validator.default_level(), 9); 177 + 178 + // Test validation 179 + assert!(validator.is_valid_level(1)); 180 + assert!(validator.is_valid_level(5)); 181 + assert!(validator.is_valid_level(9)); 182 + assert!(!validator.is_valid_level(0)); 183 + assert!(!validator.is_valid_level(10)); 184 + 185 + // Test clamping 186 + assert_eq!(validator.validate_and_clamp_level(0), 1); 187 + assert_eq!(validator.validate_and_clamp_level(5), 5); 188 + assert_eq!(validator.validate_and_clamp_level(10), 9); 189 + 190 + // Test special names 191 + assert_eq!(validator.name_to_level("fast"), Some(1)); 192 + assert_eq!(validator.name_to_level("best"), Some(9)); 193 + assert_eq!(validator.name_to_level("none"), None); 194 + assert_eq!(validator.name_to_level("invalid"), None); 195 + } 143 196 144 197 #[test] 145 198 fn roundtrip() -> Result<(), Box<dyn std::error::Error>> {
+15 -4
src/gzip.rs
··· 19 19 } 20 20 21 21 pub struct Gzip { 22 - pub compression_level: u32, 22 + pub compression_level: i32, 23 23 pub progress_args: ProgressArgs, 24 24 } 25 25 26 26 impl Default for Gzip { 27 27 fn default() -> Self { 28 + let validator = DefaultCompressionValidator; 28 29 Gzip { 29 - compression_level: 6, 30 + compression_level: validator.default_level(), 30 31 progress_args: ProgressArgs::default(), 31 32 } 32 33 } ··· 34 35 35 36 impl Gzip { 36 37 pub fn new(args: &GzipArgs) -> Gzip { 38 + let validator = DefaultCompressionValidator; 39 + let level = args.level_args.level.level; 40 + 41 + // Validate and clamp the level to gzip's valid range 42 + let level = validator.validate_and_clamp_level(level); 43 + 37 44 Gzip { 38 - compression_level: args.level_args.level.level, 45 + compression_level: level, 39 46 progress_args: args.progress_args, 40 47 } 41 48 } ··· 100 107 CmprssOutput::Pipe(stdout) => Box::new(BufWriter::new(stdout)), 101 108 }; 102 109 103 - let mut encoder = GzEncoder::new(output_stream, Compression::new(self.compression_level)); 110 + // Create a gzip encoder with the specified compression level 111 + let mut encoder = GzEncoder::new( 112 + output_stream, 113 + Compression::new(self.compression_level as u32), 114 + ); 104 115 105 116 // Use the custom output function to handle progress bar updates with CountingWriter 106 117 copy_with_progress(
+105 -16
src/utils.rs
··· 45 45 pub ignore_stdout: bool, 46 46 } 47 47 48 + /// Trait for validating compression levels for different compressors 49 + #[allow(dead_code)] 50 + pub trait CompressionLevelValidator { 51 + /// Get the minimum valid compression level 52 + fn min_level(&self) -> i32; 53 + 54 + /// Get the maximum valid compression level 55 + fn max_level(&self) -> i32; 56 + 57 + /// Get the default compression level 58 + fn default_level(&self) -> i32; 59 + 60 + /// Map special names to compression levels 61 + fn name_to_level(&self, name: &str) -> Option<i32>; 62 + 63 + /// Validate if a compression level is within the valid range 64 + fn is_valid_level(&self, level: i32) -> bool { 65 + level >= self.min_level() && level <= self.max_level() 66 + } 67 + 68 + /// Validate and clamp a compression level to the valid range 69 + fn validate_and_clamp_level(&self, level: i32) -> i32 { 70 + if level < self.min_level() { 71 + self.min_level() 72 + } else if level > self.max_level() { 73 + self.max_level() 74 + } else { 75 + level 76 + } 77 + } 78 + } 79 + 80 + /// Default implementation for most compressors (0-9 range) 81 + #[derive(Debug, Clone, Copy)] 82 + pub struct DefaultCompressionValidator; 83 + 84 + impl CompressionLevelValidator for DefaultCompressionValidator { 85 + fn min_level(&self) -> i32 { 86 + 0 87 + } 88 + fn max_level(&self) -> i32 { 89 + 9 90 + } 91 + fn default_level(&self) -> i32 { 92 + 6 93 + } 94 + 95 + fn name_to_level(&self, name: &str) -> Option<i32> { 96 + match name.to_lowercase().as_str() { 97 + "none" => Some(0), 98 + "fast" => Some(1), 99 + "best" => Some(9), 100 + _ => None, 101 + } 102 + } 103 + } 104 + 48 105 #[derive(Debug, Clone, Copy)] 49 106 pub struct CompressionLevel { 50 - pub level: u32, 107 + pub level: i32, 51 108 } 52 109 53 110 impl Default for CompressionLevel { ··· 61 118 62 119 fn from_str(s: &str) -> Result<Self, Self::Err> { 63 120 // Check for an int 64 - if let Ok(level) = s.parse::<u32>() { 65 - if level < 10 { 66 - return Ok(CompressionLevel { level }); 67 - } else { 68 - return Err("Compression level must be 0-9"); 69 - } 121 + if let Ok(level) = s.parse::<i32>() { 122 + return Ok(CompressionLevel { level }); 70 123 } 124 + 125 + // Try to parse special names 71 126 let s = s.to_lowercase(); 72 127 match s.as_str() { 73 - "none" => Ok(CompressionLevel { level: 0 }), 74 - "fast" => Ok(CompressionLevel { level: 1 }), 75 - "best" => Ok(CompressionLevel { level: 9 }), 128 + "none" | "fast" | "best" => Ok(CompressionLevel { 129 + // We'll use the DefaultCompressionValidator values here 130 + // The actual compressor will interpret these values according to its own validator 131 + level: DefaultCompressionValidator.name_to_level(&s).unwrap(), 132 + }), 76 133 _ => Err("Invalid compression level"), 77 134 } 78 135 } ··· 81 138 #[derive(Args, Debug, Default, Clone, Copy)] 82 139 pub struct LevelArgs { 83 140 /// Level of compression. 84 - /// This is an int 0-9, with 0 being no compression and 9 being highest compression. 85 - /// Also supports 'none', 'fast', and 'best'. 86 - #[arg(long, default_value = "6")] 141 + /// `none`, `fast`, and `best` are mapped to appropriate values for each compressor. 142 + #[arg(long, default_value = "fast")] 87 143 pub level: CompressionLevel, 88 144 } 89 145 ··· 175 231 use super::*; 176 232 177 233 #[test] 178 - fn compression_level_parsing() { 234 + fn test_compression_level_parsing() { 235 + // Test numeric values 236 + assert_eq!(CompressionLevel::from_str("-7").unwrap().level, -7); 179 237 assert_eq!(CompressionLevel::from_str("0").unwrap().level, 0); 180 238 assert_eq!(CompressionLevel::from_str("1").unwrap().level, 1); 181 239 assert_eq!(CompressionLevel::from_str("9").unwrap().level, 9); 240 + assert_eq!(CompressionLevel::from_str("22").unwrap().level, 22); 241 + 242 + // Test special names (these use DefaultCompressionValidator values) 182 243 assert_eq!(CompressionLevel::from_str("none").unwrap().level, 0); 183 244 assert_eq!(CompressionLevel::from_str("fast").unwrap().level, 1); 184 245 assert_eq!(CompressionLevel::from_str("best").unwrap().level, 9); 185 - assert!(CompressionLevel::from_str("-1").is_err()); 186 - assert!(CompressionLevel::from_str("10").is_err()); 246 + 247 + // Test invalid values 187 248 assert!(CompressionLevel::from_str("foo").is_err()); 249 + } 250 + 251 + #[test] 252 + fn test_default_compression_validator() { 253 + let validator = DefaultCompressionValidator; 254 + 255 + // Test range 256 + assert_eq!(validator.min_level(), 0); 257 + assert_eq!(validator.max_level(), 9); 258 + assert_eq!(validator.default_level(), 6); 259 + 260 + // Test validation 261 + assert!(validator.is_valid_level(0)); 262 + assert!(validator.is_valid_level(5)); 263 + assert!(validator.is_valid_level(9)); 264 + assert!(!validator.is_valid_level(-1)); 265 + assert!(!validator.is_valid_level(10)); 266 + 267 + // Test clamping 268 + assert_eq!(validator.validate_and_clamp_level(-1), 0); 269 + assert_eq!(validator.validate_and_clamp_level(5), 5); 270 + assert_eq!(validator.validate_and_clamp_level(10), 9); 271 + 272 + // Test special names 273 + assert_eq!(validator.name_to_level("none"), Some(0)); 274 + assert_eq!(validator.name_to_level("fast"), Some(1)); 275 + assert_eq!(validator.name_to_level("best"), Some(9)); 276 + assert_eq!(validator.name_to_level("invalid"), None); 188 277 } 189 278 }
+16 -9
src/xz.rs
··· 7 7 fs::File, 8 8 io::{self, Read, Write}, 9 9 }; 10 - use xz2::write::{XzDecoder, XzEncoder}; 10 + use xz2::read::XzDecoder; 11 + use xz2::write::XzEncoder; 11 12 12 13 #[derive(Args, Debug)] 13 14 pub struct XzArgs { ··· 22 23 } 23 24 24 25 pub struct Xz { 25 - pub level: u32, 26 + pub level: i32, 26 27 pub progress_args: ProgressArgs, 27 28 } 28 29 29 30 impl Default for Xz { 30 31 fn default() -> Self { 32 + let validator = DefaultCompressionValidator; 31 33 Xz { 32 - level: 6, 34 + level: validator.default_level(), 33 35 progress_args: ProgressArgs::default(), 34 36 } 35 37 } ··· 37 39 38 40 impl Xz { 39 41 pub fn new(args: &XzArgs) -> Xz { 42 + let validator = DefaultCompressionValidator; 43 + let level = validator.validate_and_clamp_level(args.level_args.level.level); 44 + 40 45 Xz { 41 - level: args.level_args.level.level, 46 + level, 42 47 progress_args: args.progress_args, 43 48 } 44 49 } ··· 75 80 CmprssOutput::Path(path) => Box::new(File::create(path)?), 76 81 CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 77 82 }; 78 - let mut encoder = XzEncoder::new(output_stream, self.level); 83 + let mut encoder = XzEncoder::new(output_stream, self.level as u32); 79 84 80 85 // Use the custom output function to handle progress bar updates 81 86 copy_with_progress( ··· 92 97 93 98 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 94 99 let mut file_size = None; 95 - let mut input_stream = match input { 100 + let input_stream: Box<dyn Read + Send> = match input { 96 101 CmprssInput::Path(paths) => { 97 102 if paths.len() > 1 { 98 103 return cmprss_error("only 1 file can be extracted at a time"); ··· 106 111 } 107 112 CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>, 108 113 }; 109 - let output_stream: Box<dyn Write + Send> = match &output { 114 + let mut output_stream: Box<dyn Write + Send> = match &output { 110 115 CmprssOutput::Path(path) => Box::new(File::create(path)?), 111 116 CmprssOutput::Pipe(pipe) => Box::new(pipe) as Box<dyn Write + Send>, 112 117 }; 113 - let mut decoder = XzDecoder::new(output_stream); 118 + 119 + // Create an XZ decoder to decompress the input 120 + let mut decoder = XzDecoder::new(input_stream); 114 121 115 122 // Use the custom output function to handle progress bar updates 116 123 copy_with_progress( 117 - &mut input_stream, 118 124 &mut decoder, 125 + &mut *output_stream, 119 126 self.progress_args.chunk_size.size_in_bytes, 120 127 file_size, 121 128 self.progress_args.progress,
+68 -5
src/zstd.rs
··· 1 1 use crate::progress::{copy_with_progress, ProgressArgs}; 2 - use crate::utils::*; 2 + use crate::utils::{ 3 + cmprss_error, CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor, 4 + LevelArgs, 5 + }; 3 6 use clap::Args; 4 7 use std::fs::File; 5 8 use std::io::{self, BufReader, BufWriter, Read, Write}; 6 9 use zstd::stream::{read::Decoder, write::Encoder}; 7 10 11 + /// Zstd-specific compression validator (-7 to 22 range) 12 + #[derive(Debug, Clone, Copy)] 13 + pub struct ZstdCompressionValidator; 14 + 15 + impl CompressionLevelValidator for ZstdCompressionValidator { 16 + fn min_level(&self) -> i32 { 17 + -7 18 + } 19 + fn max_level(&self) -> i32 { 20 + 22 21 + } 22 + fn default_level(&self) -> i32 { 23 + 1 24 + } 25 + 26 + fn name_to_level(&self, name: &str) -> Option<i32> { 27 + match name.to_lowercase().as_str() { 28 + "none" => Some(-7), 29 + "fast" => Some(1), 30 + "best" => Some(22), 31 + _ => None, 32 + } 33 + } 34 + } 35 + 8 36 #[derive(Args, Debug)] 9 37 pub struct ZstdArgs { 10 38 #[clap(flatten)] ··· 18 46 } 19 47 20 48 pub struct Zstd { 21 - pub compression_level: u32, 49 + pub compression_level: i32, 22 50 pub progress_args: ProgressArgs, 23 51 } 24 52 25 53 impl Default for Zstd { 26 54 fn default() -> Self { 55 + let validator = ZstdCompressionValidator; 27 56 Zstd { 28 - compression_level: 6, 57 + compression_level: validator.default_level(), 29 58 progress_args: ProgressArgs::default(), 30 59 } 31 60 } ··· 33 62 34 63 impl Zstd { 35 64 pub fn new(args: &ZstdArgs) -> Zstd { 65 + let validator = ZstdCompressionValidator; 66 + let mut level = args.level_args.level.level; 67 + 68 + // Validate and clamp the level to zstd's valid range 69 + level = validator.validate_and_clamp_level(level); 70 + 36 71 Zstd { 37 - compression_level: args.level_args.level.level, 72 + compression_level: level, 38 73 progress_args: args.progress_args, 39 74 } 40 75 } ··· 100 135 }; 101 136 102 137 // Create a zstd encoder with the specified compression level 103 - let mut encoder = Encoder::new(output_stream, self.compression_level as i32)?; 138 + let mut encoder = Encoder::new(output_stream, self.compression_level)?; 104 139 105 140 // Copy the input to the encoder with progress reporting 106 141 copy_with_progress( ··· 196 231 assert_eq!(test_data.to_vec(), output_data); 197 232 198 233 Ok(()) 234 + } 235 + 236 + #[test] 237 + fn test_zstd_compression_validator() { 238 + let validator = ZstdCompressionValidator; 239 + 240 + // Test range 241 + assert_eq!(validator.min_level(), -7); 242 + assert_eq!(validator.max_level(), 22); 243 + assert_eq!(validator.default_level(), 1); 244 + 245 + // Test validation 246 + assert!(validator.is_valid_level(-7)); 247 + assert!(validator.is_valid_level(0)); 248 + assert!(validator.is_valid_level(22)); 249 + assert!(!validator.is_valid_level(-8)); 250 + assert!(!validator.is_valid_level(23)); 251 + 252 + // Test clamping 253 + assert_eq!(validator.validate_and_clamp_level(-8), -7); 254 + assert_eq!(validator.validate_and_clamp_level(0), 0); 255 + assert_eq!(validator.validate_and_clamp_level(23), 22); 256 + 257 + // Test special names 258 + assert_eq!(validator.name_to_level("none"), Some(-7)); 259 + assert_eq!(validator.name_to_level("fast"), Some(1)); 260 + assert_eq!(validator.name_to_level("best"), Some(22)); 261 + assert_eq!(validator.name_to_level("invalid"), None); 199 262 } 200 263 }