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}