···1111pub struct Pipeline {
1212 // The chain of compressors to apply in order (innermost to outermost)
1313 compressors: Vec<Box<dyn Compressor>>,
1414+ /// Preserves the user's original format string (e.g. `tgz`) so default
1515+ /// filenames use it verbatim instead of the dotted composition of each
1616+ /// stage's extension. `None` falls back to joining the per-stage
1717+ /// extensions.
1818+ format_override: Option<String>,
1419}
15201621impl Clone for Pipeline {
1722 fn clone(&self) -> Self {
1823 Pipeline {
1924 compressors: self.compressors.iter().map(|c| c.clone_boxed()).collect(),
2525+ format_override: self.format_override.clone(),
2026 }
2127 }
2228}
···3339impl Pipeline {
3440 /// Create a new Pipeline with the given compressors
3541 pub fn new(compressors: Vec<Box<dyn Compressor>>) -> Self {
3636- Pipeline { compressors }
4242+ Pipeline {
4343+ compressors,
4444+ format_override: None,
4545+ }
4646+ }
4747+4848+ /// Create a Pipeline that keeps `format` as its canonical format string,
4949+ /// used for default output filenames. Intended for shortcut forms like
5050+ /// `tgz` where the user-facing extension differs from the dotted chain.
5151+ pub fn with_format(compressors: Vec<Box<dyn Compressor>>, format: String) -> Self {
5252+ Pipeline {
5353+ compressors,
5454+ format_override: Some(format),
5555+ }
3756 }
38573958 /// Get a string representation of the chained format (e.g., "tar.gz")
4059 fn format_chain(&self) -> String {
6060+ if let Some(ref f) = self.format_override {
6161+ return f.clone();
6262+ }
4163 self.compressors
4264 .iter()
4365 .map(|c| c.extension())
+2-1
src/main.rs
···8989 return None;
9090 }
9191 let chain = chain_from_format_str(first)?;
9292+ let format = first.clone();
9293 io_list.remove(0);
9393- Some(Box::new(Pipeline::new(chain)))
9494+ Some(Box::new(Pipeline::with_format(chain, format)))
9495}
95969697fn write_completions(shell: Shell) -> Result {
+27
tests/shortcuts.rs
···113113mod format_prefix {
114114 use super::*;
115115116116+ /// When the user explicitly names a shortcut format like `tgz`, the
117117+ /// inferred output filename should keep that spelling instead of the
118118+ /// expanded `.tar.gz` form.
119119+ #[test]
120120+ fn shortcut_preserves_extension_in_default_name() -> Result<(), Box<dyn std::error::Error>> {
121121+ let working_dir = create_working_dir()?;
122122+ let src = working_dir.child("payload");
123123+ src.create_dir_all()?;
124124+ src.child("a.txt").write_str("hello")?;
125125+126126+ let mut compress = Command::cargo_bin("cmprss")?;
127127+ compress
128128+ .current_dir(&working_dir)
129129+ .arg("--ignore-pipes")
130130+ .arg("tgz")
131131+ .arg("payload");
132132+ compress.assert().success();
133133+134134+ working_dir
135135+ .child("payload.tgz")
136136+ .assert(predicate::path::is_file());
137137+ working_dir
138138+ .child("payload.tar.gz")
139139+ .assert(predicate::path::missing());
140140+ Ok(())
141141+ }
142142+116143 #[test]
117144 fn tar_gz() -> Result<(), Box<dyn std::error::Error>> {
118145 format_prefix_roundtrip("tar.gz", "tar.gz")