this repo has no description
0
fork

Configure Feed

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

refactor: replace non-test unwraps with proper error handling

+67 -57
+4 -7
src/backends/pipeline.rs
··· 243 243 // Add all extensions: input.txt → input.txt.tar.gz 244 244 let base = in_path 245 245 .file_name() 246 - .unwrap_or_else(|| std::ffi::OsStr::new("archive")) 247 - .to_str() 248 - .unwrap(); 246 + .map(|n| n.to_string_lossy().into_owned()) 247 + .unwrap_or_else(|| "archive".to_string()); 249 248 format!("{}.{}", base, self.format_chain()) 250 249 } 251 250 ··· 256 255 // Strip all known extensions: input.tar.gz → input 257 256 let mut name = in_path 258 257 .file_name() 259 - .unwrap_or_else(|| std::ffi::OsStr::new("archive")) 260 - .to_str() 261 - .unwrap() 262 - .to_string(); 258 + .map(|n| n.to_string_lossy().into_owned()) 259 + .unwrap_or_else(|| "archive".to_string()); 263 260 for comp in self.compressors.iter().rev() { 264 261 let ext = format!(".{}", comp.extension()); 265 262 if let Some(stripped) = name.strip_suffix(&ext) {
+6 -2
src/backends/sevenz.rs
··· 4 4 CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor, 5 5 DefaultCompressionValidator, ExtractedTarget, LevelArgs, Result, 6 6 }; 7 - use anyhow::bail; 7 + use anyhow::{anyhow, bail}; 8 8 use clap::Args; 9 9 use indicatif::ProgressBar; 10 10 use sevenz_rust2::{ ··· 82 82 match input { 83 83 CmprssInput::Path(paths) => { 84 84 for path in paths { 85 - let name = path.file_name().unwrap().to_string_lossy().to_string(); 85 + let name = path 86 + .file_name() 87 + .ok_or_else(|| anyhow!("input path has no file name: {:?}", path))? 88 + .to_string_lossy() 89 + .to_string(); 86 90 if path.is_file() { 87 91 push_file_entry(&mut aw, &name, &path, bar)?; 88 92 } else if path.is_dir() {
+4 -2
src/backends/tar.rs
··· 1 1 extern crate tar; 2 2 3 - use anyhow::bail; 3 + use anyhow::{anyhow, bail}; 4 4 use clap::Args; 5 5 use indicatif::ProgressBar; 6 6 use std::fs::{File, OpenOptions}; ··· 240 240 match input { 241 241 CmprssInput::Path(paths) => { 242 242 for path in paths { 243 - let name = path.file_name().unwrap(); 243 + let name = path 244 + .file_name() 245 + .ok_or_else(|| anyhow!("input path has no file name: {:?}", path))?; 244 246 if path.is_file() { 245 247 append_file_entry(&mut archive, Path::new(name), &path, bar)?; 246 248 } else if path.is_dir() {
+8 -3
src/backends/zip.rs
··· 4 4 CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor, 5 5 DefaultCompressionValidator, ExtractedTarget, LevelArgs, Result, 6 6 }; 7 - use anyhow::bail; 7 + use anyhow::{anyhow, bail}; 8 8 use clap::Args; 9 9 use indicatif::ProgressBar; 10 10 use std::fs::{File, OpenOptions}; ··· 99 99 CmprssInput::Path(paths) => { 100 100 for path in paths { 101 101 if path.is_file() { 102 - let name = path.file_name().unwrap().to_string_lossy(); 102 + let name = path 103 + .file_name() 104 + .ok_or_else(|| anyhow!("input path has no file name: {:?}", path))? 105 + .to_string_lossy(); 103 106 zip_writer.start_file(name, options)?; 104 107 let f = File::open(&path)?; 105 108 let mut reader = ProgressReader::new(f, bar.cloned()); ··· 308 311 let entry = entry?; 309 312 let entry_path = entry.path(); 310 313 // Get relative path for archive entry 314 + // `entry_path` is a direct child of `path`, which itself sits under 315 + // `base`, so stripping always succeeds. 311 316 let name = entry_path 312 317 .strip_prefix(base) 313 - .unwrap() 318 + .expect("entry path is under base") 314 319 .to_string_lossy() 315 320 .replace('\\', "/"); 316 321 if entry_path.is_file() {
+34 -28
src/job.rs
··· 305 305 } 306 306 Some(Action::Extract) => { 307 307 if let CmprssInput::Path(paths) = input { 308 - if paths.len() != 1 { 308 + let [archive_path] = paths.as_slice() else { 309 309 bail!("Expected exactly one input archive"); 310 - } 311 - *compressor = get_compressor_from_filename(paths.first().unwrap()); 310 + }; 311 + *compressor = get_compressor_from_filename(archive_path); 312 312 } 313 313 } 314 314 None => match (input, output) { 315 - (CmprssInput::Path(paths), CmprssOutput::Path(path)) => { 316 - if path.is_dir() && paths.len() == 1 { 317 - *compressor = get_compressor_from_filename(paths.first().unwrap()); 315 + (CmprssInput::Path(paths), CmprssOutput::Path(path)) => match paths.as_slice() { 316 + [single] if path.is_dir() => { 317 + *compressor = get_compressor_from_filename(single); 318 318 *action = Some(Action::Extract); 319 319 if compressor.is_none() { 320 - bail!( 321 - "Could not determine compressor for {:?}", 322 - paths.first().unwrap() 323 - ); 320 + bail!("Could not determine compressor for {:?}", single); 324 321 } 325 - } else { 322 + } 323 + _ => { 326 324 let (c, a) = guess_from_filenames(paths, path, compressor.take())?; 327 325 *compressor = Some(c); 328 326 *action = Some(a); 329 327 } 330 - } 328 + }, 331 329 (CmprssInput::Path(paths), CmprssOutput::Pipe(_)) => { 330 + // `resolve_input` guarantees `paths` is non-empty when it 331 + // returns `CmprssInput::Path`, so `first()` is always Some — 332 + // but surface a clean error instead of relying on the invariant. 333 + let first = paths 334 + .first() 335 + .ok_or_else(|| anyhow!("No input file specified"))?; 332 336 if let Some(c) = compressor.as_deref() { 333 - *action = Some(match get_compressor_from_filename(paths.first().unwrap()) { 337 + *action = Some(match get_compressor_from_filename(first) { 334 338 Some(ic) if ic.name() == c.name() => Action::Extract, 335 339 _ => Action::Compress, 336 340 }); ··· 338 342 if paths.len() != 1 { 339 343 bail!("Expected exactly one input file when writing to stdout"); 340 344 } 341 - *compressor = get_compressor_from_filename(paths.first().unwrap()); 345 + *compressor = get_compressor_from_filename(first); 342 346 if compressor.is_some() { 343 347 *action = Some(Action::Extract); 344 348 } else { ··· 448 452 output: &Path, 449 453 compressor: Option<Box<dyn Compressor>>, 450 454 ) -> Result<(Box<dyn Compressor>, Action)> { 451 - if input.len() != 1 { 452 - if let Some(c) = get_compressor_from_filename(output) { 453 - return Ok((c, Action::Compress)); 454 - } 455 - if output.is_dir() 456 - && let Some(first) = input.first() 457 - && let Some(c) = get_compressor_from_filename(first) 458 - { 455 + let input = match input { 456 + [single] => single, 457 + _ => { 458 + if let Some(c) = get_compressor_from_filename(output) { 459 + return Ok((c, Action::Compress)); 460 + } 461 + if output.is_dir() 462 + && let Some(first) = input.first() 463 + && let Some(c) = get_compressor_from_filename(first) 464 + { 465 + return Ok((c, Action::Extract)); 466 + } 467 + // No extension hint anywhere, but we were given a compressor — 468 + // assume the user wants to extract multiple archives to a directory. 469 + let c = compressor.ok_or_else(|| anyhow!("Could not determine compressor to use"))?; 459 470 return Ok((c, Action::Extract)); 460 471 } 461 - // No extension hint anywhere, but we were given a compressor — 462 - // assume the user wants to extract multiple archives to a directory. 463 - let c = compressor.ok_or_else(|| anyhow!("Could not determine compressor to use"))?; 464 - return Ok((c, Action::Extract)); 465 - } 466 - let input = input.first().unwrap(); 472 + }; 467 473 468 474 let output_guess = get_compressor_from_filename(output); 469 475 let input_guess = get_compressor_from_filename(input);
+1 -1
src/progress.rs
··· 102 102 }; 103 103 bar.set_style( 104 104 indicatif::ProgressStyle::default_bar() 105 - .template("{spinner:.green} [{elapsed_precise}] ({eta}) [{bar:40.cyan/blue}] {bytes}/{total_bytes} => {msg}").unwrap() 105 + .template("{spinner:.green} [{elapsed_precise}] ({eta}) [{bar:40.cyan/blue}] {bytes}/{total_bytes} => {msg}").expect("progress bar template literal is valid") 106 106 .progress_chars("#>-"), 107 107 ); 108 108 bar.enable_steady_tick(Duration::from_millis(100));
+10 -14
src/utils.rs
··· 143 143 return Ok(CompressionLevel { level }); 144 144 } 145 145 146 - // Try to parse special names 147 - let s = s.to_lowercase(); 148 - match s.as_str() { 149 - "none" | "fast" | "best" => Ok(CompressionLevel { 150 - // We'll use the DefaultCompressionValidator values here 151 - // The actual compressor will interpret these values according to its own validator 152 - level: DefaultCompressionValidator.name_to_level(&s).unwrap(), 153 - }), 154 - _ => Err("Invalid compression level"), 155 - } 146 + // Otherwise expect a named level ("none"/"fast"/"best"). The concrete 147 + // compressor re-interprets this value through its own validator, so we 148 + // start from the default mapping. 149 + let level = DefaultCompressionValidator 150 + .name_to_level(&s.to_lowercase()) 151 + .ok_or("Invalid compression level")?; 152 + Ok(CompressionLevel { level }) 156 153 } 157 154 } 158 155 ··· 213 210 /// Just checks the extension by default 214 211 /// Some compressors may overwrite this to do more advanced detection 215 212 fn is_archive(&self, in_path: &Path) -> bool { 216 - if in_path.extension().is_none() { 217 - return false; 218 - } 219 - in_path.extension().unwrap() == self.extension() 213 + in_path 214 + .extension() 215 + .is_some_and(|ext| ext == self.extension()) 220 216 } 221 217 222 218 /// Generate the default name for the compressed file