···1717 Pipeline { compressors }
1818 }
19192020- /// Create a new Pipeline from compressor type names
2121- pub fn from_names(compressor_names: &[String]) -> Result<Self> {
2222- let compressors = compressor_names
2323- .iter()
2424- .map(|name| Self::create_compressor(name))
2525- .collect::<Result<Vec<_>>>()?;
2626- Ok(Self { compressors })
2727- }
2828-2920 /// Get a string representation of the chained format (e.g., "tar.gz")
3021 fn format_chain(&self) -> String {
3122 self.compressors
+8-10
src/job.rs
···375375 // e.g., "a.b.tar.gz" → gz ✓, tar ✓, b ✗ stop → [gz, tar]
376376 // `chain_from_ext` handles both single-codec extensions and compound
377377 // shortcuts like `tgz` (which expand to `[tar, gz]`).
378378- let mut compressor_names: Vec<String> = Vec::new();
378378+ let mut chain: Vec<Box<dyn Compressor>> = Vec::new();
379379 for ext in parts[1..].iter().rev() {
380380 match backends::chain_from_ext(ext) {
381381- Some(chain) => {
382382- // chain is innermost→outermost; we walk the filename
381381+ Some(stage) => {
382382+ // stage is innermost→outermost; we walk the filename
383383 // right-to-left so we push outermost first.
384384- for c in chain.iter().rev() {
385385- compressor_names.push(c.name().to_string());
384384+ for c in stage.into_iter().rev() {
385385+ chain.push(c);
386386 }
387387 }
388388 None => break,
389389 }
390390 }
391391392392- if compressor_names.is_empty() {
392392+ if chain.is_empty() {
393393 return None;
394394 }
395395396396 // Reverse to innermost-to-outermost order
397397- compressor_names.reverse();
398398- Pipeline::from_names(&compressor_names)
399399- .ok()
400400- .map(|m| Box::new(m) as Box<dyn Compressor>)
397397+ chain.reverse();
398398+ Some(Box::new(Pipeline::new(chain)))
401399}
402400403401/// Convert an input path into a Path