···32323333#[derive(Args, Debug)]
3434struct ExtractArgs {
3535- /// Input file
3535+ /// Input/Output file/directory
3636 #[arg(index = 1)]
3737- input: String,
3737+ input: Option<String>,
38383939 /// Output file/directory
4040 #[arg(index = 2)]
···49495050#[derive(Args, Debug)]
5151struct CommonArgs {
5252- /// Input file
5252+ /// Input/Output file/directory
5353 #[arg(index = 1)]
5454- input: String,
5454+ input: Option<String>,
55555656 /// Output file/directory
5757 #[arg(index = 2)]
···9191 compression: u32,
9292}
93939494+/// Get the input filename or return an error
9595+fn get_input_filename(input: &Option<String>) -> Result<&String, io::Error> {
9696+ match input {
9797+ Some(filename) => Ok(filename),
9898+ None => Err(io::Error::new(
9999+ io::ErrorKind::Other,
100100+ "error: no input specified",
101101+ )),
102102+ }
103103+}
104104+105105+/// Get the default output filename or return error if the input isn't specified
106106+fn get_default_output<T: Compressor>(
107107+ compressor: &T,
108108+ input: &Option<String>,
109109+ extract: bool,
110110+) -> Result<String, io::Error> {
111111+ match extract {
112112+ true => Ok(compressor.default_extracted_filename(Path::new(get_input_filename(input)?))),
113113+ false => Ok(compressor.default_compressed_filename(Path::new(get_input_filename(input)?))),
114114+ }
115115+}
116116+94117fn command<T: Compressor>(compressor: T) -> Result<(), io::Error> {
95118 let args = compressor.common_args();
119119+ // Use to provide a longer lifetime for this value
120120+ let default_output;
96121 // Input prefers stdin if that is a pipe, and falls back to reading from a file.
97122 let input = match std::io::stdin().is_terminal() {
9898- true => CmprssInput::Path(Path::new(&args.input)),
99123 false => CmprssInput::Pipe(std::io::stdin()),
100100- };
101101- // Define the default output filename for use if we need it later
102102- let default_output = match args.extract {
103103- true => compressor.default_extracted_filename(Path::new(&args.input)),
104104- false => compressor.default_compressed_filename(Path::new(&args.input)),
124124+ true => {
125125+ // stdin isn't a pipe, need to read from a file
126126+ CmprssInput::Path(Path::new(get_input_filename(&args.input)?))
127127+ }
105128 };
106129 // Output prefers the stdout if we're piping, and falls back to piping to a file.
107130 // TODO: Not sure that this output logic is the right thing to do
···113136 true => {
114137 if args.output.is_none() {
115138 if !std::io::stdin().is_terminal() {
116116- // Use the 'input' file as the output
117117- // TODO: make input file optional and test existence
118118- CmprssOutput::Path(Path::new(&args.input))
139139+ // Stdin is being used as the input, so use the 'input' file as the output
140140+ CmprssOutput::Path(Path::new(get_input_filename(&args.input)?))
119141 } else {
142142+ default_output = get_default_output(&compressor, &args.input, args.extract)?;
120143 CmprssOutput::Path(Path::new(&default_output))
121144 }
122145 } else {