this repo has no description
0
fork

Configure Feed

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

refactor: simplifying the compressor API

+150 -162
+47 -2
src/gzip.rs
··· 1 1 use crate::utils::*; 2 2 use flate2::write::GzEncoder; 3 3 use flate2::{read::GzDecoder, Compression}; 4 + use std::fs::File; 4 5 use std::io::{self, Read, Write}; 5 6 6 7 pub struct Gzip { ··· 23 24 &self.common_args 24 25 } 25 26 27 + fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 28 + match (input, output) { 29 + (CmprssInput::Path(in_path), CmprssOutput::Path(out_path)) => { 30 + self.compress_internal(File::open(in_path)?, File::create(out_path)?) 31 + } 32 + (CmprssInput::Path(in_path), CmprssOutput::Pipe(out_pipe)) => { 33 + self.compress_internal(File::open(in_path)?, out_pipe) 34 + } 35 + (CmprssInput::Pipe(in_pipe), CmprssOutput::Path(out_path)) => { 36 + self.compress_internal(in_pipe, File::create(out_path)?) 37 + } 38 + (CmprssInput::Pipe(in_pipe), CmprssOutput::Pipe(out_pipe)) => { 39 + self.compress_internal(in_pipe, out_pipe) 40 + } 41 + } 42 + } 43 + 44 + fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 45 + match (input, output) { 46 + (CmprssInput::Path(in_path), CmprssOutput::Path(out_path)) => { 47 + self.extract_internal(File::open(in_path)?, File::create(out_path)?) 48 + } 49 + (CmprssInput::Path(in_path), CmprssOutput::Pipe(out_pipe)) => { 50 + self.extract_internal(File::open(in_path)?, out_pipe) 51 + } 52 + (CmprssInput::Pipe(in_pipe), CmprssOutput::Path(out_path)) => { 53 + self.extract_internal(in_pipe, File::create(out_path)?) 54 + } 55 + (CmprssInput::Pipe(in_pipe), CmprssOutput::Pipe(out_pipe)) => { 56 + self.extract_internal(in_pipe, out_pipe) 57 + } 58 + } 59 + } 60 + } 61 + 62 + impl Gzip { 26 63 /// Compress an input stream into a gzip archive. 27 - fn compress<I: Read, O: Write>(&self, mut input: I, output: O) -> Result<(), io::Error> { 64 + fn compress_internal<I: Read, O: Write>( 65 + &self, 66 + mut input: I, 67 + output: O, 68 + ) -> Result<(), io::Error> { 28 69 let mut encoder = GzEncoder::new(output, Compression::new(self.compression_level)); 29 70 30 71 std::io::copy(&mut input, &mut encoder)?; ··· 33 74 } 34 75 35 76 /// Extract the gzip compressed data 36 - fn extract<I: Read, O: Write>(&self, input: I, mut output: O) -> Result<(), io::Error> { 77 + fn extract_internal<I: Read, O: Write>( 78 + &self, 79 + input: I, 80 + mut output: O, 81 + ) -> Result<(), io::Error> { 37 82 let mut decoder = GzDecoder::new(input); 38 83 std::io::copy(&mut decoder, &mut output)?; 39 84 Ok(())
+40 -70
src/main.rs
··· 91 91 compression: u32, 92 92 } 93 93 94 - /// Generates the output filename. 95 - /// This either takes the given name or guesses the name based on the extension 96 - fn output_filename(input: &Path, output: &Option<String>, extension: &str) -> String { 97 - match output.clone() { 98 - Some(file) => file, 99 - None => { 100 - format!( 101 - "{}.{}", 102 - input.file_name().unwrap().to_str().unwrap(), 103 - extension 104 - ) 105 - } 106 - } 107 - } 108 - 109 - /// Compress using the compressor 110 - fn compress_generic<T: Compressor>(compressor: T) -> Result<(), io::Error> { 94 + fn command_targets<T: Compressor>(compressor: T) -> Result<(), io::Error> { 95 + let args = compressor.common_args(); 96 + // Input prefers stdin if that is a pipe, and falls back to reading from a file. 97 + let input = match std::io::stdin().is_terminal() { 98 + true => CmprssInput::Path(Path::new(&args.input)), 99 + false => CmprssInput::Pipe(std::io::stdin()), 100 + }; 101 + let default_output = match args.extract { 102 + true => compressor.default_extracted_filename(Path::new(&args.input)), 103 + false => compressor.default_compressed_filename(Path::new(&args.input)), 104 + }; 105 + // Output prefers the stdout if we're piping, and falls back to piping to a file. 106 + // TODO: Not sure that this output logic is the right thing to do 111 107 // TODO: Properly handle the output file 112 108 // Fail/Warn on existence 113 109 // Remove if you've created a stub 114 - let args = compressor.common_args(); 115 - let input_path = Path::new(&args.input); 116 - 117 - match &args.output { 118 - Some(out) => { 119 - // Output file specified, use that 120 - println!("Compressing {} into {}", input_path.display(), out); 121 - compressor.compress_path_to_path(input_path, out)?; 122 - } 123 - None => { 124 - // No output filename. Send to stdout if stream or guess the filename 125 - if std::io::stdout().is_terminal() { 126 - let out = output_filename(input_path, &args.output, compressor.extension()); 127 - println!("Compressing {} into {}", input_path.display(), out); 128 - compressor.compress_path_to_path(input_path, out)?; 110 + let output = match std::io::stdout().is_terminal() { 111 + false => CmprssOutput::Pipe(std::io::stdout()), 112 + true => { 113 + if args.output.is_none() { 114 + if !std::io::stdin().is_terminal() { 115 + // Use the 'input' file as the output 116 + // TODO: make input file optional and test existence 117 + CmprssOutput::Path(Path::new(&args.input)) 118 + } else { 119 + CmprssOutput::Path(Path::new(&default_output)) 120 + } 129 121 } else { 130 - // Stdout is a pipe, attempt to compress to that 131 - compressor.compress_file(input_path, std::io::stdout())?; 122 + CmprssOutput::Path(Path::new(args.output.as_ref().unwrap())) 132 123 } 133 124 } 134 - } 135 - Ok(()) 136 - } 137 - 138 - /// Implement compression/extraction with a generic Compressor. 139 - fn command_generic<T: Compressor>(compressor: T) -> Result<(), io::Error> { 140 - let args = compressor.common_args(); 141 - let input_path = Path::new(&args.input); 125 + }; 142 126 if args.compress { 143 - compress_generic(compressor)?; 127 + compressor.compress(input, output)?; 144 128 } else if args.extract { 145 - match &args.output { 146 - Some(out) => { 147 - // Output file specified, extract there 148 - compressor.extract_path_to_path(input_path, out)?; 149 - } 150 - None => { 151 - // No output file specified 152 - if std::io::stdout().is_terminal() { 153 - compressor.extract_path_to_path( 154 - input_path, 155 - compressor.default_extracted_filename(input_path), 156 - )?; 157 - } else { 158 - // Stdout is a pipe, extract to the pipe 159 - compressor.extract_file(input_path, std::io::stdout())?; 129 + compressor.extract(input, output)?; 130 + } else { 131 + // Neither compress or extract is specified. 132 + // Compress by default, warn if if looks like an archive. 133 + match &input { 134 + CmprssInput::Path(path) => { 135 + if path.extension().unwrap() == compressor.extension() { 136 + return cmprss_error( 137 + &format!("error: input appears to already be a {} archive, exiting. Use '--compress' if needed.", compressor.name())); 160 138 } 139 + compressor.compress(input, output)?; 161 140 } 162 - }; 163 - } else { 164 - // Neither is set. 165 - // Compress by default, warn if if looks like an archive. 166 - if input_path.extension().unwrap() == compressor.extension() { 167 - println!( 168 - "error: input appears to already be a {} archive, exiting. Use '--compress' if needed.", compressor.name() 169 - ) 170 - } else { 171 - compress_generic(compressor)?; 141 + _ => compressor.compress(input, output)?, 172 142 } 173 143 } 174 144 Ok(()) ··· 190 160 fn main() -> Result<(), io::Error> { 191 161 let args = CmprssArgs::parse(); 192 162 match args.format { 193 - Some(Format::Tar(a)) => command_generic(parse_tar(a)), 163 + Some(Format::Tar(a)) => command_targets(parse_tar(a)), 194 164 //Some(Format::Extract(a)) => command_extract(a), 195 - Some(Format::Gzip(a)) => command_generic(parse_gzip(a)), 165 + Some(Format::Gzip(a)) => command_targets(parse_gzip(a)), 196 166 _ => Err(io::Error::new(io::ErrorKind::Other, "unknown input")), 197 167 } 198 168 }
+45 -25
src/tar.rs
··· 26 26 ".".to_string() 27 27 } 28 28 29 - /// Compress an input file or directory into a tar archive. 30 - fn compress_file<I: AsRef<Path>, O: Write>( 29 + fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 30 + match output { 31 + CmprssOutput::Pipe(pipe) => self.compress_internal(input, Builder::new(pipe)), 32 + CmprssOutput::Path(path) => { 33 + self.compress_internal(input, Builder::new(File::create(path)?)) 34 + } 35 + } 36 + } 37 + 38 + fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 39 + match input { 40 + CmprssInput::Path(path) => { 41 + self.extract_internal(Archive::new(File::open(path)?), output) 42 + } 43 + CmprssInput::Pipe(pipe) => self.extract_internal(Archive::new(pipe), output), 44 + } 45 + } 46 + } 47 + 48 + impl Tar { 49 + /// Internal extract helper 50 + fn extract_internal<R: Read>( 31 51 &self, 32 - in_file: I, 33 - output: O, 52 + mut archive: Archive<R>, 53 + output: CmprssOutput, 34 54 ) -> Result<(), io::Error> { 35 - let in_file = in_file.as_ref(); 36 - let mut archive = Builder::new(output); 55 + let out_path = match output { 56 + CmprssOutput::Pipe(_) => { 57 + return cmprss_error("error: tar does not support stdout as extract output") 58 + } 59 + CmprssOutput::Path(path) => path, 60 + }; 61 + archive.unpack(out_path) 62 + } 63 + 64 + /// Internal compress helper 65 + fn compress_internal<W: Write>( 66 + &self, 67 + input: CmprssInput, 68 + mut archive: Builder<W>, 69 + ) -> Result<(), io::Error> { 70 + let in_file = match input { 71 + CmprssInput::Path(path) => path, 72 + CmprssInput::Pipe(_) => { 73 + return cmprss_error("error: tar does not support stdin as input") 74 + } 75 + }; 37 76 if in_file.is_file() { 38 77 archive.append_file(in_file.file_name().unwrap(), &mut File::open(in_file)?)?; 39 78 } else if in_file.is_dir() { ··· 45 84 )); 46 85 } 47 86 archive.finish() 48 - } 49 - 50 - /// Extract one path to another path 51 - fn extract_path_to_path<I: AsRef<Path>, O: AsRef<Path>>( 52 - &self, 53 - in_file: I, 54 - out_file: O, 55 - ) -> Result<(), io::Error> { 56 - self.extract_to_path(File::open(in_file)?, out_file) 57 - } 58 - 59 - /// Extract the archive into a directory 60 - fn extract_to_path<I: Read, O: AsRef<Path>>( 61 - &self, 62 - input: I, 63 - out_path: O, 64 - ) -> Result<(), io::Error> { 65 - let mut archive = Archive::new(input); 66 - archive.unpack(out_path.as_ref()) 67 87 } 68 88 }
+18 -65
src/utils.rs
··· 1 - use std::fs::File; 2 - use std::io::{self, Read, Write}; 1 + use std::io; 3 2 use std::path::Path; 4 3 5 4 pub struct CmprssCommonArgs { ··· 38 37 // TODO: There is probably a cleaner way to do this? 39 38 fn common_args(&self) -> &CmprssCommonArgs; 40 39 41 - /// Compress one path to another path 42 - fn compress_path_to_path<I: AsRef<Path>, O: AsRef<Path>>( 43 - &self, 44 - in_file: I, 45 - out_file: O, 46 - ) -> Result<(), io::Error> { 47 - self.compress_file(in_file, File::create(out_file)?) 40 + fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 41 + cmprss_error("compress_target unimplemented") 48 42 } 49 43 50 - /// Compress an input filename to a stream 51 - fn compress_file<I: AsRef<Path>, O: Write>( 52 - &self, 53 - in_file: I, 54 - output: O, 55 - ) -> Result<(), io::Error> { 56 - self.compress(File::open(in_file)?, output) 44 + fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 45 + cmprss_error("extract_target unimplemented") 57 46 } 47 + } 58 48 59 - /// Extract one path to another path 60 - fn extract_path_to_path<I: AsRef<Path>, O: AsRef<Path>>( 61 - &self, 62 - in_file: I, 63 - out_file: O, 64 - ) -> Result<(), io::Error> { 65 - self.extract_file(in_file, File::create(out_file)?) 66 - } 49 + pub fn cmprss_error(message: &str) -> Result<(), io::Error> { 50 + Err(io::Error::new(io::ErrorKind::Other, message)) 51 + } 67 52 68 - /// Extract an input filename to a stream 69 - fn extract_file<I: AsRef<Path>, O: Write>( 70 - &self, 71 - in_file: I, 72 - output: O, 73 - ) -> Result<(), io::Error> { 74 - self.extract(File::open(in_file)?, output) 75 - } 76 - 77 - /// Compress a Read trait object to a Write object. 78 - fn compress<I: Read, O: Write>(&self, input: I, output: O) -> Result<(), io::Error> { 79 - cmprss_error("compress unimplemented") 80 - } 81 - 82 - /// Extract a Read trait object to a Write object. 83 - fn extract<I: Read, O: Write>(&self, input: I, output: O) -> Result<(), io::Error> { 84 - cmprss_error("extract unimplemented") 85 - } 86 - 87 - /// Extract a Read trait object to a path. 88 - /// Some compressors require this instead of writing to a stream 89 - fn extract_to_path<I: Read, O: AsRef<Path>>( 90 - &self, 91 - input: I, 92 - out_path: O, 93 - ) -> Result<(), io::Error> { 94 - cmprss_error("extract_to_path unimplemented") 95 - } 96 - 97 - /// Extract a file to a path 98 - fn extract_file_to_path<I: AsRef<Path>, O: AsRef<Path>>( 99 - &self, 100 - input_file: I, 101 - out_directory: O, 102 - ) -> Result<(), io::Error> { 103 - let input_file = input_file.as_ref(); 104 - let out_directory = out_directory.as_ref(); 105 - self.extract_to_path(File::open(input_file)?, out_directory) 106 - } 53 + /// Defines the possible inputs of a compressor 54 + // TODO: Implement fmt for CmprssInput/CmprssOutput 55 + pub enum CmprssInput<'a> { 56 + Path(&'a Path), 57 + Pipe(std::io::Stdin), 107 58 } 108 59 109 - fn cmprss_error(message: &str) -> Result<(), io::Error> { 110 - Err(io::Error::new(io::ErrorKind::Other, message)) 60 + /// Defines the possible outputs of a compressor 61 + pub enum CmprssOutput<'a> { 62 + Path(&'a Path), 63 + Pipe(std::io::Stdout), 111 64 }