xref: /NovaShell/src/shell/command/mod.rs (revision 6e297d90f5f41276888e7d969f995c9e8466afd8)
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::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         println!();
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 mut target_path = args.get(2).unwrap().clone();
298                 match self.is_file(&target_path) {
299                     Ok(str) => target_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 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 {
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 {
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\t\tused\t\tfree\t\tshared\t\tcache_used\tcache_free\tavailable\n");
576         print!("Mem:\t");
577 
578         if args.len() == 0 {
579             print!(
580                 "{}\t\t{}\t\t{}\t\t{}\t\t{}\t\t{}\t\t{}\n",
581                 mst.total,
582                 mst.used,
583                 mst.free,
584                 mst.shared,
585                 mst.cache_used,
586                 mst.cache_free,
587                 mst.available
588             );
589         } else {
590             print!(
591                 "{}\t\t{}\t\t{}\t\t{}\t\t{}\t\t{}\n",
592                 mst.total >> 10,
593                 mst.used >> 10,
594                 mst.free >> 10,
595                 mst.shared >> 10,
596                 mst.cache_used >> 10,
597                 mst.available >> 10
598             );
599         }
600         Ok(())
601     }
602 
603     fn shell_cmd_kill(&self, args: &Vec<String>) -> Result<(), CommandError> {
604         if args.len() == 1 {
605             let pid: i32;
606             match args.get(0).unwrap().parse::<i32>() {
607                 Ok(x) => pid = x,
608                 Err(_) => {
609                     return Err(CommandError::InvalidArgument(
610                         args.get(0).unwrap().to_string(),
611                     ))
612                 }
613             }
614             unsafe {
615                 libc::kill(pid, libc::SIGKILL);
616             }
617             return Ok(());
618         } else {
619             return Err(CommandError::WrongArgumentCount(args.len()));
620         }
621     }
622 
623     fn shell_cmd_help(&self, args: &Vec<String>) -> Result<(), CommandError> {
624         if args.len() == 0 {
625             for BuildInCmd(cmd) in BuildInCmd::BUILD_IN_CMD {
626                 Help::shell_help(cmd)
627             }
628             return Ok(());
629         }
630         return Err(CommandError::WrongArgumentCount(args.len()));
631     }
632 
633     fn shell_cmd_export(&self, args: &Vec<String>) -> Result<(), CommandError> {
634         if args.len() == 1 {
635             let pair = args.get(0).unwrap().split('=').collect::<Vec<&str>>();
636 
637             if pair.len() == 2 && !pair.contains(&"") {
638                 let name = pair.get(0).unwrap().to_string();
639                 let value = pair.get(1).unwrap().to_string();
640                 Env::insert(name, value);
641                 return Ok(());
642             } else {
643                 return Err(CommandError::InvalidArgument(args.get(0).unwrap().clone()));
644             }
645         }
646         return Err(CommandError::WrongArgumentCount(args.len()));
647     }
648 
649     fn shell_cmd_env(&self, args: &Vec<String>) -> Result<(), CommandError> {
650         if args.len() == 0 {
651             let mut file = File::open("/etc/profile").unwrap();
652             let mut buf: Vec<u8> = Vec::new();
653             file.read_to_end(&mut buf).unwrap();
654             println!("{}", String::from_utf8(buf).unwrap());
655             return Ok(());
656         } else {
657             return Err(CommandError::InvalidArgument(args.get(0).unwrap().clone()));
658         }
659     }
660 
661     fn shell_cmd_compgen(&self, _args: &Vec<String>) -> Result<(), CommandError> {
662         Ok(())
663     }
664 
665     fn shell_cmd_complete(&self, _args: &Vec<String>) -> Result<(), CommandError> {
666         Ok(())
667     }
668 
669     fn path_format(&self, path: &String) -> Result<String, CommandError> {
670         let mut abs_path = path.clone();
671         if !path.starts_with('/') {
672             abs_path = format!("{}/{}", self.current_dir(), path);
673         }
674         if let Ok(path) = Path::new(&abs_path).canonicalize() {
675             let mut fmt_path = path.to_str().unwrap().to_string();
676             let replacement = |_caps: &regex::Captures| -> String { String::from("/") };
677             let re = regex::Regex::new(r"\/{2,}").unwrap();
678             fmt_path = re.replace_all(fmt_path.as_str(), replacement).to_string();
679             return Ok(fmt_path);
680         } else {
681             return Err(CommandError::PathNotFound(path.clone()));
682         }
683     }
684 
685     fn is_file(&self, path_str: &String) -> Result<String, CommandError> {
686         match self.path_format(path_str) {
687             Ok(path_str) => {
688                 let path = Path::new(&path_str);
689                 if !path.is_file() {
690                     return Err(CommandError::NotFile(path_str.clone()));
691                 };
692                 Ok(path_str)
693             }
694             Err(_) => Err(CommandError::FileNotFound(path_str.clone())),
695         }
696     }
697 
698     fn is_dir(&self, path_str: &String) -> Result<String, CommandError> {
699         match self.path_format(path_str) {
700             Ok(path_str) => {
701                 let path = Path::new(&path_str);
702                 if !path.is_dir() {
703                     return Err(CommandError::NotDirectory(path_str.clone()));
704                 };
705                 Ok(path_str)
706             }
707             Err(_) => Err(CommandError::DirectoryNotFound(path_str.clone())),
708         }
709     }
710 
711     fn is_file_or_dir(&self, path_str: &String) -> Result<String, CommandError> {
712         match self.path_format(path_str) {
713             Ok(path_str) => Ok(path_str),
714             Err(_) => Err(CommandError::PathNotFound(path_str.clone())),
715         }
716     }
717 }
718