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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
mod macros;
mod utils;

mod test;

use std::{
    fmt::Debug,
    fs::{File, OpenOptions},
    io::prelude::*,
    sync::OnceLock,
};

use utils::{importance_tags::*, time_utils};

/// Is given to the log functions to specify the importance of the log and is provided in the header of the log
pub enum LogImportance {
    Error,
    Warning,
    Info,
    Debug,
}

/// Is given to the log functions to specify the log importance and the message of the log
pub struct LogData {
    pub importance: LogImportance,
    pub message: String,
}

/// Scorched version, has no internal use
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// The crate statics for the logging path and prefix
pub static LOG_PATH: OnceLock<&str> = OnceLock::new();
pub static LOG_PREFIX: OnceLock<&str> = OnceLock::new();

/// Changes the environment variable for logging path
pub fn set_logging_path(path: &str) {
    let path = if path.ends_with('/') || path.ends_with('\\') {
        path.to_string()
    } else {
        let mut path = path.to_string();
        path.push('/');
        path
    };

    match LOG_PATH.set(Box::leak(path.into_boxed_str())) {
        Ok(_) => (),
        Err(_) => panic!("Failed to set logging path"),
    }
}

/// Changes the environment variable for logging prefix, this is the text that is displayed before the log message
pub fn set_log_prefix(prefix: String) {
    match LOG_PREFIX.set(Box::leak(prefix.into_boxed_str())) {
        Ok(_) => (),
        Err(_) => panic!("Failed to set logging prefix"),
    }
}

/// Logs the given data to the console with the error type and then to a file
pub fn log_this(data: LogData) {
    // Creates logs folder if it doesn't exist
    if !std::path::Path::new(LOG_PATH.get().unwrap_or(&"logs/")).exists() {
        std::fs::create_dir_all(LOG_PATH.get().unwrap_or(&"logs/")).log_expect(
            LogImportance::Error,
            "Failed to create full path to logs folder",
        );
    }

    let file = OpenOptions::new().append(true).create(true).open(format!(
        "{}{}.log",
        LOG_PATH.get().unwrap_or(&"logs/"),
        time_utils::get_formatted_time(time_utils::TimeFormat::Date)
    ));

    // Appends the prefix to the message if it exists
    let message = {
        match LOG_PREFIX.get() {
            Some(val) => format!("{} {}", val, data.message),
            None => data.message,
        }
    };

    fn write_log(importance: LogImportance, message: &str, file: &mut File) {
        let tag = match importance {
            LogImportance::Error => (ERROR_TAG, "ERROR"),
            LogImportance::Warning => (WARNING_TAG, "WARNING"),
            LogImportance::Info => (INFO_TAG, "INFO"),
            LogImportance::Debug => (DEBUG_TAG, "DEBUG"),
        };

        file.write_all(
            format!(
                "{} [{}] {}\n",
                time_utils::get_formatted_time(time_utils::TimeFormat::DateTime),
                tag.1,
                message
            )
            .as_bytes(),
        )
        .unwrap();

        println!(
            "{} {} {}",
            time_utils::get_formatted_time(time_utils::TimeFormat::Time),
            tag.0,
            message
        );
    }

    match data.importance {
        LogImportance::Error => write_log(LogImportance::Error, &message, &mut file.unwrap()),
        LogImportance::Warning => write_log(LogImportance::Warning, &message, &mut file.unwrap()),
        LogImportance::Info => write_log(LogImportance::Info, &message, &mut file.unwrap()),
        LogImportance::Debug => write_log(LogImportance::Debug, &message, &mut file.unwrap()),
    }
}

pub trait LogExpect<T, E: Debug> {
    fn log_expect(self, importance: LogImportance, msg: &str) -> T;
}

impl<T, E: Debug> LogExpect<T, E> for Result<T, E> {
    fn log_expect(self, importance: LogImportance, msg: &str) -> T {
        match self {
            Ok(val) => val,
            Err(err) => {
                log_this(LogData {
                    importance,
                    message: msg.to_string(),
                });

                panic!("{}: {:?}", msg, err);
            }
        }
    }
}

impl<T> LogExpect<T, ()> for Option<T> {
    fn log_expect(self, importance: LogImportance, msg: &str) -> T {
        match self {
            Some(val) => val,
            None => {
                log_this(LogData {
                    importance,
                    message: msg.to_string(),
                });

                panic!("{}", msg);
            }
        }
    }
}