xref: /NovaShell/src/shell/command/mod.rs (revision e0958189d78d37dd987b20403a92be387d7a741d)
1 use help::Help;
2 use regex::{Captures, Regex};
3 use std::io::Read;
4 use std::os::unix::ffi::OsStrExt;
5 use std::{
6     format,
7     fs::{self, File, OpenOptions},
8     io::Write,
9     path::Path,
10     print, println,
11     string::String,
12     vec::Vec,
13 };
14 
15 use crate::shell::Shell;
16 use crate::Env;
17 use crate::{special_keycode::*, ROOT_PATH};
18 
19 mod help;
20 
21 enum CommandType {
22     InternalCommand(BuildInCmd),
23     ExternalCommand(String),
24 }
25 
26 pub struct Command {
27     args: Vec<String>,
28     cmd_type: CommandType,
29 }
30 
31 #[derive(Debug, PartialEq, Eq, Clone)]
32 pub enum CommandError {
33     CommandNotFound(String),
34     InvalidArgument(String),
35     WrongArgumentCount(usize),
36     EnvironmentVariableNotFound(String),
37     PathNotFound(String),
38     FileNotFound(String),
39     DirectoryNotFound(String),
40     NotDirectory(String),
41     NotFile(String),
42 }
43 
44 impl CommandError {
45     pub fn handle(e: CommandError) {
46         match e {
47             CommandError::CommandNotFound(command) => {
48                 println!("cannot find command: {}", command)
49             }
50             CommandError::InvalidArgument(argument) => {
51                 println!("invalid argument: {}", argument)
52             }
53             CommandError::WrongArgumentCount(count) => {
54                 println!("argument count incorrect: {}", count)
55             }
56             CommandError::EnvironmentVariableNotFound(env) => {
57                 println!("environment variable not found: {}", env);
58             }
59             CommandError::PathNotFound(path) => {
60                 println!("cannot found file or dirctory: {}", path)
61             }
62             CommandError::FileNotFound(path) => {
63                 println!("cannot found file: {}", path)
64             }
65             CommandError::DirectoryNotFound(path) => {
66                 println!("cannot found dirctory: {}", path)
67             }
68             CommandError::NotDirectory(path) => {
69                 println!("path is not a dirctory: {}", path)
70             }
71             CommandError::NotFile(path) => {
72                 println!("path is not a file: {}", path)
73             }
74         };
75     }
76 }
77 
78 impl Command {
79     fn new(name: String, args: Vec<String>) -> Result<Command, CommandError> {
80         for BuildInCmd(cmd) in BuildInCmd::BUILD_IN_CMD {
81             if name == *cmd {
82                 return Ok(Command {
83                     args,
84                     cmd_type: CommandType::InternalCommand(BuildInCmd(cmd)),
85                 });
86             }
87         }
88 
89         return Ok(Command {
90             args,
91             cmd_type: CommandType::ExternalCommand(name),
92         });
93     }
94 
95     fn from_string(str: String) -> Result<Command, CommandError> {
96         let regex: Regex = Regex::new(r#"([^\s'"]|("[^"]*"|'[^']*'))+"#).unwrap();
97         let hay = str.clone();
98         let mut iter = regex
99             .captures_iter(hay.as_str())
100             .map(|c| String::from(c.get(0).unwrap().as_str()));
101         // let mut iter = str.split_ascii_whitespace();
102         let name = iter.next().unwrap();
103         let re: Regex = Regex::new(r"\$[\w_]+").unwrap();
104         let replacement = |caps: &Captures| -> String {
105             match Env::get(&String::from(&caps[0][1..])) {
106                 Some(value) => value,
107                 None => String::from(&caps[0]),
108             }
109         };
110         let mut args: Vec<String> = Vec::new();
111         for arg in iter.collect::<Vec<String>>().iter() {
112             let arg = re.replace_all(arg.as_str(), &replacement).to_string();
113             match re.captures(arg.as_str()) {
114                 Some(caps) => {
115                     return Err(CommandError::EnvironmentVariableNotFound(String::from(
116                         caps.get(0).unwrap().as_str(),
117                     )))
118                 }
119                 None => args.push(arg),
120             }
121         }
122         Command::new(name, args)
123     }
124 
125     pub fn from_strings(str: String) -> Vec<Command> {
126         str.split(';')
127             .filter_map(|s| match Command::from_string(String::from(s)) {
128                 Ok(s) => Some(s),
129                 Err(e) => {
130                     CommandError::handle(e);
131                     None
132                 }
133             })
134             .collect::<Vec<Command>>()
135     }
136 }
137 
138 pub struct BuildInCmd(pub &'static str);
139 
140 impl BuildInCmd {
141     pub const BUILD_IN_CMD: &[BuildInCmd] = &[
142         BuildInCmd("cd"),
143         BuildInCmd("ls"),
144         BuildInCmd("cat"),
145         BuildInCmd("touch"),
146         BuildInCmd("mkdir"),
147         BuildInCmd("rm"),
148         BuildInCmd("rmdir"),
149         BuildInCmd("pwd"),
150         BuildInCmd("cp"),
151         BuildInCmd("exec"),
152         BuildInCmd("echo"),
153         BuildInCmd("reboot"),
154         BuildInCmd("free"),
155         BuildInCmd("kill"),
156         BuildInCmd("help"),
157         BuildInCmd("export"),
158         BuildInCmd("env"),
159         BuildInCmd("compgen"),
160         BuildInCmd("complete"),
161     ];
162 }
163 
164 impl Shell {
165     pub fn exec_internal_command(
166         &mut self,
167         cmd: &str,
168         args: &Vec<String>,
169     ) -> Result<(), CommandError> {
170         match cmd {
171             "cd" => self.shell_cmd_cd(args),
172             "ls" => self.shell_cmd_ls(args),
173             "cat" => self.shell_cmd_cat(args),
174             "touch" => self.shell_cmd_touch(args),
175             "mkdir" => self.shell_cmd_mkdir(args),
176             "rm" => self.shell_cmd_rm(args),
177             "rmdir" => self.shell_cmd_rmdir(args),
178             "pwd" => self.shell_cmd_pwd(args),
179             "cp" => self.shell_cmd_cp(args),
180             "exec" => self.shell_cmd_exec(args),
181             "echo" => self.shell_cmd_echo(args),
182             "reboot" => self.shell_cmd_reboot(args),
183             "free" => self.shell_cmd_free(args),
184             "kill" => self.shell_cmd_kill(args),
185             "help" => self.shell_cmd_help(args),
186             "export" => self.shell_cmd_export(args),
187             "env" => self.shell_cmd_env(args),
188             "compgen" => self.shell_cmd_compgen(args),
189             "complete" => self.shell_cmd_complete(args),
190 
191             _ => Err(CommandError::CommandNotFound(String::from(cmd))),
192         }
193     }
194 
195     pub fn exec_external_command(&mut self, path: String, args: &Vec<String>) {
196         let mut full_args = args.clone();
197         full_args.insert(0, path.clone());
198         self.shell_cmd_exec(&full_args).unwrap_or_else(|e| {
199             let err = match e {
200                 CommandError::FileNotFound(_) => CommandError::CommandNotFound(path.clone()),
201                 _ => e,
202             };
203             CommandError::handle(err);
204         })
205     }
206 
207     pub fn exec_command(&mut self, command: &Command) {
208         match &command.cmd_type {
209             CommandType::ExternalCommand(path) => {
210                 self.exec_external_command(path.to_string(), &command.args);
211             }
212 
213             CommandType::InternalCommand(BuildInCmd(cmd)) => {
214                 match self.exec_internal_command(cmd, &command.args) {
215                     Ok(_) => {}
216                     Err(e) => CommandError::handle(e),
217                 }
218                 if command.args.contains(&String::from("--help")) {
219                     Help::shell_help(cmd);
220                 }
221             }
222         }
223     }
224 
225     fn shell_cmd_cd(&mut self, args: &Vec<String>) -> Result<(), CommandError> {
226         if args.len() == 0 {
227             self.set_current_dir(&String::from(ROOT_PATH));
228             return Ok(());
229         }
230         if args.len() == 1 {
231             let mut path = args.get(0).unwrap().clone();
232             match self.is_dir(&path) {
233                 Ok(str) => path = str,
234                 Err(e) => return Err(e),
235             }
236             self.set_current_dir(&path);
237             return Ok(());
238         }
239         return Err(CommandError::WrongArgumentCount(args.len()));
240     }
241 
242     fn shell_cmd_ls(&self, args: &Vec<String>) -> Result<(), CommandError> {
243         let mut path = String::new();
244         if args.len() == 0 {
245             path = self.current_dir();
246         }
247         if args.len() == 1 {
248             path = args.get(0).unwrap().clone();
249             match self.is_dir(&path) {
250                 Ok(str) => path = str,
251                 Err(e) => return Err(e),
252             }
253         }
254 
255         if path.is_empty() {
256             return Err(CommandError::WrongArgumentCount(args.len()));
257         }
258 
259         let dir: fs::ReadDir;
260         match fs::read_dir(Path::new(&path)) {
261             Ok(readdir) => dir = readdir,
262             Err(_) => return Err(CommandError::InvalidArgument(path)),
263         }
264         for entry in dir {
265             let entry = entry.unwrap();
266             if entry.file_type().unwrap().is_dir() {
267                 crate::shell::Printer::print_color(
268                     entry.file_name().as_bytes(),
269                     0x000088ff,
270                     0x00000000,
271                 );
272                 print!("    ");
273             } else {
274                 print!("{}    ", entry.file_name().into_string().unwrap());
275             }
276         }
277         print!("{}", String::from_utf8(vec![CR, LF]).unwrap());
278         return Ok(());
279     }
280 
281     fn shell_cmd_cat(&self, args: &Vec<String>) -> Result<(), CommandError> {
282         if args.len() > 0 {
283             let mut path = args.get(0).unwrap().clone();
284             let mut buf: Vec<u8> = Vec::new();
285 
286             match self.is_file(&path) {
287                 Ok(str) => path = str,
288                 Err(e) => return Err(e),
289             }
290 
291             File::open(path).unwrap().read_to_end(&mut buf).unwrap();
292             if args.len() == 1 {
293                 println!("{}", String::from_utf8(buf.clone()).unwrap());
294             }
295 
296             if args.len() == 3 {
297                 let target_path = args.get(2).unwrap().clone();
298                 match self.is_file(&target_path) {
299                     Ok(str) => path = str,
300                     Err(e) => return Err(e),
301                 }
302 
303                 if args[1] == ">" {
304                     match OpenOptions::new().write(true).open(target_path) {
305                         Ok(mut file) => {
306                             file.write_all(&buf).unwrap();
307                         }
308                         Err(e) => print!("{e}"),
309                     }
310                 } else if args[1] == ">>" {
311                     match OpenOptions::new().append(true).open(target_path) {
312                         Ok(mut file) => {
313                             file.write_all(&buf).unwrap();
314                         }
315                         Err(e) => print!("{e}"),
316                     }
317                 }
318             }
319             return Ok(());
320         }
321         return Err(CommandError::WrongArgumentCount(args.len()));
322     }
323 
324     fn shell_cmd_touch(&self, args: &Vec<String>) -> Result<(), CommandError> {
325         if args.len() == 1 {
326             let mut path = args.get(0).unwrap().clone();
327             match self.is_file(&path) {
328                 Ok(str) => path = str,
329                 Err(e) => return Err(e),
330             }
331             File::open(path).unwrap();
332             return Ok(());
333         }
334         return Err(CommandError::WrongArgumentCount(args.len()));
335     }
336 
337     fn shell_cmd_mkdir(&self, args: &Vec<String>) -> Result<(), CommandError> {
338         if args.len() == 1 {
339             let path = args.get(0).unwrap();
340             match fs::create_dir_all(path) {
341                 Ok(_) => {}
342                 Err(e) => {
343                     print!("{e}")
344                 }
345             }
346             return Ok(());
347         } else {
348             return Err(CommandError::WrongArgumentCount(args.len()));
349         }
350     }
351 
352     fn shell_cmd_rm(&self, args: &Vec<String>) -> Result<(), CommandError> {
353         if args.len() == 1 {
354             let mut path = args.get(0).unwrap().clone();
355             // match fs::remove_file(path) {
356             //     Ok(_) => {}
357             //     Err(e) => {
358             //         print!("{e}")
359             //     }
360             // }
361 
362             match self.is_file(&path) {
363                 Ok(str) => path = str,
364                 Err(e) => return Err(e),
365             }
366 
367             let path_cstr = std::ffi::CString::new(path.clone()).unwrap();
368             unsafe {
369                 libc::syscall(libc::SYS_unlinkat, 0, path_cstr.as_ptr(), 0, 0, 0, 0);
370             }
371             return Ok(());
372         }
373         return Err(CommandError::WrongArgumentCount(args.len()));
374     }
375 
376     fn shell_cmd_rmdir(&self, args: &Vec<String>) -> Result<(), CommandError> {
377         if args.len() == 1 {
378             let mut path = args.get(0).unwrap().clone();
379             match self.is_dir(&path) {
380                 Ok(str) => path = str,
381                 Err(e) => return Err(e),
382             }
383 
384             let path_cstr = std::ffi::CString::new(path).unwrap();
385             unsafe { libc::unlinkat(0, path_cstr.as_ptr(), libc::AT_REMOVEDIR) };
386             return Ok(());
387         }
388         return Err(CommandError::WrongArgumentCount(args.len()));
389     }
390 
391     fn shell_cmd_pwd(&self, args: &Vec<String>) -> Result<(), CommandError> {
392         if args.len() == 0 {
393             println!("{}", self.current_dir());
394             return Ok(());
395         }
396         return Err(CommandError::WrongArgumentCount(args.len()));
397     }
398 
399     fn shell_cmd_cp(&self, args: &Vec<String>) -> Result<(), CommandError> {
400         if args.len() == 2 {
401             let mut src_path = args.get(0).unwrap().clone();
402             let mut target_path = args.get(1).unwrap().clone();
403 
404             match self.is_file(&src_path) {
405                 Ok(str) => src_path = str,
406                 Err(e) => return Err(e),
407             }
408 
409             match self.is_file_or_dir(&target_path) {
410                 Ok(str) => target_path = str,
411                 Err(e) => {
412                     let prefix = &target_path[..target_path.rfind('/').unwrap_or(0)];
413                     if !Path::new(prefix).is_dir() {
414                         return Err(e);
415                     }
416                 }
417             }
418 
419             if Path::new(&src_path).is_dir() {
420                 let mut name = &src_path[src_path.rfind('/').unwrap_or(0)..];
421                 target_path = format!("{}/{}", target_path, name);
422             }
423 
424             let mut src_file = File::open(&src_path).unwrap();
425             let mut target_file = File::create(target_path).unwrap();
426             let mut buf: Vec<u8> = Vec::new();
427             src_file.read_to_end(&mut buf).unwrap();
428             target_file.write_all(&buf).unwrap();
429             return Ok(());
430         }
431         return Err(CommandError::WrongArgumentCount(args.len()));
432     }
433 
434     pub fn shell_cmd_exec(&self, args: &Vec<String>) -> Result<(), CommandError> {
435         if args.len() <= 0 {
436             return Err(CommandError::WrongArgumentCount(args.len()));
437         }
438         let path = args.get(0).unwrap();
439         let mut real_path = String::new();
440         if !path.starts_with('/') && !path.starts_with('.') {
441             let mut prefix_collection = Env::path();
442             prefix_collection.insert(0, self.current_dir());
443             for prefix in prefix_collection {
444                 real_path = format!("{}/{}", prefix, path);
445                 if Path::new(&real_path).is_file() {
446                     break;
447                 }
448             }
449         }
450 
451         match self.is_file(&real_path) {
452             Ok(str) => real_path = str,
453             Err(e) => return Err(e),
454         }
455 
456         let pid: libc::pid_t = unsafe {
457             libc::syscall(libc::SYS_fork, 0, 0, 0, 0, 0, 0)
458                 .try_into()
459                 .unwrap()
460         };
461 
462         let mut retval = 0;
463         if pid == 0 {
464             let path_cstr = std::ffi::CString::new(real_path).unwrap();
465             let mut argv: *const *const i8 = std::ptr::null();
466             if args.len() > 1 {
467                 let args_cstr = args
468                     .iter()
469                     .skip(1)
470                     .map(|str| std::ffi::CString::new(str.as_str()).unwrap())
471                     .collect::<Vec<std::ffi::CString>>();
472                 let mut args_ptr = args_cstr
473                     .iter()
474                     .map(|c_str| c_str.as_ptr())
475                     .collect::<Vec<*const i8>>();
476                 args_ptr.push(std::ptr::null());
477                 argv = args_ptr.as_ptr();
478             }
479             unsafe {
480                 libc::execv(path_cstr.as_ptr(), argv);
481             }
482         } else {
483             if args.last().unwrap() != &"&" {
484                 unsafe { libc::waitpid(pid, &mut retval as *mut i32, 0) };
485             } else {
486                 println!("[1] {}", pid);
487             }
488         }
489         return Ok(());
490     }
491 
492     fn shell_cmd_echo(&self, args: &Vec<String>) -> Result<(), CommandError> {
493         if args.len() > 0 {
494             let str = args.get(0).unwrap();
495             if args.len() == 1 {
496                 println!("{str}");
497             }
498 
499             if args.len() == 3 {
500                 let mut target_path = args.get(2).unwrap().clone();
501                 match self.is_file(&target_path) {
502                     Ok(str) => target_path = str,
503                     Err(e) => return Err(e),
504                 }
505 
506                 if args[1] == ">" {
507                     match OpenOptions::new().write(true).open(target_path) {
508                         Ok(mut file) => {
509                             file.write_all(str.as_bytes()).unwrap();
510                         }
511                         Err(e) => print!("{e}"),
512                     }
513                 } else if args[1] == ">>" {
514                     match OpenOptions::new().append(true).open(target_path) {
515                         Ok(mut file) => {
516                             file.write_all(str.as_bytes()).unwrap();
517                         }
518                         Err(e) => print!("{e}"),
519                     }
520                 }
521             }
522             return Ok(());
523         }
524         return Err(CommandError::WrongArgumentCount(args.len()));
525     }
526 
527     fn shell_cmd_reboot(&self, args: &Vec<String>) -> Result<(), CommandError> {
528         if args.len() == 0 {
529             unsafe { libc::syscall(libc::SYS_reboot, 0, 0, 0, 0, 0, 0) };
530             return Ok(());
531         } else {
532             return Err(CommandError::WrongArgumentCount(args.len()));
533         }
534     }
535 
536     fn shell_cmd_free(&self, args: &Vec<String>) -> Result<(), CommandError> {
537         if args.len() == 1 && args.get(0).unwrap() != "-m" {
538             return Err(CommandError::InvalidArgument(
539                 args.get(0).unwrap().to_string(),
540             ));
541         }
542 
543         struct mstat_t {
544             total: u64,      // 计算机的总内存数量大小
545             used: u64,       // 已使用的内存大小
546             free: u64,       // 空闲物理页所占的内存大小
547             shared: u64,     // 共享的内存大小
548             cache_used: u64, // 位于slab缓冲区中的已使用的内存大小
549             cache_free: u64, // 位于slab缓冲区中的空闲的内存大小
550             available: u64,  // 系统总空闲内存大小(包括kmalloc缓冲区)
551         };
552 
553         let mut mst = mstat_t {
554             total: 0,
555             used: 0,
556             free: 0,
557             shared: 0,
558             cache_used: 0,
559             cache_free: 0,
560             available: 0,
561         };
562 
563         let mut info_file = File::open("/proc/meminfo").unwrap();
564         let mut buf: Vec<u8> = Vec::new();
565         info_file.read_to_end(&mut buf).unwrap();
566         let str = String::from_utf8(buf).unwrap();
567         let info = str
568             .split(&['\n', '\t', ' '])
569             .filter_map(|str| str.parse::<u64>().ok())
570             .collect::<Vec<u64>>();
571         mst.total = *info.get(0).unwrap();
572         mst.free = *info.get(1).unwrap();
573         mst.used = mst.total - mst.free;
574 
575         print!("\ttotal\tused\tfree\tshared\tcache\tavailable\n");
576         print!("Mem:\t");
577 
578         if args.len() == 0 {
579             print!(
580                 "{}\t{}\t{}\t{}\t{}\t{}\t\n",
581                 mst.total, mst.used, mst.free, mst.shared, mst.cache_used, mst.available
582             );
583         } else {
584             print!(
585                 "{}\t{}\t{}\t{}\t{}\t{}\t\n",
586                 mst.total >> 10,
587                 mst.used >> 10,
588                 mst.free >> 10,
589                 mst.shared >> 10,
590                 mst.cache_used >> 10,
591                 mst.available >> 10
592             );
593         }
594         Ok(())
595     }
596 
597     fn shell_cmd_kill(&self, args: &Vec<String>) -> Result<(), CommandError> {
598         if args.len() == 1 {
599             let pid: i32;
600             match args.get(0).unwrap().parse::<i32>() {
601                 Ok(x) => pid = x,
602                 Err(_) => {
603                     return Err(CommandError::InvalidArgument(
604                         args.get(0).unwrap().to_string(),
605                     ))
606                 }
607             }
608             unsafe {
609                 libc::kill(pid, libc::SIGKILL);
610             }
611             return Ok(());
612         } else {
613             return Err(CommandError::WrongArgumentCount(args.len()));
614         }
615     }
616 
617     fn shell_cmd_help(&self, args: &Vec<String>) -> Result<(), CommandError> {
618         if args.len() == 0 {
619             for BuildInCmd(cmd) in BuildInCmd::BUILD_IN_CMD {
620                 Help::shell_help(cmd)
621             }
622             return Ok(());
623         }
624         return Err(CommandError::WrongArgumentCount(args.len()));
625     }
626 
627     fn shell_cmd_export(&self, args: &Vec<String>) -> Result<(), CommandError> {
628         if args.len() == 1 {
629             let pair = args.get(0).unwrap().split('=').collect::<Vec<&str>>();
630 
631             if pair.len() == 2 && !pair.contains(&"") {
632                 let name = pair.get(0).unwrap().to_string();
633                 let value = pair.get(1).unwrap().to_string();
634                 Env::insert(name, value);
635                 return Ok(());
636             } else {
637                 return Err(CommandError::InvalidArgument(args.get(0).unwrap().clone()));
638             }
639         }
640         return Err(CommandError::WrongArgumentCount(args.len()));
641     }
642 
643     fn shell_cmd_env(&self, args: &Vec<String>) -> Result<(), CommandError> {
644         if args.len() == 0 {
645             let mut file = File::open("/etc/profile").unwrap();
646             let mut buf: Vec<u8> = Vec::new();
647             file.read_to_end(&mut buf).unwrap();
648             println!("{}", String::from_utf8(buf).unwrap());
649             return Ok(());
650         } else {
651             return Err(CommandError::InvalidArgument(args.get(0).unwrap().clone()));
652         }
653     }
654 
655     fn shell_cmd_compgen(&self, args: &Vec<String>) -> Result<(), CommandError> {
656         Ok(())
657     }
658 
659     fn shell_cmd_complete(&self, args: &Vec<String>) -> Result<(), CommandError> {
660         Ok(())
661     }
662 
663     fn path_format(&self, path: &String) -> Result<String, CommandError> {
664         let mut abs_path = path.clone();
665         if !path.starts_with('/') {
666             abs_path = format!("{}/{}", self.current_dir(), abs_path);
667         }
668         if let Ok(path) = Path::new(path).canonicalize() {
669             let mut fmt_path = path.to_str().unwrap().to_string();
670             let replacement = |_caps: &regex::Captures| -> String { String::from("/") };
671             let re = regex::Regex::new(r"\/{2,}").unwrap();
672             fmt_path = re.replace_all(fmt_path.as_str(), replacement).to_string();
673             return Ok(fmt_path);
674         } else {
675             return Err(CommandError::PathNotFound(path.clone()));
676         }
677     }
678 
679     fn is_file(&self, path_str: &String) -> Result<String, CommandError> {
680         match self.path_format(path_str) {
681             Ok(path_str) => {
682                 let path = Path::new(&path_str);
683                 if !path.is_file() {
684                     return Err(CommandError::NotFile(path_str.clone()));
685                 };
686                 Ok(path_str)
687             }
688             Err(_) => Err(CommandError::FileNotFound(path_str.clone())),
689         }
690     }
691 
692     fn is_dir(&self, path_str: &String) -> Result<String, CommandError> {
693         match self.path_format(path_str) {
694             Ok(path_str) => {
695                 let path = Path::new(&path_str);
696                 if !path.is_dir() {
697                     return Err(CommandError::NotDirectory(path_str.clone()));
698                 };
699                 Ok(path_str)
700             }
701             Err(_) => Err(CommandError::DirectoryNotFound(path_str.clone())),
702         }
703     }
704 
705     fn is_file_or_dir(&self, path_str: &String) -> Result<String, CommandError> {
706         match self.path_format(path_str) {
707             Ok(path_str) => Ok(path_str),
708             Err(_) => Err(CommandError::PathNotFound(path_str.clone())),
709         }
710     }
711 }
712