1use std::collections::HashMap;
2use std::io;
3
4use crate::command_handler::{CommandHandler, CommandResult};
5
6pub struct Cmd<R, W>
9where
10 W: io::Write + 'static,
11 R: io::BufRead + 'static,
12{
13 handles: HashMap<String, Box<dyn CommandHandler<W>>>,
14 stdin: R,
15 stdout: W,
16}
17
18impl<R, W> Cmd<R, W>
19where
20 W: io::Write + 'static,
21 R: io::BufRead + 'static,
22{
23 pub fn new(reader: R, writer: W) -> Cmd<R, W>
25 where
26 W: io::Write,
27 R: io::Read,
28 {
29 Cmd {
30 handles: HashMap::new(),
31 stdin: reader,
32 stdout: writer,
33 }
34 }
35
36 pub fn run(&mut self) -> Result<(), io::Error> {
40 loop {
41 write!(self.stdout, "(cmd) ")?;
44 self.stdout.flush()?;
45
46 let mut inputs = String::new();
48 self.stdin.read_line(&mut inputs)?;
49 let inputs = inputs.trim();
50
51 if !inputs.is_empty() {
53 let (command, args) = parse_cmd(inputs);
54 let args = split_args(args);
55
56 if let Some(handler) = self.handles.get(command) {
58 if matches!(
59 handler.execute(&mut self.stdout, &args),
60 CommandResult::Break
61 ) {
62 break;
63 }
64 } else {
65 writeln!(self.stdout, "No command {}", command)?;
66 }
67 }
68 }
69 Ok(())
70 }
71
72 pub fn add_cmd_fn(
76 &mut self,
77 name: String,
78 handler: impl Fn(&mut W, &[&str]) -> CommandResult + 'static,
79 ) -> Result<(), io::Error> {
80 self.add_cmd(name, handler)
81 }
82
83 pub fn add_cmd(
87 &mut self,
88 name: String,
89 handler: impl CommandHandler<W> + 'static,
90 ) -> Result<(), io::Error> {
91 match self.handles.get(&name) {
92 Some(_) => write!(
93 self.stdout,
94 "Warning: Command with handle {} already exists.",
95 name
96 )?,
97 None => {
98 self.handles.insert(name, Box::new(handler));
99 }
100 }
101
102 Ok(())
103 }
104
105 #[cfg(test)]
106 fn get_cmd(&self, key: String) -> Option<&Box<dyn CommandHandler<W>>> {
107 self.handles.get(&key)
108 }
109}
110
111fn parse_cmd(line: &str) -> (&str, &str) {
113 let line = line.trim();
114 let first_space = line.find(' ').unwrap_or(line.len());
115 let command = &line[..first_space];
116
117 let args = line[command.len()..].trim();
118 (command, args)
119}
120
121fn split_args(args: &str) -> Vec<&str> {
122 args.split_whitespace().map(|arg| arg.trim()).collect()
123}
124
125#[cfg(test)]
126mod tests {
127 use std::io::BufRead;
128 use std::io::{self, BufReader, Write};
129
130 use super::*;
131 use crate::command_handler::CommandResult;
132 use crate::handlers::Quit;
133
134 #[derive(Default)]
135 pub struct Greeting {}
136
137 impl<W: io::Write> CommandHandler<W> for Greeting {
138 fn execute(&self, stdout: &mut W, _args: &[&str]) -> CommandResult {
139 write!(stdout, "Hello there!").unwrap();
140 CommandResult::Continue
141 }
142 }
143
144 struct StdinAlwaysErr;
146
147 impl io::Read for StdinAlwaysErr {
148 fn read(&mut self, _: &mut [u8]) -> Result<usize, std::io::Error> {
149 Err(io::Error::new(io::ErrorKind::Other, "failed on read"))
150 }
151 }
152
153 struct StdoutWriteErr;
155
156 impl io::Write for StdoutWriteErr {
157 fn write(&mut self, _: &[u8]) -> Result<usize, std::io::Error> {
158 Err(io::Error::new(io::ErrorKind::Other, "failed on write"))
159 }
160 fn flush(&mut self) -> Result<(), std::io::Error> {
161 Ok(())
162 }
163 }
164 struct StdoutFlushErr;
166
167 impl io::Write for StdoutFlushErr {
168 fn write(&mut self, _: &[u8]) -> Result<usize, std::io::Error> {
169 Ok(1)
170 }
171 fn flush(&mut self) -> Result<(), std::io::Error> {
172 Err(io::Error::new(io::ErrorKind::Other, "failed on flush"))
173 }
174 }
175
176 fn setup() -> Cmd<io::BufReader<std::fs::File>, Vec<u8>> {
177 let f = std::fs::File::open("test_files/test_in.txt").unwrap();
178 let stdin = io::BufReader::new(f);
179
180 let stdout: Vec<u8> = Vec::new();
181 let mut app: Cmd<io::BufReader<std::fs::File>, Vec<u8>> = Cmd::new(stdin, stdout);
182 let greet_handler = Greeting::default();
183
184 app.add_cmd(String::from("greet"), greet_handler).unwrap();
186 app.add_cmd(String::from("quit"), Quit::default()).unwrap();
187 app
188 }
189
190 #[test]
191 fn test_add_cmd() {
192 let app = setup();
193 let mut stdout = vec![];
194
195 let h = app.get_cmd(String::from("greet"));
197 assert!(h.is_some());
198
199 h.unwrap().execute(&mut stdout, &[]);
201 assert_eq!(String::from_utf8(stdout).unwrap(), "Hello there!");
202 }
203
204 #[test]
205 fn test_add_existing_cmd() {
206 let mut app = setup();
207
208 app.add_cmd("greet".to_string(), Greeting {}).unwrap();
210
211 let mut std_out_lines = app.stdout.lines();
212 let line1 = std_out_lines.next().unwrap().unwrap();
213
214 assert_eq!(line1, "Warning: Command with handle greet already exists.");
215 }
216
217 #[test]
218 fn test_add_cmd_always_error() {
219 let f = std::fs::File::open("test_files/test_in.txt").unwrap();
220 let stdin = io::BufReader::new(f);
221 let stdout = StdoutWriteErr;
222 let mut app = Cmd::new(stdin, stdout);
223
224 let _ok = app.add_cmd("greet".to_string(), Greeting {}).unwrap();
226 let e = app.add_cmd("greet".to_string(), Greeting {}).unwrap_err();
227
228 assert_eq!(e.to_string(), "failed on write");
229 assert_eq!(e.kind(), io::ErrorKind::Other);
230 }
231
232 #[test]
233 fn test_parse_cmd() {
234 let line = "command arg1 arg2";
235 assert_eq!(parse_cmd(line), ("command", "arg1 arg2"))
236 }
237 #[test]
238
239 fn test_parse_cmd_empty_line() {
240 assert_eq!(parse_cmd(""), ("", ""));
241 assert_eq!(parse_cmd(" "), ("", ""));
242 }
243
244 #[test]
245 fn test_parse_cmd_remove_extra_spaces() {
246 let line = " command arg1 arg2";
247 assert_eq!(parse_cmd(line), ("command", "arg1 arg2"))
248 }
249
250 #[test]
251 fn test_parse_cmd_empty_args() {
252 let line = "command";
253 assert_eq!(parse_cmd(line), ("command", ""));
254
255 let line = " command";
256 assert_eq!(parse_cmd(line), ("command", ""));
257 }
258
259 #[test]
260 fn test_run() {
261 let mut app = setup();
262
263 app.run().unwrap();
264
265 let line1 = String::from_utf8(app.stdout).unwrap();
267
268 assert_eq!(
269 line1,
270 "(cmd) Hello there!(cmd) (cmd) No command non\n(cmd) "
271 );
272 }
273
274 #[test]
275 fn test_run_stdout_write_err() {
276 let f = std::fs::File::open("test_files/test_in.txt").unwrap();
277 let stdin = io::BufReader::new(f);
278 let stdout = StdoutWriteErr;
279 let mut app = Cmd::new(stdin, stdout);
280
281 app.stdout.flush().unwrap(); let e = app.run().unwrap_err();
284
285 assert_eq!(e.kind(), io::ErrorKind::Other);
286 assert_eq!(e.to_string(), "failed on write");
287 }
288
289 #[test]
290 fn test_run_stdout_flush_err() {
291 let f = std::fs::File::open("test_files/test_in.txt").unwrap();
292 let stdin = io::BufReader::new(f);
293 let stdout = StdoutFlushErr;
294 let mut app = Cmd::new(stdin, stdout);
295
296 let e = app.run().unwrap_err();
297
298 assert_eq!(e.kind(), io::ErrorKind::Other);
299 assert_eq!(e.to_string(), "failed on flush");
300 }
301
302 #[test]
303 fn test_run_stdin_read_err() {
304 let stdin = BufReader::new(StdinAlwaysErr);
305 let stdout = io::stdout();
306 let mut app = Cmd::new(stdin, stdout);
307
308 let e = app.run().unwrap_err();
309
310 assert_eq!(e.kind(), io::ErrorKind::Other);
311 assert_eq!(e.to_string(), "failed on read");
312 }
313
314 #[test]
315 fn test_split_args() {
316 let args = "arg1 arg2 arg3";
317 let expected = vec!["arg1", "arg2", "arg3"];
318 assert_eq!(split_args(args), expected);
319 }
320
321 #[test]
322 fn split_empty_args() {
323 let args = "";
324 let expected: Vec<&str> = vec![];
325 assert_eq!(split_args(args), expected);
326 }
327}