···4242 _ => None,
4343 }
4444}
4545+4646+/// Resolve an extension to a compressor chain in innermost→outermost order.
4747+/// Single-codec extensions (`gz`, `xz`, `tar`, …) produce a one-element chain;
4848+/// compound shortcut extensions (`tgz`, `tbz`, `tbz2`, `txz`, `tzst`) expand
4949+/// into the chain they represent (e.g. `tgz` → `[tar, gz]`).
5050+///
5151+/// This is the single source of truth for what any archive-like extension
5252+/// means. Both single extensions and compound shortcuts flow through here.
5353+pub fn chain_from_ext(ext: &str) -> Option<Vec<Box<dyn Compressor>>> {
5454+ match ext {
5555+ "tgz" => Some(vec![Box::<Tar>::default(), Box::<Gzip>::default()]),
5656+ "tbz" | "tbz2" => Some(vec![Box::<Tar>::default(), Box::<Bzip2>::default()]),
5757+ "txz" => Some(vec![Box::<Tar>::default(), Box::<Xz>::default()]),
5858+ "tzst" => Some(vec![Box::<Tar>::default(), Box::<Zstd>::default()]),
5959+ _ => compressor_from_str(ext).map(|c| vec![c]),
6060+ }
6161+}
+10-24
src/job.rs
···361361 }
362362}
363363364364-/// Expand a compound shortcut extension like `.tgz` into its equivalent
365365-/// compressor chain, in innermost-to-outermost order. Returns `None` for
366366-/// extensions that aren't a known shortcut.
367367-fn expand_shortcut_ext(ext: &str) -> Option<&'static [&'static str]> {
368368- match ext {
369369- "tgz" => Some(&["tar", "gz"]),
370370- "tbz" | "tbz2" => Some(&["tar", "bz2"]),
371371- "txz" => Some(&["tar", "xz"]),
372372- "tzst" => Some(&["tar", "zst"]),
373373- _ => None,
374374- }
375375-}
376376-377364/// Get a compressor pipeline from a filename by scanning extensions right-to-left
378365pub fn get_compressor_from_filename(filename: &Path) -> Option<Box<dyn Compressor>> {
379366 let file_name = filename.file_name()?.to_str()?;
···386373 // Scan extensions right-to-left, collecting known compressors
387374 // until hitting an unknown extension or the base name.
388375 // e.g., "a.b.tar.gz" → gz ✓, tar ✓, b ✗ stop → [gz, tar]
389389- // Compound shortcuts like "tgz" expand to their component chain, so
390390- // "archive.tgz" behaves identically to "archive.tar.gz".
376376+ // `chain_from_ext` handles both single-codec extensions and compound
377377+ // shortcuts like `tgz` (which expand to `[tar, gz]`).
391378 let mut compressor_names: Vec<String> = Vec::new();
392379 for ext in parts[1..].iter().rev() {
393393- if let Some(chain) = expand_shortcut_ext(ext) {
394394- // chain is innermost→outermost; we push in right-to-left order
395395- // (outermost first) to match how we're walking the filename.
396396- for name in chain.iter().rev() {
397397- compressor_names.push((*name).to_string());
380380+ match backends::chain_from_ext(ext) {
381381+ Some(chain) => {
382382+ // chain 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());
386386+ }
398387 }
399399- } else if let Some(c) = backends::compressor_from_str(ext) {
400400- compressor_names.push(c.name().to_string());
401401- } else {
402402- break;
388388+ None => break,
403389 }
404390 }
405391