lang_interpreter/
terminal_io.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::error::Error;
use std::ffi::OsString;
use std::{fmt, io};
use std::fs::File;
use std::io::Write;
use chrono::Local;

#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
#[repr(i8)]
pub enum Level {
    #[default]
    NotSet = -1,
    User = 0,
    Debug = 1,
    Config = 2,
    Info = 3,
    Warning = 4,
    Error = 5,
    Critical = 6,
}

impl Level {
    pub(crate) const VALUES: [Self; 8] = [
        Self::NotSet,
        Self::User,
        Self::Debug,
        Self::Config,
        Self::Info,
        Self::Warning,
        Self::Error,
        Self::Critical,
    ];

    pub fn level(&self) -> i8 {
        *self as i8
    }

    pub fn name(&self) -> String {
        match self {
            Level::NotSet => "Not set",
            Level::User => "User",
            Level::Debug => "Debug",
            Level::Config => "Config",
            Level::Info => "Info",
            Level::Warning => "Warning",
            Level::Error => "Error",
            Level::Critical => "Critical",
        }.to_string()
    }

    pub fn should_log(&self, log_level: Level) -> bool {
        log_level == Level::User || *self as i8 >= log_level as i8
    }
}

#[derive(Debug)]
pub struct TerminalIO {
    file: Option<File>,
    log_level: Level,
}

impl TerminalIO {
    const TIME_FORMAT: &'static str = "%d.%m.%Y|%H:%M:%S";

    pub fn new(log_file: Option<OsString>) -> Result<Self, io::Error> {
        let file = if let Some(log_file) = log_file {
            Some(File::create(log_file)?)
        }else {
            None
        };

        Ok(Self {
            file,
            log_level: Level::NotSet,
        })
    }

    pub fn set_level(&mut self, level: Level) {
        self.log_level = level;
    }

    fn log_internal(&mut self, lvl: Level, txt: impl Into<String>, tag: impl Into<String>, new_line: bool) {
        if !lvl.should_log(self.log_level) {
            return;
        }

        let current_time = Local::now().format(Self::TIME_FORMAT);

        let mut log = format!(
            "[{}][{}][{}]: {}",
            lvl.name(),
            current_time,
            tag.into(),
            txt.into(),
        );

        if new_line {
            log += "\n";
        }

        print!("{log}");
        
        if let Some(file) = &mut self.file {
            let err = write!(file, "{log}");
            if let Err(e) = err {
                //Doesn't use the log_stack_trace method to avoid a stack overflow
                eprintln!("{e}");
            }
        }
    }
    
    pub fn log(&mut self, lvl: Level, txt: impl Into<String>, tag: impl Into<String>) {
        self.log_internal(lvl, txt, tag, false);
    }
    
    pub fn logf(&mut self, lvl: Level, fmt: fmt::Arguments<'_>, tag: impl Into<String>) {
        self.log(lvl, fmt.to_string(), tag);
    }
    
    pub fn logln(&mut self, lvl: Level, txt: impl Into<String>, tag: impl Into<String>) {
        self.log_internal(lvl, txt, tag, true);
    }
    
    pub fn log_stack_trace(&mut self, error: Box<dyn Error>, tag: impl Into<String>) {
        self.logln(Level::Error, error.to_string(), tag);
    }
}