1 use std::path::PathBuf;
2 use std::{env, ffi, fs, io, process};
3 
4 extern crate weezl;
5 use weezl::{decode as delzw, encode as enlzw, BitOrder};
6 
main()7 fn main() {
8     let args = env::args_os().skip(1);
9     let flags = Flags::from_args(args).unwrap_or_else(|ParamError| explain());
10 
11     let out = io::stdout();
12     let out = out.lock();
13 
14     let mut files = flags.files;
15     let input = files.pop().unwrap_or_else(explain);
16     if !files.is_empty() {
17         return explain();
18     }
19     let operation = flags.operation.unwrap_or_else(explain);
20     let min_code = if flags.min_code < 2 || flags.min_code > 12 {
21         return explain();
22     } else {
23         flags.min_code
24     };
25     let bit_order = flags.bit_order;
26 
27     let result = match (input, operation) {
28         (Input::File(file), Operation::Encode) => (|| {
29             let data = fs::File::open(file)?;
30             let file = io::BufReader::with_capacity(1 << 26, data);
31 
32             let mut encoder = enlzw::Encoder::new(bit_order, min_code);
33             encoder.into_stream(out).encode_all(file).status
34         })(),
35         (Input::Stdin, Operation::Encode) => {
36             let input = io::BufReader::with_capacity(1 << 26, io::stdin());
37             let mut encoder = enlzw::Encoder::new(bit_order, min_code);
38             encoder.into_stream(out).encode_all(input).status
39         }
40         (Input::File(file), Operation::Decode) => (|| {
41             let data = fs::File::open(file)?;
42             let file = io::BufReader::with_capacity(1 << 26, data);
43 
44             let mut decoder = delzw::Decoder::new(bit_order, min_code);
45             decoder.into_stream(out).decode_all(file).status
46         })(),
47         (Input::Stdin, Operation::Decode) => {
48             let input = io::BufReader::with_capacity(1 << 26, io::stdin());
49             let mut decoder = delzw::Decoder::new(bit_order, min_code);
50             decoder.into_stream(out).decode_all(input).status
51         }
52     };
53 
54     result.expect("Operation Failed: ");
55 }
56 
57 struct Flags {
58     files: Vec<Input>,
59     operation: Option<Operation>,
60     min_code: u8,
61     bit_order: BitOrder,
62 }
63 
64 struct ParamError;
65 
66 enum Input {
67     File(PathBuf),
68     Stdin,
69 }
70 
71 enum Operation {
72     Encode,
73     Decode,
74 }
75 
explain<T>() -> T76 fn explain<T>() -> T {
77     println!(
78         "Usage: lzw [-e|-d] <file>\n\
79         Arguments:\n\
80         -e\t operation encode (default)\n\
81         -d\t operation decode\n\
82         <file>\tfilepath or '-' for stdin"
83     );
84     process::exit(1);
85 }
86 
87 impl Default for Flags {
default() -> Flags88     fn default() -> Flags {
89         Flags {
90             files: vec![],
91             operation: None,
92             min_code: 8,
93             bit_order: BitOrder::Msb,
94         }
95     }
96 }
97 
98 impl Flags {
from_args(mut args: impl Iterator<Item = ffi::OsString>) -> Result<Self, ParamError>99     fn from_args(mut args: impl Iterator<Item = ffi::OsString>) -> Result<Self, ParamError> {
100         let mut flags = Flags::default();
101         let mut operation = None;
102         loop {
103             match args.next().as_ref().and_then(|s| s.to_str()) {
104                 Some("-d") | Some("--decode") => {
105                     if operation.is_some() {
106                         return Err(ParamError);
107                     }
108                     operation = Some(Operation::Decode);
109                 }
110                 Some("-e") | Some("--encode") => {
111                     if operation.is_some() {
112                         return Err(ParamError);
113                     }
114                     operation = Some(Operation::Encode);
115                 }
116                 Some("-w") | Some("--word-bits") => match args.next() {
117                     None => return Err(ParamError),
118                     Some(bits) => {
119                         let st = bits.to_str().ok_or(ParamError)?;
120                         flags.min_code = st.parse().ok().ok_or(ParamError)?;
121                     }
122                 },
123                 Some("-le") | Some("--little-endian") => {
124                     flags.bit_order = BitOrder::Lsb;
125                 }
126                 Some("-be") | Some("--big-endian") | Some("-ne") | Some("--network-endian") => {
127                     flags.bit_order = BitOrder::Msb;
128                 }
129                 Some("-") => {
130                     flags.files.push(Input::Stdin);
131                 }
132                 Some(other) if other.starts_with('-') => {
133                     // Reserved for future use.
134                     // -a: self-describing archive format, similar to actual compress
135                     // -b: maximum bits
136                     // -v: verbosity
137                     // some compress compatibility mode? Probably through arg(0) though.
138                     return Err(ParamError);
139                 }
140                 Some(file) => {
141                     flags.files.push(Input::File(file.into()));
142                 }
143                 None => break,
144             };
145         }
146 
147         flags.files.extend(args.map(|file| {
148             if let Some("-") = file.to_str() {
149                 Input::Stdin
150             } else {
151                 Input::File(file.into())
152             }
153         }));
154 
155         flags.operation = operation;
156         Ok(flags)
157     }
158 }
159