prettylogger/
logging.rs

1use crate::{
2    colors::*, fileio::append_to_file, filtering::*
3};
4use chrono::{Local, DateTime};
5use serde::{Serialize, Deserialize};
6use std::fmt::{Display, Formatter};
7
8#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default,
9    Serialize, Deserialize)]
10/// Defines the policy for handling log file flushing when the logger is
11/// dropped.
12///
13/// The available options are:
14/// - `IgnoreLogFileLock`: Completely ignores any log file lock and forces the
15/// log entries to be written to the file regardless of the lock status.
16/// - `DiscardLogBuffer`: If a log file lock is enabled, the log buffer is
17/// discarded instead of writing to the file. This prevents race conditions.
18///
19/// The default policy is `DiscardLogBuffer`.
20pub enum OnDropPolicy {
21    /// Completely ignore log file lock and write to file anyway.
22    IgnoreLogFileLock,
23    #[default]
24    /// Don't write to the log file when lock is enabled.
25    DiscardLogBuffer,
26}
27
28impl Display for OnDropPolicy {
29    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
30        let level_str = match *self {
31            OnDropPolicy::IgnoreLogFileLock => "IgnoreLogFileLock",
32            OnDropPolicy::DiscardLogBuffer => "DiscardLogBuffer",
33        };
34        write!(f, "{}", level_str)
35    }
36}
37
38
39#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default,
40    Serialize, Deserialize)]
41/// Represents the different types of log messages.
42///
43/// This enum is used to categorize the severity or type of a log message. 
44/// The variants correspond to different levels of logging, from debugging 
45/// information to fatal errors.
46/// 
47/// The variants are:
48/// * `Debug`: Represents debug-level log messages, typically used for
49/// detailed internal information during development.
50/// * `Info`: Represents informational log messages.
51/// * `Warning`: Represents warning messages.
52/// * `Err`: Represents error messages.
53/// * `FatalError`: Represents critical errors that usually lead to program
54/// termination or an unrecoverable state.
55///
56/// The default variant is `Info`.
57pub enum LogType {
58    Debug = 0,
59    #[default]
60    Info = 1,
61    Warning = 2,
62    Err = 3,
63    FatalError = 4,
64}
65
66impl TryFrom<i32> for LogType {
67    type Error = &'static str;
68    fn try_from(value: i32) -> Result<Self, Self::Error> {
69        match value {
70            0 => Ok(LogType::Debug),
71            1 => Ok(LogType::Info),
72            2 => Ok(LogType::Warning),
73            3 => Ok(LogType::Err),
74            4 => Ok(LogType::FatalError),
75            _ => Err("Invalid value! Please provide a value in range 0-9."),
76        }
77    }
78}
79
80impl Display for LogType {
81    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
82        let level_str = match *self {
83            LogType::Debug => "Debug",
84            LogType::Info => "Info",
85            LogType::Warning => "Warning",
86            LogType::Err => "Error",
87            LogType::FatalError => "FatalError",
88        };
89        write!(f, "{}", level_str)
90    }
91}
92
93impl AsRef<str> for LogType {
94    fn as_ref(&self) -> &str {
95        match self {
96            LogType::Debug => "Debug",
97            LogType::Info => "Info",
98            LogType::Warning => "Warning",
99            LogType::Err => "Err",
100            LogType::FatalError => "Fatal Error",
101        }
102    }
103}
104
105
106#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
107/// Represents a single log entry.
108///
109/// This struct is used to store information about a single log message.
110/// It includes the log's message, its type (e.g., 
111/// `Debug`, `Error`, etc.), and the date and time when the log was created.
112/// It can be used for storing logs in memory more efficiently.
113///
114/// Fields:
115/// - `message`: The actual log message as a string.
116/// - `log_type`: The type of the log (e.g., `Debug`, `Error`, `Info`, etc.).
117/// - `datetime`: The timestamp of when the log entry was created.
118pub struct LogStruct {
119    pub message: String,
120    pub log_type: LogType,
121    /// The date and time at which the log was created.
122    pub datetime: DateTime<Local>,
123}
124
125impl Display for LogStruct {
126    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127        write!(
128            f,
129            "Log: {}\nType: {:?}\nDateTime: {}",
130            self.message,
131            self.log_type,
132            self.datetime
133        )
134    }
135}
136
137
138/// A logger struct used for printing logs.
139///
140/// # You can create a `Logger` instance with the default configuration using:
141/// ```
142/// # use prettylogger::logging::Logger;
143/// let l = Logger::default();
144/// ```
145///
146/// # Alternatively, initialize a `Logger` instance from a JSON template file:
147/// ```ignore
148/// Logger::from_template("/path/to/file.json");
149/// ```
150///
151/// # Once you have a `Logger` instance, you can start printing logs:
152/// ```
153/// # use prettylogger::logging::Logger;
154/// # let mut l = Logger::default();
155/// l.debug("debug message");
156/// l.info("info message");
157/// l.warning("warning message");
158/// l.error("error message");
159/// l.fatal("fatal error message");
160/// ```
161#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default,
162    Serialize, Deserialize)]
163pub struct Logger {
164    pub(crate) verbosity: Verbosity,
165    pub(crate) filtering_enabled: bool,
166    pub(crate) log_header_color_enabled: bool,
167
168    pub(crate) debug_color: Color,
169    pub(crate) info_color: Color,
170    pub(crate) warning_color: Color,
171    pub(crate) error_color: Color,
172    pub(crate) fatal_color: Color,
173
174    pub(crate) debug_header: String,
175    pub(crate) info_header: String,
176    pub(crate) warning_header: String,
177    pub(crate) error_header: String,
178    pub(crate) fatal_header: String,
179
180    pub(crate) log_format: String,
181    pub(crate) datetime_format: String,
182
183    pub(crate) file_logging_enabled: bool,
184    pub(crate) log_file_path: String,
185    pub(crate) log_buffer_max_size: usize,
186    pub(crate) on_drop_policy: OnDropPolicy,
187
188    // Dynamic variables that shouldn't be included in the template file:
189    #[serde(skip)]
190    pub(crate) log_buffer: Vec<LogStruct>,
191    #[serde(skip)]
192    pub(crate) show_datetime: bool,
193    #[serde(skip)]
194    pub(crate) log_file_lock: bool,
195}
196
197impl Drop for Logger {
198    fn drop(&mut self) {
199        let _ = self.drop_flush();
200    }
201}
202
203impl Logger {
204    // INTERNAL METHODS
205
206    pub(crate) fn print_log(&mut self, log: &LogStruct) {
207        let log_str = self.format_log(log);
208
209        if self.file_logging_enabled {
210            self.log_buffer.push(log.clone());
211
212            if self.log_buffer_max_size != 0
213            && self.log_buffer.len() >= self.log_buffer_max_size {
214                let _ = self.flush_file_log_buffer(false);
215            }
216        }
217
218        print!("{}", log_str);
219    }
220
221    pub(crate) fn get_log_headers(&self, log: &LogStruct)
222    -> (String, String, String) {
223        let header = self.get_main_header(log.log_type);
224        let datetime = self.get_datetime_formatted(&log.datetime);
225        return (header, datetime, log.message.clone());
226    }
227
228    pub(crate) fn get_main_header(&self, log_type: LogType) -> String {
229        match log_type {
230            LogType::Debug => { 
231                self.colorify(&self.debug_header,
232                    self.log_header_color(log_type))
233            }
234            LogType::Info => {
235                self.colorify(&self.info_header,
236                    self.log_header_color(log_type))
237            }
238            LogType::Warning => {
239                self.colorify(&self.warning_header,
240                    self.log_header_color(log_type))
241            }
242            LogType::Err => {
243                self.colorify(&self.error_header,
244                    self.log_header_color(log_type))
245            }
246            LogType::FatalError => {
247                self.colorify(&self.fatal_header,
248                    self.log_header_color(log_type))
249            }
250        }
251    }
252
253    pub(crate) fn get_datetime_formatted(&self,
254    datetime: &DateTime<Local>) -> String {
255        if self.show_datetime {
256            let datetime_formatted = datetime.format(&self.datetime_format);
257            return datetime_formatted.to_string();
258        }
259        else {
260            return String::from("");
261        }
262    }
263
264    pub(crate) fn colorify(&self, text: &str, color: Color) -> String {
265        if self.log_header_color_enabled {
266            if color != Color::None {
267                return get_color_code(color) + text + &RESET;
268            }
269            else {
270                return text.to_string();
271            }
272        }
273        else {
274            return text.to_string();
275        }
276    }
277
278    pub(crate) fn filter_log(&self, log_type: LogType) -> bool {
279        return !self.filtering_enabled
280            || ((log_type as i32) < self.verbosity as i32)
281    }
282
283    pub(crate) fn get_datetime(&self) -> DateTime<Local> {
284        return Local::now();
285    }
286
287    pub(crate) fn log_header_color(&self, log_type: LogType) -> Color {
288        match log_type {
289            LogType::Debug => { self.debug_color.clone() }
290            LogType::Info => { self.info_color.clone() }
291            LogType::Warning => { self.warning_color.clone() }
292            LogType::Err => { self.error_color.clone() }
293            LogType::FatalError => { self.fatal_color.clone() }
294        }
295    }
296
297    pub(crate) fn drop_flush(&mut self) {
298        if self.file_logging_enabled {
299            let _ = self.flush_file_log_buffer(true);
300        }
301    }
302
303    pub(crate) fn flush_file_log_buffer(&mut self, is_drop_flush: bool)
304    -> Result<(), String> {
305        if self.log_file_lock {
306            if is_drop_flush {
307                match self.on_drop_policy {
308                    OnDropPolicy::IgnoreLogFileLock => { }
309                    OnDropPolicy::DiscardLogBuffer => {
310                        let message = format!("Log file lock enabled and on
311                            drop policy set to {}!",
312                            self.on_drop_policy);
313                        return Err(message);
314                    }
315                }
316            }
317            else {
318               return Err(String::from("Log file lock enabled!"))
319            }
320        }
321        let mut buf = String::from("");
322
323        for log in &self.log_buffer {
324            buf += &self.format_log(&log);
325        }
326
327        self.log_buffer = Vec::new();
328        let result = append_to_file(&self.log_file_path, &buf);
329
330        match result {
331            Ok(_) => Ok(()),
332            Err(_) => { 
333                self.file_logging_enabled = false;
334                Err(String::from("Failed to write log buffer to a file!"))
335            },
336        }
337    }
338
339    // CONSTRUCTORS
340
341    /// Returns a `Logger` instance with default configuration applied.
342    pub fn default() -> Self {
343        Logger {
344            verbosity: Verbosity::default(),
345            filtering_enabled: true,
346            log_header_color_enabled: true,
347
348            debug_color: Color::Blue,
349            info_color: Color::Green,
350            warning_color: Color::Yellow,
351            error_color: Color::Red,
352            fatal_color: Color::Magenta,
353
354            debug_header: String::from("DBG"),
355            info_header: String::from("INF"),
356            warning_header: String::from("WAR"),
357            error_header: String::from("ERR"),
358            fatal_header: String::from("FATAL"),
359
360            log_format: String::from("[%h] %m"),
361            datetime_format: String::from("%Y-%m-%d %H:%M:%S"),
362
363            file_logging_enabled: false,
364            log_file_path: String::new(),
365            log_buffer_max_size: 128,
366            on_drop_policy: OnDropPolicy::default(),
367
368            show_datetime: false,
369            log_buffer: Vec::new(),
370            log_file_lock: false,
371        }
372    }
373
374
375    // PUBLIC METHODS
376
377    /// Returns a log entry out of a `LogStruct` based on current `Logger`
378    /// configuration.
379    pub fn format_log(&self, log: &LogStruct) -> String {
380        let headers = self.get_log_headers(&log);
381        let mut result = String::new();
382        let mut char_iter = self.log_format.char_indices().peekable();
383
384        while let Some((_, c)) = char_iter.next() {
385            match c {
386                '%' => {
387                    if let Some((_, nc)) = char_iter.peek() {
388                        match nc {
389                            'h' => {
390                                result += &headers.0;
391                                char_iter.next();
392                            }
393                            'd' => {
394                                result += &headers.1;
395                                char_iter.next();
396                            }
397                            'm' => {
398                                result += &headers.2;
399                                char_iter.next();
400                            }
401                            _ => {
402                                result += &nc.to_string();
403                                char_iter.next();
404                            }
405                        }
406                    }
407                }
408                _ => {
409                    result += &c.to_string();
410                }
411            }
412        }
413
414        result += &"\n";
415        return result;
416    }
417
418    /// Flushes log buffer (if file logging is enabled and log file lock
419    /// disabled, it writes the log buffer to a file) and then clears the log
420    /// buffer.
421    ///
422    /// Returns an error when there is an issue writing to a file or log file
423    /// lock is enabled.
424    pub fn flush(&mut self) -> Result<(), String> {
425        if self.file_logging_enabled {
426            self.flush_file_log_buffer(false)?;
427        }
428        return Ok(());
429    }
430
431    /// Prints a **debug message**.
432    pub fn debug(&mut self, message: &str) {
433        if self.filter_log(LogType::Debug)
434        {
435            return;
436        }
437        let log = LogStruct {
438            message: message.to_string(),
439            log_type: LogType::Debug,
440            datetime: self.get_datetime(),
441        };
442        self.print_log(&log);
443    }
444
445    /// Prints a **debug message**, bypasses filtering.
446    pub fn debug_no_filtering(&mut self, message: &str) {
447        let log = LogStruct {
448            message: message.to_string(),
449            log_type: LogType::Debug,
450            datetime: self.get_datetime(),
451        };
452        self.print_log(&log);
453    }
454
455    /// Prints **info message**.
456    pub fn info(&mut self, message: &str) {
457        if self.filter_log(LogType::Info)
458        {
459            return;
460        }
461        let log = LogStruct {
462            message: message.to_string(),
463            log_type: LogType::Info,
464            datetime: self.get_datetime(),
465        };
466        self.print_log(&log);
467    }
468
469    /// Prints **info message**, bypasses filtering.
470    pub fn info_no_filtering(&mut self, message: &str) {
471        let log = LogStruct {
472            message: message.to_string(),
473            log_type: LogType::Info,
474            datetime: self.get_datetime(),
475        };
476        self.print_log(&log);
477    }
478
479    /// Prints a **warning**.
480    pub fn warning(&mut self, message: &str) {
481        if self.filter_log(LogType::Warning)
482        {
483            return;
484        }
485        let log = LogStruct {
486            message: message.to_string(),
487            log_type: LogType::Warning,
488            datetime: self.get_datetime(),
489        };
490        self.print_log(&log);
491    }
492
493    /// Prints a **warning**, bypasses filtering.
494    pub fn warning_no_filtering(&mut self, message: &str) {
495        let log = LogStruct {
496            message: message.to_string(),
497            log_type: LogType::Warning,
498            datetime: self.get_datetime(),
499        };
500        self.print_log(&log);
501    }
502
503    /// Prints an **error** (errors cannot be suppressed).
504    pub fn error(&mut self, message: &str) {
505        let log = LogStruct {
506            message: message.to_string(),
507            log_type: LogType::Err,
508            datetime: self.get_datetime(),
509        };
510        self.print_log(&log);
511    }
512
513    /// Prints a **fatal error** (errors cannot be suppressed).
514    pub fn fatal(&mut self, message: &str) {
515        let log = LogStruct {
516            message: message.to_string(),
517            log_type: LogType::FatalError,
518            datetime: self.get_datetime(),
519        };
520        self.print_log(&log);
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527    use std::{
528        env, io, path::PathBuf
529    };
530
531    fn get_current_dir() -> io::Result<PathBuf> {
532        let current_dir = env::current_dir()?;
533        Ok(current_dir)
534    }
535
536    #[test]
537    fn test_log_filtering() {
538        let mut l = Logger::default();
539        l.toggle_log_filtering(true);
540        l.set_verbosity(Verbosity::ErrorsOnly);
541
542        if !l.filter_log(LogType::Debug) {
543            panic!("A debug log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
544        }
545        if !l.filter_log(LogType::Info) {
546            panic!("An informative log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
547        }
548        if !l.filter_log(LogType::Warning) {
549            panic!("A warning log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
550        }
551
552        l.set_verbosity(Verbosity::Quiet);
553        if !l.filter_log(LogType::Debug) {
554            panic!("A debug log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
555        }
556        if !l.filter_log(LogType::Info) {
557            panic!("An informative log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
558        }
559        if l.filter_log(LogType::Warning) {
560            panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
561        }
562
563        l.set_verbosity(Verbosity::Standard);
564        if !l.filter_log(LogType::Debug) {
565            panic!("A debug log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
566        }
567        if l.filter_log(LogType::Info) {
568            panic!("An informative log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
569        }
570        if l.filter_log(LogType::Warning) {
571            panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
572        }
573
574        l.set_verbosity(Verbosity::All);
575        if l.filter_log(LogType::Debug) {
576            panic!("A debug log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
577        }
578        if l.filter_log(LogType::Info) {
579            panic!("An informative log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
580        }
581        if l.filter_log(LogType::Warning) {
582            panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
583        }
584
585        l.set_verbosity(Verbosity::All);
586        l.toggle_log_filtering(true);
587        if l.filter_log(LogType::Debug) {
588            panic!("A debug log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
589        }
590        if l.filter_log(LogType::Info) {
591            panic!("An informative log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
592        }
593        if l.filter_log(LogType::Warning) {
594            panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
595        }
596    }
597
598    #[test]
599    fn test_log_headers() {
600        // Test if header format setting works
601        let header = "askljdfh";
602
603        let mut l = Logger::default();
604
605        l.set_debug_header(header);
606        if l.get_main_header(LogType::Debug) != 
607        l.colorify(header, l.log_header_color(LogType::Debug)) {
608            panic!("Debug headers do not match!");
609        }
610        l.set_info_header(header);
611        if l.get_main_header(LogType::Info) != 
612        l.colorify(header, l.log_header_color(LogType::Info)) {
613            panic!("Info headers do not match!");
614        }
615        l.set_warning_header(header);
616        if l.get_main_header(LogType::Warning) !=
617        l.colorify(header, l.log_header_color(LogType::Warning)) {
618            panic!("Warning headers do not match!");
619        }
620        l.set_error_header(header);
621        if l.get_main_header(LogType::Err) != 
622        l.colorify(header, l.log_header_color(LogType::Err)) {
623            panic!("Error headers do not match!");
624        }
625        l.set_fatal_header(header);
626        if l.get_main_header(LogType::FatalError) != 
627        l.colorify(header, l.log_header_color(LogType::FatalError)) {
628            panic!("Fatal error headers do not match!");
629        }
630    }
631
632    #[test]
633    fn test_log_colors() {
634        // Test if colorify works
635        let l = Logger::default();
636        if l.colorify("a", Color::Red) != "\x1b[31ma\x1b[0m"
637        {
638            panic!("Failed to colorify a string!");
639        }
640    }
641
642    #[test]
643    fn test_templates() {
644        let file_name = "/templates/test.json";
645        match get_current_dir() {
646            Ok(current_dir) => {
647                let path = current_dir
648                    .to_str()
649                    .map(|s| s.to_string() + file_name)
650                    .unwrap_or_else(|| String::from(file_name));
651
652                let mut l = Logger::default();
653                l.save_template(&path);
654                l = Logger::from_template(&path);
655
656                if l != Logger::default() {
657                    panic!("Templates are not the same!");
658                }
659            }
660            Err(e) => {
661                eprintln!("Error getting current directory: {}", e);
662            }
663        }
664    }
665
666    #[test]
667    fn test_formats() {
668        let mut l = Logger::default();
669
670        l.set_datetime_format("aaa");
671        l.set_debug_header("d");
672        l.set_info_header("i");
673        l.set_warning_header("W");
674        l.set_error_header("E");
675        l.set_fatal_header("!");
676        let _ = l.set_log_format("<l> <h>%h</h> <d>%d</d> <m>%m</m> </l>");
677
678        let mut logstruct = LogStruct {
679            datetime: l.get_datetime(),
680            log_type: LogType::Debug,
681            message: "aaa".to_string(),
682        };
683        let mut comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
684            l.colorify("d", l.log_header_color(LogType::Debug))
685        );
686
687        if l.format_log(&logstruct) != comp {
688            panic!("Bad log formatting, expected \n'{}', got \n'{}'",
689                comp,
690                l.format_log(&logstruct));
691        }
692
693        logstruct.log_type = LogType::Info;
694        comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
695            l.colorify("i", l.log_header_color(LogType::Info))
696        );
697        if l.format_log(&logstruct) != comp {
698            panic!("Bad log formatting, expected \n'{}', got \n'{}'",
699                comp,
700                l.format_log(&logstruct));
701        }
702
703        logstruct.log_type = LogType::Warning;
704        comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
705            l.colorify("W", l.log_header_color(LogType::Warning))
706        );
707        if l.format_log(&logstruct) != comp {
708            panic!("Bad log formatting, expected \n'{}', got \n'{}'",
709                comp,
710                l.format_log(&logstruct));
711        }
712
713        logstruct.log_type = LogType::Err;
714        comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
715            l.colorify("E", l.log_header_color(LogType::Err))
716        );
717        if l.format_log(&logstruct) != comp {
718            panic!("Bad log formatting, expected \n'{}', got \n'{}'",
719                comp,
720                l.format_log(&logstruct));
721        }
722
723        logstruct.log_type = LogType::FatalError;
724        comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
725            l.colorify("!", l.log_header_color(LogType::FatalError))
726        );
727        if l.format_log(&logstruct) != comp {
728            panic!("Bad log formatting, expected \n'{}', got \n'{}'",
729                comp,
730                l.format_log(&logstruct));
731        }
732    }
733
734    #[test]
735    fn test_file_logging() {
736        let file_name = "/output.log";
737        let max_size: usize = 16;
738        let mut l = Logger::default();
739        l.set_max_log_buffer_size(max_size);
740
741        let current_dir = get_current_dir();
742
743        match current_dir {
744            Ok(current_dir) => {
745                let path = current_dir
746                    .to_str()
747                    .map(|s| s.to_string() + file_name)
748                    .unwrap_or_else(|| String::from(file_name));
749
750                let result = l.set_log_file_path(&path);
751
752                match result {
753                    Ok(()) => {
754                        let _ = l.toggle_file_logging(true);
755                        let mut i = 0;
756                        loop {
757                            l.fatal(&format!("i: {}", i));
758
759                            if i >= max_size {
760                                break;
761                            } 
762                            i += 1;
763                        }
764                    },
765                    Err(_) => { panic!("Failed to set the log file path to
766                        '{}'!", path) },
767                }
768            },
769            Err(_) => { panic!("Failed to get current directory!") },
770        }
771    }
772}