this repo has no description
1use clap::Args;
2use std::ffi::OsStr;
3use std::fmt;
4use std::io;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7
8#[derive(Args, Debug)]
9pub struct CommonArgs {
10 /// Input file/directory
11 #[arg(short, long)]
12 pub input: Option<String>,
13
14 /// Output file/directory
15 #[arg(short, long)]
16 pub output: Option<String>,
17
18 /// Compress the input (default)
19 #[arg(short, long)]
20 pub compress: bool,
21
22 /// Extract the input
23 #[arg(short, long)]
24 pub extract: bool,
25
26 /// Decompress the input. Alias of --extract
27 #[arg(short, long)]
28 pub decompress: bool,
29
30 /// List of I/O.
31 /// This consists of all the inputs followed by the single output, with intelligent fallback to stdin/stdout.
32 #[arg()]
33 pub io_list: Vec<String>,
34
35 /// Ignore pipes when inferring I/O
36 #[arg(long)]
37 pub ignore_pipes: bool,
38
39 /// Ignore stdin when inferring I/O
40 #[arg(long)]
41 pub ignore_stdin: bool,
42
43 /// Ignore stdout when inferring I/O
44 #[arg(long)]
45 pub ignore_stdout: bool,
46}
47
48#[derive(Debug, Clone, Copy)]
49pub struct CompressionLevel {
50 pub level: u32,
51}
52
53impl Default for CompressionLevel {
54 fn default() -> Self {
55 CompressionLevel { level: 6 }
56 }
57}
58
59impl FromStr for CompressionLevel {
60 type Err = &'static str;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 // Check for an int
64 if let Ok(level) = s.parse::<u32>() {
65 if level < 10 {
66 return Ok(CompressionLevel { level });
67 } else {
68 return Err("Compression level must be 0-9");
69 }
70 }
71 let s = s.to_lowercase();
72 match s.as_str() {
73 "none" => Ok(CompressionLevel { level: 0 }),
74 "fast" => Ok(CompressionLevel { level: 1 }),
75 "best" => Ok(CompressionLevel { level: 9 }),
76 _ => Err("Invalid compression level"),
77 }
78 }
79}
80
81#[derive(Args, Debug, Default, Clone, Copy)]
82pub struct LevelArgs {
83 /// Level of compression.
84 /// This is an int 0-9, with 0 being no compression and 9 being highest compression.
85 /// Also supports 'none', 'fast', and 'best'.
86 #[arg(long, default_value = "6")]
87 pub level: CompressionLevel,
88}
89
90/// Common interface for all compressor implementations
91#[allow(unused_variables)]
92pub trait Compressor {
93 /// Name of this Compressor
94 fn name(&self) -> &str;
95
96 /// Default extension for this Compressor
97 fn extension(&self) -> &str {
98 self.name()
99 }
100
101 /// Detect if the input is an archive of this type
102 /// Just checks the extension by default
103 /// Some compressors may overwrite this to do more advanced detection
104 fn is_archive(&self, in_path: &Path) -> bool {
105 if in_path.extension().is_none() {
106 return false;
107 }
108 in_path.extension().unwrap() == self.extension()
109 }
110
111 /// Generate the default name for the compressed file
112 fn default_compressed_filename(&self, in_path: &Path) -> String {
113 format!(
114 "{}.{}",
115 in_path
116 .file_name()
117 .unwrap_or_else(|| OsStr::new("archive"))
118 .to_str()
119 .unwrap(),
120 self.extension()
121 )
122 }
123
124 /// Generate the default extracted filename
125 fn default_extracted_filename(&self, in_path: &Path) -> String {
126 // If the file has the extension for this type, return the filename without the extension
127 if in_path.extension().unwrap() == self.extension() {
128 return in_path.file_stem().unwrap().to_str().unwrap().to_string();
129 }
130 // If the file has no extension, return the current directory
131 if in_path.extension().is_none() {
132 return ".".to_string();
133 }
134 // Otherwise, return the current directory and hope for the best
135 ".".to_string()
136 }
137
138 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> {
139 cmprss_error("compress_target unimplemented")
140 }
141
142 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> {
143 cmprss_error("extract_target unimplemented")
144 }
145}
146
147impl fmt::Debug for dyn Compressor {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "Compressor {{ name: {} }}", self.name())
150 }
151}
152
153pub fn cmprss_error(message: &str) -> Result<(), io::Error> {
154 Err(io::Error::new(io::ErrorKind::Other, message))
155}
156
157/// Defines the possible inputs of a compressor
158#[derive(Debug)]
159pub enum CmprssInput {
160 /// Path(s) to the input files.
161 Path(Vec<PathBuf>),
162 /// Input pipe
163 Pipe(std::io::Stdin),
164}
165
166/// Defines the possible outputs of a compressor
167#[derive(Debug)]
168pub enum CmprssOutput {
169 Path(PathBuf),
170 Pipe(std::io::Stdout),
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn compression_level_parsing() {
179 assert_eq!(CompressionLevel::from_str("0").unwrap().level, 0);
180 assert_eq!(CompressionLevel::from_str("1").unwrap().level, 1);
181 assert_eq!(CompressionLevel::from_str("9").unwrap().level, 9);
182 assert_eq!(CompressionLevel::from_str("none").unwrap().level, 0);
183 assert_eq!(CompressionLevel::from_str("fast").unwrap().level, 1);
184 assert_eq!(CompressionLevel::from_str("best").unwrap().level, 9);
185 assert!(CompressionLevel::from_str("-1").is_err());
186 assert!(CompressionLevel::from_str("10").is_err());
187 assert!(CompressionLevel::from_str("foo").is_err());
188 }
189}