lang_interpreter/
terminal_io.rs1#[cfg(feature = "custom-logging")]
2#[doc(hidden)]
3pub mod custom_logging;
4#[cfg(feature = "custom-logging")]
5#[doc(inline)]
6pub use custom_logging::{Logger, DefaultLogger};
7
8#[cfg(all(
9 feature = "custom-logging",
10 feature = "wasm",
11))]
12#[doc(inline)]
13pub use custom_logging::wasm::JsConsoleLogger;
14
15use std::error::Error;
16use std::ffi::OsString;
17use std::io;
18use std::fs::File;
19use std::io::Write;
20use chrono::Local;
21
22#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Default)]
23#[repr(i8)]
24pub enum Level {
25 #[default]
26 NotSet = -1,
27 User = 0,
28 Debug = 1,
29 Config = 2,
30 Info = 3,
31 Warning = 4,
32 Error = 5,
33 Critical = 6,
34}
35
36impl Level {
37 pub(crate) const VALUES: [Self; 8] = [
38 Self::NotSet,
39 Self::User,
40 Self::Debug,
41 Self::Config,
42 Self::Info,
43 Self::Warning,
44 Self::Error,
45 Self::Critical,
46 ];
47
48 pub fn level(&self) -> i8 {
49 *self as i8
50 }
51
52 pub fn name(&self) -> String {
53 match self {
54 Level::NotSet => "Not set",
55 Level::User => "User",
56 Level::Debug => "Debug",
57 Level::Config => "Config",
58 Level::Info => "Info",
59 Level::Warning => "Warning",
60 Level::Error => "Error",
61 Level::Critical => "Critical",
62 }.to_string()
63 }
64
65 pub fn should_log(&self, log_level: Level) -> bool {
66 log_level == Level::User || *self as i8 >= log_level as i8
67 }
68}
69
70#[derive(Debug)]
71pub struct TerminalIO {
72 file: Option<File>,
73 log_level: Level,
74
75 #[cfg(feature = "custom-logging")]
76 logger: Box<dyn Logger>,
77}
78
79impl TerminalIO {
80 const TIME_FORMAT: &'static str = "%d.%m.%Y|%H:%M:%S";
81
82 pub fn new(log_file: Option<OsString>) -> Result<Self, io::Error> {
83 let file = if let Some(log_file) = log_file {
84 Some(File::create(log_file)?)
85 }else {
86 None
87 };
88
89 #[cfg(not(feature = "custom-logging"))]
90 {
91 Ok(Self {
92 file,
93 log_level: Level::NotSet,
94 })
95 }
96 #[cfg(feature = "custom-logging")]
97 {
98 #[cfg(not(feature = "wasm"))]
99 {
100 Ok(Self {
101 file,
102 log_level: Level::NotSet,
103 logger: Box::new(DefaultLogger),
104 })
105 }
106 #[cfg(feature = "wasm")]
107 {
108 Ok(Self {
109 file,
110 log_level: Level::NotSet,
111 logger: Box::new(JsConsoleLogger),
112 })
113 }
114 }
115 }
116
117 #[cfg(feature = "custom-logging")]
121 pub fn with_custom_logger(log_file: Option<OsString>, logger: Box<dyn Logger>) -> Result<Self, io::Error> {
122 let file = if let Some(log_file) = log_file {
123 Some(File::create(log_file)?)
124 }else {
125 None
126 };
127
128 Ok(Self {
129 file,
130 log_level: Level::NotSet,
131 logger,
132 })
133 }
134
135 pub fn set_level(&mut self, level: Level) {
136 self.log_level = level;
137 }
138
139 fn log_internal(&mut self, lvl: Level, txt: &str, tag: &str) {
140 if !lvl.should_log(self.log_level) {
141 return;
142 }
143
144 let current_time = Local::now().format(Self::TIME_FORMAT).to_string();
145
146 let log = format!(
147 "[{}][{}][{}]: {}",
148 lvl.name(),
149 current_time,
150 tag,
151 txt,
152 );
153
154 #[cfg(not(feature = "custom-logging"))]
155 {
156 #[cfg(not(feature = "wasm"))]
157 {
158 println!("{log}");
159 }
160 #[cfg(feature = "wasm")]
161 {
162 match lvl {
163 Level::NotSet | Level::User | Level::Config => web_sys::console::log_1(&log.as_str().into()),
164 Level::Debug => web_sys::console::debug_1(&log.as_str().into()),
165 Level::Info => web_sys::console::info_1(&log.as_str().into()),
166 Level::Warning => web_sys::console::warn_1(&log.as_str().into()),
167 Level::Error | Level::Critical => web_sys::console::error_1(&log.as_str().into()),
168 }
169 }
170 }
171 #[cfg(feature = "custom-logging")]
172 self.logger.log(lvl, ¤t_time, txt, tag);
173
174 if let Some(file) = &mut self.file {
175 let err = write!(file, "{log}");
176 if let Err(e) = err {
177 #[cfg(not(feature = "wasm"))]
179 {
180 eprintln!("{e}");
181 }
182 #[cfg(feature = "wasm")]
183 {
184 web_sys::console::error_1(&e.to_string().into());
185 }
186 }
187 }
188 }
189
190 pub fn log(&mut self, lvl: Level, txt: impl Into<String>, tag: impl Into<String>) {
191 self.log_internal(lvl, &txt.into(), &tag.into());
192 }
193
194 pub fn log_stack_trace(&mut self, error: Box<dyn Error>, tag: impl Into<String>) {
195 self.log(Level::Error, error.to_string(), tag);
196 }
197}