this repo has no description
0
fork

Configure Feed

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

refactor(job): linearize output/action/compressor resolution in get_job

Replace the tangled pair of nested matches with three explicit branches
keyed on how the output is determined:

1. Explicit output path (-o or trailing io_list item)
2. Stdout pipe
3. No output — invent a filename from the resolved compressor+action

Extract finalize_with_output, finalize_without_output, and
fill_missing_from_io as focused helpers. Each is ~linear; the previous
interleaving of output construction with as-side-effect action/compressor
updates is gone.

Behavior preserved; tests stay green.

+177 -165
+177 -165
src/job.rs
··· 116 116 } 117 117 118 118 /// Parse the common args and determine the details of the job requested. 119 + /// 120 + /// The resolution has three phases: 121 + /// 1. Collect explicit signals from CLI flags (action hint, `-i`/`-o`, the 122 + /// positional `io_list`) into an input list and an optional output path. 123 + /// 2. Build the final `CmprssInput` (falling back to stdin if no paths). 124 + /// 3. Decide the output and the missing pieces of (compressor, action). This 125 + /// branches on how the output is determined: an explicit path, stdout pipe, 126 + /// or a filename we invent from the resolved compressor + action. 119 127 pub fn get_job(compressor: Option<Box<dyn Compressor>>, common_args: &CommonArgs) -> Result<Job> { 120 - let mut compressor = compressor; 121 - let mut action = action_from_flags(common_args); 128 + let action_hint = action_from_flags(common_args); 129 + let (input_paths, output_path) = partition_paths(common_args, action_hint)?; 130 + let input = resolve_input(input_paths, common_args)?; 122 131 123 - let (input_paths, output_path) = partition_paths(common_args, action)?; 132 + // Branch 1: user gave us an output path. Resolve compressor + action 133 + // using both sides' extensions. 134 + if let Some(path) = output_path { 135 + let output = CmprssOutput::Path(path); 136 + let (compressor, action) = finalize_with_output(compressor, action_hint, &input, &output)?; 137 + return Ok(Job { 138 + compressor, 139 + input, 140 + output, 141 + action, 142 + }); 143 + } 124 144 125 - let cmprss_input = resolve_input(input_paths, common_args)?; 145 + // Branch 2: stdout is a pipe. Same resolution, but the output has no path. 146 + if stdout_pipe_usable(common_args) { 147 + let output = CmprssOutput::Pipe(std::io::stdout()); 148 + let (compressor, action) = finalize_with_output(compressor, action_hint, &input, &output)?; 149 + return Ok(Job { 150 + compressor, 151 + input, 152 + output, 153 + action, 154 + }); 155 + } 126 156 127 - let cmprss_output = match output_path { 128 - Some(path) => CmprssOutput::Path(path), 129 - None => { 130 - if stdout_pipe_usable(common_args) { 131 - CmprssOutput::Pipe(std::io::stdout()) 132 - } else { 133 - match action { 134 - Action::Compress => { 135 - let c = compressor 136 - .as_ref() 137 - .ok_or_else(|| anyhow!("Must specify a compressor"))?; 138 - CmprssOutput::Path(PathBuf::from( 139 - c.default_compressed_filename(get_input_filename(&cmprss_input)?), 140 - )) 141 - } 142 - Action::Extract => { 143 - if compressor.is_none() { 144 - compressor = 145 - get_compressor_from_filename(get_input_filename(&cmprss_input)?); 146 - } 147 - let c = compressor 148 - .as_ref() 149 - .ok_or_else(|| anyhow!("Must specify a compressor"))?; 150 - CmprssOutput::Path(PathBuf::from( 151 - c.default_extracted_filename(get_input_filename(&cmprss_input)?), 152 - )) 153 - } 154 - Action::Unknown => { 155 - if let Some(ref c) = compressor { 156 - // We know the compressor, does the input have the same extension? 157 - if let Some(compressor_from_input) = 158 - get_compressor_from_filename(get_input_filename(&cmprss_input)?) 159 - { 160 - if c.name() == compressor_from_input.name() { 161 - action = Action::Extract; 162 - CmprssOutput::Path(PathBuf::from(c.default_extracted_filename( 163 - get_input_filename(&cmprss_input)?, 164 - ))) 165 - } else { 166 - action = Action::Compress; 167 - CmprssOutput::Path(PathBuf::from( 168 - c.default_compressed_filename(get_input_filename( 169 - &cmprss_input, 170 - )?), 171 - )) 172 - } 173 - } else { 174 - action = Action::Compress; 175 - CmprssOutput::Path(PathBuf::from(c.default_compressed_filename( 176 - get_input_filename(&cmprss_input)?, 177 - ))) 178 - } 179 - } else { 180 - // Can still work if the input is an archive 181 - compressor = 182 - get_compressor_from_filename(get_input_filename(&cmprss_input)?); 183 - let c = compressor 184 - .as_ref() 185 - .ok_or_else(|| anyhow!("Must specify a compressor"))?; 186 - action = Action::Extract; 187 - CmprssOutput::Path(PathBuf::from( 188 - c.default_extracted_filename(get_input_filename(&cmprss_input)?), 189 - )) 190 - } 191 - } 192 - } 193 - } 194 - } 157 + // Branch 3: no output and stdout is a terminal. We must invent a filename, 158 + // which requires the compressor and action up front. 159 + let (compressor, action) = finalize_without_output(compressor, action_hint, &input)?; 160 + let default_name = match action { 161 + Action::Compress => compressor.default_compressed_filename(get_input_filename(&input)?), 162 + Action::Extract => compressor.default_extracted_filename(get_input_filename(&input)?), 163 + Action::Unknown => bail!("Could not determine action to take"), 195 164 }; 165 + Ok(Job { 166 + compressor, 167 + input, 168 + output: CmprssOutput::Path(PathBuf::from(default_name)), 169 + action, 170 + }) 171 + } 196 172 197 - // If we don't have the compressor/action, we can attempt to infer 173 + /// Finalize compressor + action when the output is already materialized 174 + /// (either `CmprssOutput::Path` or `CmprssOutput::Pipe`). 175 + fn finalize_with_output( 176 + mut compressor: Option<Box<dyn Compressor>>, 177 + mut action: Action, 178 + input: &CmprssInput, 179 + output: &CmprssOutput, 180 + ) -> Result<(Box<dyn Compressor>, Action)> { 198 181 if compressor.is_none() || action == Action::Unknown { 199 - match action { 200 - Action::Compress => { 201 - // Look at the output name 202 - if let CmprssOutput::Path(path) = &cmprss_output { 203 - compressor = get_compressor_from_filename(path); 182 + fill_missing_from_io(&mut compressor, &mut action, input, output)?; 183 + } 184 + let compressor = compressor.ok_or_else(|| anyhow!("Could not determine compressor to use"))?; 185 + if action == Action::Unknown { 186 + bail!("Could not determine action to take"); 187 + } 188 + Ok((compressor, action)) 189 + } 190 + 191 + /// Finalize compressor + action when no output path is known (stdout is a 192 + /// terminal and we'll invent a filename next). All inference must come from 193 + /// the input side. 194 + fn finalize_without_output( 195 + compressor: Option<Box<dyn Compressor>>, 196 + action: Action, 197 + input: &CmprssInput, 198 + ) -> Result<(Box<dyn Compressor>, Action)> { 199 + let input_path = get_input_filename(input)?; 200 + match action { 201 + Action::Compress => { 202 + let c = compressor.ok_or_else(|| anyhow!("Must specify a compressor"))?; 203 + Ok((c, Action::Compress)) 204 + } 205 + Action::Extract => { 206 + let c = compressor 207 + .or_else(|| get_compressor_from_filename(input_path)) 208 + .ok_or_else(|| anyhow!("Must specify a compressor"))?; 209 + Ok((c, Action::Extract)) 210 + } 211 + Action::Unknown => match compressor { 212 + Some(c) => { 213 + // Compare the compressor's extension against the input's. 214 + let action = match get_compressor_from_filename(input_path) { 215 + Some(ic) if ic.name() == c.name() => Action::Extract, 216 + _ => Action::Compress, 217 + }; 218 + Ok((c, action)) 219 + } 220 + None => { 221 + // The input has to be something we can identify as an archive. 222 + let c = get_compressor_from_filename(input_path) 223 + .ok_or_else(|| anyhow!("Must specify a compressor"))?; 224 + Ok((c, Action::Extract)) 225 + } 226 + }, 227 + } 228 + } 229 + 230 + /// Fill in a missing compressor and/or action by inspecting the input and 231 + /// output shapes. Called after the output is known; covers every combination 232 + /// of (Path, Pipe) input × (Path, Pipe) output. 233 + fn fill_missing_from_io( 234 + compressor: &mut Option<Box<dyn Compressor>>, 235 + action: &mut Action, 236 + input: &CmprssInput, 237 + output: &CmprssOutput, 238 + ) -> Result { 239 + match *action { 240 + Action::Compress => { 241 + if let CmprssOutput::Path(path) = output { 242 + *compressor = get_compressor_from_filename(path); 243 + } 244 + } 245 + Action::Extract => { 246 + if let CmprssInput::Path(paths) = input { 247 + if paths.len() != 1 { 248 + bail!("Expected a single archive to extract"); 204 249 } 250 + *compressor = get_compressor_from_filename(paths.first().unwrap()); 205 251 } 206 - Action::Extract => { 207 - if let CmprssInput::Path(paths) = &cmprss_input { 208 - if paths.len() != 1 { 209 - bail!("Expected a single archive to extract"); 252 + } 253 + Action::Unknown => match (input, output) { 254 + (CmprssInput::Path(paths), CmprssOutput::Path(path)) => { 255 + if path.is_dir() && paths.len() == 1 { 256 + *compressor = get_compressor_from_filename(paths.first().unwrap()); 257 + *action = Action::Extract; 258 + if compressor.is_none() { 259 + bail!( 260 + "Couldn't determine how to extract {:?}", 261 + paths.first().unwrap() 262 + ); 210 263 } 211 - compressor = get_compressor_from_filename(paths.first().unwrap()); 264 + } else { 265 + let (c, a) = guess_from_filenames(paths, path, compressor.take()); 266 + *compressor = c; 267 + *action = a; 212 268 } 213 269 } 214 - Action::Unknown => match (&cmprss_input, &cmprss_output) { 215 - (CmprssInput::Path(paths), CmprssOutput::Path(path)) => { 216 - if path.is_dir() && paths.len() == 1 { 217 - compressor = get_compressor_from_filename(paths.first().unwrap()); 218 - action = Action::Extract; 219 - 220 - if compressor.is_none() { 221 - bail!( 222 - "Couldn't determine how to extract {:?}", 223 - paths.first().unwrap() 224 - ); 225 - } 270 + (CmprssInput::Path(paths), CmprssOutput::Pipe(_)) => { 271 + if let Some(c) = compressor.as_deref() { 272 + *action = match get_compressor_from_filename(paths.first().unwrap()) { 273 + Some(ic) if ic.name() == c.name() => Action::Extract, 274 + _ => Action::Compress, 275 + }; 276 + } else { 277 + if paths.len() != 1 { 278 + bail!("Expected a single input file for piping to stdout"); 279 + } 280 + *compressor = get_compressor_from_filename(paths.first().unwrap()); 281 + if compressor.is_some() { 282 + *action = Action::Extract; 226 283 } else { 227 - let (guessed_compressor, guessed_action) = 228 - guess_from_filenames(paths, path, compressor); 229 - compressor = guessed_compressor; 230 - action = guessed_action; 284 + bail!("Can't guess compressor to use"); 231 285 } 232 286 } 233 - (CmprssInput::Path(paths), CmprssOutput::Pipe(_)) => { 234 - if let Some(ref c) = compressor { 235 - if let Some(input_c) = get_compressor_from_filename(paths.first().unwrap()) 236 - { 237 - if c.name() == input_c.name() { 238 - action = Action::Extract; 239 - } else { 240 - action = Action::Compress; 241 - } 242 - } else { 243 - action = Action::Compress; 244 - } 287 + } 288 + (CmprssInput::Pipe(_), CmprssOutput::Path(path)) => { 289 + if let Some(c) = compressor.as_deref() { 290 + *action = if get_compressor_from_filename(path) 291 + .is_some_and(|pc| c.name() == pc.name()) 292 + { 293 + Action::Compress 245 294 } else { 246 - if paths.len() != 1 { 247 - bail!("Expected a single input file for piping to stdout"); 248 - } 249 - compressor = get_compressor_from_filename(paths.first().unwrap()); 250 - if compressor.is_some() { 251 - action = Action::Extract; 252 - } else { 253 - bail!("Can't guess compressor to use"); 254 - } 255 - } 256 - } 257 - (CmprssInput::Pipe(_), CmprssOutput::Path(path)) => { 258 - if let Some(ref c) = compressor { 259 - if get_compressor_from_filename(path) 260 - .is_some_and(|pc| c.name() == pc.name()) 261 - { 262 - action = Action::Compress; 263 - } else { 264 - action = Action::Extract; 265 - } 295 + Action::Extract 296 + }; 297 + } else { 298 + *compressor = get_compressor_from_filename(path); 299 + if compressor.is_some() { 300 + *action = Action::Compress; 266 301 } else { 267 - compressor = get_compressor_from_filename(path); 268 - if compressor.is_some() { 269 - action = Action::Compress; 270 - } else { 271 - bail!("Can't guess compressor to use"); 272 - } 302 + bail!("Can't guess compressor to use"); 273 303 } 274 304 } 275 - (CmprssInput::Pipe(_), CmprssOutput::Pipe(_)) => { 276 - action = Action::Compress; 277 - } 278 - // Handle all Writer output cases 279 - (_, CmprssOutput::Writer(_)) => { 280 - // Writer outputs are only used internally by Pipeline 281 - // In main.rs we'll assume compression 282 - action = Action::Compress; 283 - } 284 - // Handle all Reader input cases 285 - (&CmprssInput::Reader(_), _) => { 286 - // For Reader input, we'll assume extraction 287 - action = Action::Extract; 288 - } 289 - }, 290 - } 291 - } 292 - 293 - let compressor = compressor.ok_or_else(|| anyhow!("Could not determine compressor to use"))?; 294 - if action == Action::Unknown { 295 - bail!("Could not determine action to take"); 305 + } 306 + (CmprssInput::Pipe(_), CmprssOutput::Pipe(_)) => { 307 + *action = Action::Compress; 308 + } 309 + // Writer output and Reader input are only constructed internally 310 + // by the Pipeline compressor; they don't reach get_job from main. 311 + (_, CmprssOutput::Writer(_)) => *action = Action::Compress, 312 + (CmprssInput::Reader(_), _) => *action = Action::Extract, 313 + }, 296 314 } 297 - 298 - Ok(Job { 299 - compressor, 300 - input: cmprss_input, 301 - output: cmprss_output, 302 - action, 303 - }) 315 + Ok(()) 304 316 } 305 317 306 318 /// Get the input filename or return a default file