i6_shell/
command.rs

1#![allow(non_camel_case_types)]
2#![allow(unused_variables)]
3
4use std::{
5  collections::HashMap,
6  sync::{Arc, Mutex},
7};
8
9use lazy_static::lazy_static;
10
11lazy_static! {
12  static ref COMMAND_STATE: Arc<Mutex<HashMap<&'static str, Vec<u8>>>> =
13    Arc::new(Mutex::new(HashMap::new()));
14}
15
16pub trait Command: Send {
17  fn run(
18    &self,
19    input: &str,
20  ) -> Result<String, Box<dyn std::error::Error + Send>>;
21}
22
23fn is_path(s: &str, used_args: Vec<&str>) -> bool {
24  if (used_args.contains(&s)) {
25    return false;
26  }
27
28  let path = std::path::Path::new(s);
29  return path.components().count() > 0;
30}
31
32pub struct cd;
33impl Command for cd {
34  fn run(
35    &self,
36    input: &str,
37  ) -> Result<String, Box<dyn std::error::Error + Send>> {
38    let args = input.split(" ").collect::<Vec<&str>>();
39
40    let deafult_path = &"";
41    let mut path = args.first().unwrap_or(deafult_path).to_string();
42
43    let _ = COMMAND_STATE.lock().map(|mut lock| {
44      if (path == "-") {
45        lock.get("cd").map(|bytes| {
46          String::from_utf8(bytes.clone()).map(|prev_path| {
47            path = prev_path;
48          })
49        });
50      }
51
52      let path_buf = std::env::current_dir().unwrap_or_default();
53      let temp_pwd = path_buf.to_str().unwrap_or_default();
54      lock.insert("cd", temp_pwd.into());
55    });
56
57    std::env::set_current_dir(path).unwrap_or_default();
58
59    return Ok("".into());
60  }
61}
62
63pub struct ls;
64impl Command for ls {
65  fn run(
66    &self,
67    input: &str,
68  ) -> Result<String, Box<dyn std::error::Error + Send>> {
69    let args = input.split(" ").collect::<Vec<&str>>();
70
71    let mut list_all = false;
72    if (args.contains(&"-la")) {
73      list_all = true;
74    }
75
76    let used_args = vec!["-la"];
77
78    let mut path = ".";
79    if let Some(x) = args.last() {
80      if is_path(x, used_args) {
81        path = x;
82      }
83    }
84
85    let mut items = std::fs::read_dir(path)
86      .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?
87      .map(|res| res.map(|e| e.path()))
88      .collect::<Result<Vec<_>, std::io::Error>>()
89      .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
90
91    items.sort_by_key(|dir| !dir.is_dir());
92
93    let mut results = String::new();
94
95    if (list_all) {
96      for i in 0..items.len() {
97        let metadata = std::fs::metadata(path)
98          .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
99        let file_type = metadata.file_type();
100        let permissions = metadata.permissions();
101
102        let file_type_str = if file_type.is_dir() { "d" } else { "-" };
103        let readonly_str = if permissions.readonly() { "r-" } else { "rw" };
104
105        results.push_str(&format!(
106          "{}{}x  {}  {}",
107          file_type_str,
108          readonly_str,
109          metadata.len(),
110          items.get(i).unwrap().display()
111        ));
112
113        if (i < items.len() - 1) {
114          results.push('\n');
115        }
116      }
117    } else {
118      for path in items {
119        results.push_str(&format!("{} ", path.display()));
120      }
121    }
122
123    return Ok(results);
124  }
125}
126
127pub struct pwd;
128impl Command for pwd {
129  fn run(
130    &self,
131    input: &str,
132  ) -> Result<String, Box<dyn std::error::Error + Send>> {
133    let args = input.split(" ").collect::<Vec<&str>>();
134
135    let path_buf = std::env::current_dir().unwrap_or_default();
136    return Ok(path_buf.to_str().unwrap_or_default().into());
137  }
138}
139
140pub struct touch;
141impl Command for touch {
142  fn run(
143    &self,
144    input: &str,
145  ) -> Result<String, Box<dyn std::error::Error + Send>> {
146    let path = std::path::Path::new(input);
147    match std::fs::OpenOptions::new()
148      .create(true)
149      .truncate(false)
150      .write(true)
151      .open(path)
152    {
153      Ok(file) => {
154        let now = std::time::SystemTime::now();
155        file
156          .set_modified(now)
157          .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
158      }
159      Err(e) => return Err(Box::new(e)),
160    }
161
162    return Ok("".into());
163  }
164}
165
166pub struct mkdir;
167impl Command for mkdir {
168  fn run(
169    &self,
170    input: &str,
171  ) -> Result<String, Box<dyn std::error::Error + Send>> {
172    let path = std::path::Path::new(input);
173    std::fs::create_dir_all(path)
174      .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
175    Ok("".into())
176  }
177}
178
179pub struct cat;
180impl Command for cat {
181  fn run(
182    &self,
183    input: &str,
184  ) -> Result<String, Box<dyn std::error::Error + Send>> {
185    let contents = std::fs::read_to_string(input)
186      .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
187    Ok(contents)
188  }
189}
190
191pub struct echo;
192impl echo {
193  fn interpret_escapes(s: &str) -> String {
194    let mut output = String::new();
195    let mut chars = s.chars().peekable();
196    while let Some(c) = chars.next() {
197      if c == '\\' {
198        match chars.next() {
199          Some('a') => output.push('\x07'),
200          Some('b') => output.push('\x08'),
201          Some('c') => break,
202          Some('f') => output.push('\x0C'),
203          Some('n') => output.push('\n'),
204          Some('r') => output.push('\r'),
205          Some('t') => output.push('\t'),
206          Some('v') => output.push('\x0B'),
207          Some('\\') => output.push('\\'),
208          Some('0') => {
209            let mut octal = String::new();
210            for _ in 0..3 {
211              if let Some(octal_digit) = chars.peek() {
212                if octal_digit.is_digit(8) {
213                  octal.push(chars.next().unwrap());
214                } else {
215                  break;
216                }
217              }
218            }
219            if let Ok(value) = u8::from_str_radix(&octal, 8) {
220              output.push(value as char);
221            }
222          }
223          _ => (),
224        }
225      } else {
226        output.push(c);
227      }
228    }
229    output
230  }
231}
232impl Command for echo {
233  fn run(
234    &self,
235    input: &str,
236  ) -> std::result::Result<String, Box<dyn std::error::Error + Send>> {
237    let mut args =
238      shlex::split(input).unwrap_or_else(|| vec![input.to_string()]);
239    let mut add_newline = false;
240    if let Some(first_arg) = args.first() {
241      if first_arg == "-n" {
242        add_newline = true;
243        args.remove(0);
244      }
245    }
246    let output = args
247      .iter()
248      .map(|arg| echo::interpret_escapes(arg))
249      .collect::<Vec<String>>()
250      .join(" ");
251    if add_newline {
252      Ok(format!("{output}\n"))
253    } else {
254      Ok(output)
255    }
256  }
257}
258
259pub struct mv;
260impl Command for mv {
261  fn run(
262    &self,
263    input: &str,
264  ) -> std::result::Result<String, Box<dyn std::error::Error + Send>> {
265    let args = shlex::split(input).unwrap_or_else(|| vec![input.to_string()]);
266    if args.len() != 2 {
267      return Err(Box::new(std::io::Error::new(
268        std::io::ErrorKind::InvalidInput,
269        "mv command requires exactly 2 arguments",
270      )));
271    }
272    let (src, dest) = (&args[0], &args[1]);
273    match std::fs::rename(src, dest) {
274      Ok(_) => Ok(String::new()),
275      Err(e) => Err(Box::new(e) as Box<dyn std::error::Error + Send>),
276    }
277  }
278}
279
280pub struct cp;
281impl Command for cp {
282  fn run(
283    &self,
284    input: &str,
285  ) -> std::result::Result<String, Box<dyn std::error::Error + Send>> {
286    let args = shlex::split(input).unwrap_or_else(|| vec![input.to_string()]);
287    let mut preserve = false;
288    let args: Vec<String> = args
289      .into_iter()
290      .filter(|arg| {
291        if arg == "-p" {
292          preserve = true;
293          false
294        } else {
295          true
296        }
297      })
298      .collect();
299    if args.len() != 2 {
300      return Err(Box::new(std::io::Error::new(
301        std::io::ErrorKind::InvalidInput,
302        "cp command requires exactly 2 arguments",
303      )));
304    }
305    let (src, dest) = (&args[0], &args[1]);
306    match std::fs::metadata(src) {
307      Ok(metadata) => {
308        if metadata.is_dir() {
309          let entries = match std::fs::read_dir(src) {
310            Ok(entries) => entries,
311            Err(e) => {
312              return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
313            }
314          };
315          let dest_path = std::path::Path::new(dest);
316          match std::fs::create_dir_all(dest_path) {
317            Ok(_) => (),
318            Err(e) => {
319              return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
320            }
321          };
322          for entry in entries {
323            let entry = match entry {
324              Ok(entry) => entry,
325              Err(e) => {
326                return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
327              }
328            };
329            let entry_path = entry.path();
330            let dest_child_path = match entry_path.file_name() {
331              Some(name) => dest_path.join(name),
332              None => {
333                return Err(Box::new(std::io::Error::new(
334                  std::io::ErrorKind::Other,
335                  "Invalid file name",
336                ))
337                  as Box<dyn std::error::Error + Send>)
338              }
339            };
340            match std::fs::copy(&entry_path, &dest_child_path) {
341              Ok(_) => (),
342              Err(e) => {
343                return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
344              }
345            };
346            if preserve {
347              let metadata = match std::fs::metadata(&entry_path) {
348                Ok(metadata) => metadata,
349                Err(e) => {
350                  return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
351                }
352              };
353              let permissions = metadata.permissions();
354              match std::fs::set_permissions(&dest_child_path, permissions) {
355                Ok(_) => (),
356                Err(e) => {
357                  return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
358                }
359              };
360            }
361          }
362        } else {
363          match std::fs::copy(src, dest) {
364            Ok(_) => (),
365            Err(e) => {
366              return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
367            }
368          };
369          if preserve {
370            let metadata = match std::fs::metadata(src) {
371              Ok(metadata) => metadata,
372              Err(e) => {
373                return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
374              }
375            };
376            let permissions = metadata.permissions();
377            match std::fs::set_permissions(dest, permissions) {
378              Ok(_) => (),
379              Err(e) => {
380                return Err(Box::new(e) as Box<dyn std::error::Error + Send>)
381              }
382            };
383          }
384        }
385        Ok(String::new())
386      }
387      Err(e) => Err(Box::new(e) as Box<dyn std::error::Error + Send>),
388    }
389  }
390}
391
392pub struct rm;
393impl Command for rm {
394  fn run(
395    &self,
396    input: &str,
397  ) -> std::result::Result<String, Box<dyn std::error::Error + Send>> {
398    let args = shlex::split(input).unwrap_or_else(|| vec![input.to_string()]);
399    if args.len() != 1 {
400      return Err(Box::new(std::io::Error::new(
401        std::io::ErrorKind::InvalidInput,
402        "rm command requires exactly 1 argument",
403      )));
404    }
405    let path = &args[0];
406    let metadata = match std::fs::metadata(path) {
407      Ok(metadata) => metadata,
408      Err(e) => return Err(Box::new(e) as Box<dyn std::error::Error + Send>),
409    };
410    if metadata.is_dir() {
411      match std::fs::remove_dir_all(path) {
412        Ok(_) => (),
413        Err(e) => return Err(Box::new(e) as Box<dyn std::error::Error + Send>),
414      };
415    } else {
416      match std::fs::remove_file(path) {
417        Ok(_) => (),
418        Err(e) => return Err(Box::new(e) as Box<dyn std::error::Error + Send>),
419      };
420    }
421    Ok(String::new())
422  }
423}
424
425pub struct clear;
426impl Command for clear {
427  fn run(
428    &self,
429    _input: &str,
430  ) -> std::result::Result<String, Box<dyn std::error::Error + Send>> {
431    print!("\x1Bc");
432    std::io::Write::flush(&mut std::io::stdout())
433      .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
434    return Ok("".into());
435  }
436}
437
438pub struct grep;
439impl Command for grep {
440  fn run(
441    &self,
442    input: &str,
443  ) -> std::result::Result<String, Box<dyn std::error::Error + Send>> {
444    let args = shlex::split(input).unwrap_or_else(|| vec![input.to_string()]);
445    if args.len() != 2 {
446      return Err(Box::new(std::io::Error::new(
447        std::io::ErrorKind::InvalidInput,
448        "grep command requires exactly 2 arguments",
449      )));
450    }
451    let (pattern, text) = (&args[0], &args[1]);
452    let mut results = String::new();
453    for line in text.lines() {
454      if line.contains(pattern) {
455        results.push_str(line);
456        results.push('\n');
457      }
458    }
459    Ok(results)
460  }
461}
462
463type CommandResult =
464  Result<HashMap<String, Box<dyn Command>>, Box<dyn std::error::Error + Send>>;
465type CommandMap = Arc<Mutex<CommandResult>>;
466
467lazy_static! {
468  pub static ref DEFAULT_COMMANDS: CommandMap = Arc::new(Mutex::new({
469    let mut result: HashMap<String, Box<dyn Command>> = Default::default();
470
471    result.insert("cd".to_owned(), Box::new(cd));
472    result.insert("ls".to_owned(), Box::new(ls));
473    result.insert("pwd".to_owned(), Box::new(pwd));
474    result.insert("touch".to_owned(), Box::new(touch));
475    result.insert("mkdir".to_owned(), Box::new(mkdir));
476    result.insert("cat".to_owned(), Box::new(cat));
477    result.insert("echo".to_owned(), Box::new(echo));
478    result.insert("mv".to_owned(), Box::new(mv));
479    result.insert("cp".to_owned(), Box::new(cp));
480    result.insert("rm".to_owned(), Box::new(rm));
481    result.insert("clear".to_owned(), Box::new(clear));
482    result.insert("grep".to_owned(), Box::new(grep));
483
484    Ok(result)
485  }));
486}