this repo has no description
0
fork

Configure Feed

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

refactor(job): extract mechanical helpers from get_job

Pull four leaf helpers out of get_job so its top now reads as a
phased pipeline:

let action = action_from_flags(args);
let (inputs, output) = partition_paths(args, action)?;
let input = resolve_input(inputs, args)?;
// ...resolve output + compressor + action

No behavior change; the remaining output/action inference logic is
unchanged and will be attacked in a follow-up.

+88 -88
+88 -88
src/job.rs
··· 13 13 use crate::backends::{self, Pipeline}; 14 14 use crate::utils::{CmprssInput, CmprssOutput, CommonArgs, Compressor, Result}; 15 15 16 - /// Defines a single compress/extract action to take. 17 - #[derive(Debug)] 18 - pub struct Job { 19 - pub compressor: Box<dyn Compressor>, 20 - pub input: CmprssInput, 21 - pub output: CmprssOutput, 22 - pub action: Action, 23 - } 24 - 25 - #[derive(Debug, PartialEq, Clone, Copy)] 26 - pub enum Action { 27 - Compress, 28 - Extract, 29 - Unknown, 16 + /// Extract an action hint from the CLI flags. Returns `Action::Unknown` when 17 + /// the user hasn't specified `--compress`/`--extract`/`--decompress`. 18 + fn action_from_flags(args: &CommonArgs) -> Action { 19 + if args.compress { 20 + Action::Compress 21 + } else if args.extract || args.decompress { 22 + Action::Extract 23 + } else { 24 + Action::Unknown 25 + } 30 26 } 31 27 32 - /// Parse the common args and determine the details of the job requested. 33 - pub fn get_job(compressor: Option<Box<dyn Compressor>>, common_args: &CommonArgs) -> Result<Job> { 34 - let mut compressor = compressor; 35 - let mut action = { 36 - if common_args.compress { 37 - Action::Compress 38 - } else if common_args.extract || common_args.decompress { 39 - Action::Extract 40 - } else { 41 - Action::Unknown 42 - } 43 - }; 44 - 28 + /// Partition the CLI path arguments (`-i`, `-o`, and the positional `io_list`) 29 + /// into a list of input paths and an optional output path. 30 + /// 31 + /// The heuristic for which trailing `io_list` entry becomes the output: 32 + /// * If it doesn't exist on disk → output (we'll create it). 33 + /// * If it exists and is a directory, and the action hint is `Extract` → 34 + /// output (extract into the directory). 35 + /// * Otherwise → treat as an input. This preserves the existing behavior for 36 + /// `cmprss tar file1.txt file2.txt existing_dir/`, where the trailing 37 + /// directory is treated as another input to archive. 38 + fn partition_paths( 39 + args: &CommonArgs, 40 + action_hint: Action, 41 + ) -> Result<(Vec<PathBuf>, Option<PathBuf>)> { 45 42 let mut inputs = Vec::new(); 46 - if let Some(in_file) = &common_args.input { 47 - match get_path(in_file) { 48 - Some(path) => inputs.push(path), 49 - None => { 50 - bail!("Specified input path does not exist"); 51 - } 52 - } 43 + if let Some(in_file) = &args.input { 44 + inputs 45 + .push(get_path(in_file).ok_or_else(|| anyhow!("Specified input path does not exist"))?); 53 46 } 54 47 55 - let mut output = match &common_args.output { 48 + let mut output: Option<PathBuf> = match &args.output { 56 49 Some(output) => { 57 - let path = Path::new(output); 50 + let path = PathBuf::from(output); 58 51 if path.try_exists()? && !path.is_dir() { 59 - // Output path exists, bail out 60 52 bail!("Specified output path already exists"); 61 53 } 62 54 Some(path) ··· 64 56 None => None, 65 57 }; 66 58 67 - // Process the io_list, check if there is an output first 68 - let mut io_list = common_args.io_list.clone(); 59 + let mut io_list = args.io_list.clone(); 69 60 if output.is_none() 70 - && let Some(possible_output) = common_args.io_list.last() 61 + && let Some(possible_output) = io_list.last() 71 62 { 72 - let path = Path::new(possible_output); 63 + let path = PathBuf::from(possible_output); 73 64 if !path.try_exists()? { 74 - // Use the given path if it doesn't exist 75 65 output = Some(path); 76 66 io_list.pop(); 77 - } else if path.is_dir() { 78 - match action { 79 - Action::Compress => { 80 - // A directory can potentially be a target output location or 81 - // an input, for now assume it is an input. 82 - } 83 - Action::Extract => { 84 - // Can extract to a directory, and it wouldn't make any sense as an input 85 - output = Some(path); 86 - io_list.pop(); 87 - } 88 - _ => { 89 - // TODO: don't know if this is an input or output, assume we're compressing this directory 90 - // This does cause problems for inferencing "cat archive.tar | cmprss tar ." 91 - // Probably need to add some special casing 92 - } 93 - }; 94 - } else { 95 - // TODO: check for scenarios where we want to append to an existing archive 67 + } else if path.is_dir() && action_hint == Action::Extract { 68 + // Only treat an existing directory as the output when the user 69 + // hinted extraction. In Compress/Unknown, we keep it as another 70 + // input — this matches e.g. `cmprss tar dir1/ dir2/`. 71 + output = Some(path); 72 + io_list.pop(); 96 73 } 74 + // TODO: check for scenarios where we want to append to an existing archive. 97 75 } 98 76 99 - // Validate the specified inputs 100 - // Everything in the io_list should be an input 101 77 for input in &io_list { 102 - if let Some(path) = get_path(input) { 103 - inputs.push(path); 104 - } else { 105 - bail!("Specified input path does not exist"); 106 - } 78 + inputs.push(get_path(input).ok_or_else(|| anyhow!("Specified input path does not exist"))?); 107 79 } 108 80 109 - // Fallback to stdin/stdout if we're missing files 110 - let cmprss_input = match inputs.is_empty() { 111 - true => { 112 - if !std::io::stdin().is_terminal() 113 - && !&common_args.ignore_pipes 114 - && !&common_args.ignore_stdin 115 - { 116 - CmprssInput::Pipe(std::io::stdin()) 117 - } else { 118 - bail!("No specified input"); 119 - } 120 - } 121 - false => CmprssInput::Path(inputs), 122 - }; 81 + Ok((inputs, output)) 82 + } 123 83 124 - let cmprss_output = match output { 125 - Some(path) => CmprssOutput::Path(path.to_path_buf()), 84 + /// Turn the collected input paths into a `CmprssInput`, falling back to 85 + /// stdin when no paths were given and stdin is piped. 86 + fn resolve_input(inputs: Vec<PathBuf>, args: &CommonArgs) -> Result<CmprssInput> { 87 + if !inputs.is_empty() { 88 + return Ok(CmprssInput::Path(inputs)); 89 + } 90 + if !std::io::stdin().is_terminal() && !args.ignore_pipes && !args.ignore_stdin { 91 + return Ok(CmprssInput::Pipe(std::io::stdin())); 92 + } 93 + bail!("No specified input"); 94 + } 95 + 96 + /// Whether we can send the output to stdout (piped, and the user hasn't 97 + /// suppressed pipe inference). 98 + fn stdout_pipe_usable(args: &CommonArgs) -> bool { 99 + !std::io::stdout().is_terminal() && !args.ignore_pipes && !args.ignore_stdout 100 + } 101 + 102 + /// Defines a single compress/extract action to take. 103 + #[derive(Debug)] 104 + pub struct Job { 105 + pub compressor: Box<dyn Compressor>, 106 + pub input: CmprssInput, 107 + pub output: CmprssOutput, 108 + pub action: Action, 109 + } 110 + 111 + #[derive(Debug, PartialEq, Clone, Copy)] 112 + pub enum Action { 113 + Compress, 114 + Extract, 115 + Unknown, 116 + } 117 + 118 + /// Parse the common args and determine the details of the job requested. 119 + 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); 122 + 123 + let (input_paths, output_path) = partition_paths(common_args, action)?; 124 + 125 + let cmprss_input = resolve_input(input_paths, common_args)?; 126 + 127 + let cmprss_output = match output_path { 128 + Some(path) => CmprssOutput::Path(path), 126 129 None => { 127 - if !std::io::stdout().is_terminal() 128 - && !&common_args.ignore_pipes 129 - && !&common_args.ignore_stdout 130 - { 130 + if stdout_pipe_usable(common_args) { 131 131 CmprssOutput::Pipe(std::io::stdout()) 132 132 } else { 133 133 match action {