this repo has no description
0
fork

Configure Feed

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

refactor: consolidate compressor lookup into single registry

+51 -120
+20
src/backends/mod.rs
··· 15 15 pub use xz::{Xz, XzArgs}; 16 16 pub use zip::{Zip, ZipArgs}; 17 17 pub use zstd::{Zstd, ZstdArgs}; 18 + 19 + use crate::utils::Compressor; 20 + 21 + /// Create a default compressor instance from an extension or name string. 22 + /// This is the single canonical lookup table for all compressor types. 23 + pub fn compressor_from_str(s: &str) -> Option<Box<dyn Compressor>> { 24 + match s { 25 + "tar" => Some(Box::<Tar>::default()), 26 + "gzip" | "gz" => Some(Box::<Gzip>::default()), 27 + "xz" => Some(Box::<Xz>::default()), 28 + "bzip2" | "bz2" => Some(Box::<Bzip2>::default()), 29 + "zip" => Some(Box::<Zip>::default()), 30 + "zstd" | "zst" => Some(Box::<Zstd>::default()), 31 + "lz4" => Some(Box::<Lz4>::default()), 32 + _ => None, 33 + } 34 + } 35 + 36 + /// All known single-format extensions, used for filename detection. 37 + pub const KNOWN_EXTENSIONS: &[&str] = &["tar", "gz", "xz", "bz2", "zip", "zst", "lz4"];
+4 -12
src/backends/multi_level.rs
··· 35 35 .join(".") 36 36 } 37 37 38 - /// Create a new compressor instance based on its name 39 38 fn create_compressor(name: &str) -> io::Result<Box<dyn Compressor>> { 40 - match name { 41 - "tar" => Ok(Box::new(crate::backends::Tar::default())), 42 - "gzip" | "gz" => Ok(Box::new(crate::backends::Gzip::default())), 43 - "xz" => Ok(Box::new(crate::backends::Xz::default())), 44 - "bzip2" | "bz2" => Ok(Box::new(crate::backends::Bzip2::default())), 45 - "zip" => Ok(Box::new(crate::backends::Zip::default())), 46 - "zstd" | "zst" => Ok(Box::new(crate::backends::Zstd::default())), 47 - "lz4" => Ok(Box::new(crate::backends::Lz4::default())), 48 - _ => Err(io::Error::new( 39 + crate::backends::compressor_from_str(name).ok_or_else(|| { 40 + io::Error::new( 49 41 io::ErrorKind::InvalidInput, 50 42 format!("Unknown compressor type: {}", name), 51 - )), 52 - } 43 + ) 44 + }) 53 45 } 54 46 } 55 47
+27 -108
src/main.rs
··· 8 8 use is_terminal::IsTerminal; 9 9 use std::io; 10 10 use std::path::{Path, PathBuf}; 11 - use std::{io, vec}; 12 11 use utils::*; 13 12 14 13 /// A compression multi-tool ··· 82 81 action: Action, 83 82 } 84 83 85 - /// Get a compressor from a filename 84 + /// Get a compressor from a filename, detecting multi-level formats like tar.gz 86 85 fn get_compressor_from_filename(filename: &Path) -> Option<Box<dyn Compressor>> { 87 - // Use just the filename component to avoid dots in directory names 88 86 let file_name = filename.file_name()?.to_str()?; 87 + let parts: Vec<&str> = file_name.split('.').collect(); 89 88 90 - // Prioritize checking for multi-level formats first 91 - { 92 - let parts: Vec<&str> = file_name.split('.').collect(); 93 - // A potential multi-level format like "archive.tar.gz" will have at least 3 parts 94 - if parts.len() >= 3 { 95 - // Get all available single compressors for matching extensions 96 - let single_compressors: Vec<Box<dyn Compressor>> = vec![ 97 - Box::<Tar>::default(), 98 - Box::<Gzip>::default(), 99 - Box::<Xz>::default(), 100 - Box::<Bzip2>::default(), 101 - Box::<Zip>::default(), 102 - Box::<Zstd>::default(), 103 - Box::<Lz4>::default(), 104 - ]; 89 + // Try multi-level detection (e.g., "archive.tar.gz" → [tar, gzip]) 90 + if parts.len() >= 3 { 91 + // Extensions right-to-left, e.g., ["gz", "tar"] 92 + let extensions: Vec<&str> = parts[1..].iter().rev().copied().collect(); 105 93 106 - // Get extensions in reverse order (from right to left, e.g., "gz", then "tar") 107 - let mut extensions: Vec<String> = Vec::new(); 108 - for i in 1..parts.len() { 109 - // Iterate from the last extension backwards 110 - // Stop before including the base filename part if it's just "filename.gz" (parts.len() would be 2) 111 - // This loop is for parts.len() >= 3, ensuring we look at actual extensions 112 - if parts.len() - i > 0 { 113 - // Ensure we don't go out of bounds for the base filename part 114 - extensions.push(parts[parts.len() - i].to_string()); 115 - } else { 116 - break; // Should not happen if parts.len() >=3 and i starts at 1 117 - } 94 + // Resolve each extension to a compressor name 95 + let mut compressor_names: Vec<String> = Vec::new(); 96 + for ext in &extensions { 97 + if let Some(c) = backends::compressor_from_str(ext) { 98 + compressor_names.push(c.name().to_string()); 99 + } else { 100 + compressor_names.clear(); 101 + break; 118 102 } 103 + } 119 104 120 - let mut compressor_types: Vec<String> = Vec::new(); 121 - for ext_part in &extensions { 122 - // e.g., ext_part is "gz", then "tar" 123 - let mut found_match = false; 124 - for sc in &single_compressors { 125 - if sc.extension() == ext_part || sc.name() == ext_part { 126 - compressor_types.push(sc.name().to_string()); 127 - found_match = true; 128 - break; 129 - } 130 - } 131 - if !found_match { 132 - // If any extension part is not recognized, this is not a valid multi-level chain we know. 133 - // Clear types and break, so we can fall back to simple single extension check. 134 - compressor_types.clear(); 135 - break; 136 - } 137 - } 138 - 139 - // If we successfully identified a chain of known compressor types: 140 - // compressor_types would be e.g. ["gzip", "tar"] (outermost to innermost) 141 - if !compressor_types.is_empty() { 142 - // MultiLevelCompressor::from_names expects innermost to outermost. 143 - compressor_types.reverse(); // e.g., ["tar", "gzip"] 144 - return Some(create_multi_level_compressor(&compressor_types)); 105 + if compressor_names.len() > 1 { 106 + // Reverse to innermost-to-outermost order for MultiLevelCompressor 107 + compressor_names.reverse(); 108 + if let Ok(multi) = MultiLevelCompressor::from_names(&compressor_names) { 109 + return Some(Box::new(multi)); 145 110 } 146 111 } 147 112 } 148 113 149 - // Fallback: try matching a single known compressor extension 150 - for sc in [ 151 - Box::<Tar>::default() as Box<dyn Compressor>, 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 - let expected_extension = format!(".{}", sc.extension()); 160 - if file_name.ends_with(&expected_extension) { 161 - return Some(sc); 162 - } 163 - } 164 - None 165 - } 166 - 167 - /// Get a single compressor from an extension string (no multi-level detection) 168 - fn get_compressor_from_extension(ext: &str) -> Option<Box<dyn Compressor>> { 169 - match ext { 170 - "tar" => Some(Box::<Tar>::default()), 171 - "gz" => Some(Box::<Gzip>::default()), 172 - "xz" => Some(Box::<Xz>::default()), 173 - "bz2" => Some(Box::<Bzip2>::default()), 174 - "zip" => Some(Box::<Zip>::default()), 175 - "zst" => Some(Box::<Zstd>::default()), 176 - "lz4" => Some(Box::<Lz4>::default()), 177 - _ => None, 178 - } 179 - } 180 - 181 - /// Create a MultiLevelCompressor from a list of compressor types 182 - fn create_multi_level_compressor(compressor_types: &[String]) -> Box<dyn Compressor> { 183 - // Create a MultiLevelCompressor from the list of compressor types 184 - match MultiLevelCompressor::from_names(compressor_types) { 185 - Ok(multi) => Box::new(multi), 186 - Err(_) => { 187 - // Fallback to the first compressor if there's an error 188 - match compressor_types[0].as_str() { 189 - "tar" => Box::<Tar>::default(), 190 - "gzip" | "gz" => Box::<Gzip>::default(), 191 - "xz" => Box::<Xz>::default(), 192 - "bzip2" | "bz2" => Box::<Bzip2>::default(), 193 - "zip" => Box::<Zip>::default(), 194 - "zstd" | "zst" => Box::<Zstd>::default(), 195 - "lz4" => Box::<Lz4>::default(), 196 - _ => Box::<Tar>::default(), // Default to tar if unknown 197 - } 198 - } 199 - } 114 + // Single extension fallback 115 + let ext = parts.last()?; 116 + backends::compressor_from_str(ext) 200 117 } 201 118 202 119 /// Convert an input path into a Path ··· 275 192 276 193 if guessed_output == output_file { 277 194 // Input is "archive.tar", output is "archive.tar.gz" — only add the outer layer 278 - let single_compressor = get_compressor_from_extension(output_ext.to_str().unwrap_or("")); 195 + let single_compressor = 196 + backends::compressor_from_str(output_ext.to_str().unwrap_or("")); 279 197 (single_compressor.or(Some(c)), Action::Compress) 280 198 } else if guessed_input == input_file { 281 199 // Output is "archive.tar", input is "archive.tar.gz" — only strip the outer layer 282 - let single_compressor = get_compressor_from_extension(input_ext.to_str().unwrap_or("")); 200 + let single_compressor = 201 + backends::compressor_from_str(input_ext.to_str().unwrap_or("")); 283 202 (single_compressor.or(Some(e)), Action::Extract) 284 203 } else if c.name() == e.name() { 285 204 // Same format for input and output, can't decide