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
use std::{
    env,
    fmt::Debug,
    fs,
    io::{self, Write},
};

use once_cell::sync::Lazy;

///
/// pub for access in log! macro
#[doc(hidden)]
pub static LOGGER: Lazy<Index> = Lazy::new(Index::default);

#[macro_export]
macro_rules! log {
    ($name:tt $ext:tt, $($arg:tt)*) => {
        file_log::LOGGER.write_log($name, $ext, format!($($arg)*)).unwrap();
    };
    ($name:tt, $($arg:tt)*) => {
        file_log::LOGGER.write_log($name, "log", format!($($arg)*)).unwrap();
    };
}

const INDEX: &str = "log_index";
const FILE_LOG_INDEX_ENV_VAR: &str = "FILE_LOG_INDEX";

#[doc(hidden)]
#[derive(Debug)]
pub struct Index(usize);

/// Represents an index used for file logging.
///
/// The `Index` struct provides methods for managing and manipulating the index value.
impl Index {
    /// Increments the index value by 1.
    pub fn next(&mut self) {
        self.0 += 1;
    }

    /// Retrieves the index value from the environment variable, if it exists.
    /// If the environment variable is not set, it retrieves the index value from the index file.
    ///
    /// # Returns
    ///
    /// The `Index` value obtained from the environment or index file.
    fn get() -> Index {
        match env::var(FILE_LOG_INDEX_ENV_VAR) {
            // There is an env, prioritize its value
            Ok(index) => Index(index.parse().unwrap_or_default()),
            // There is no env, create use index_file
            Err(_) => Index(
                fs::read_to_string(INDEX)
                    .map(|i| i.parse().unwrap_or_default())
                    .unwrap_or_default(),
            ),
        }
    }

    ///
    /// Save the index to the index file
    fn save(&self) {
        fs::write(INDEX, format!("{}", self.0)).unwrap();
    }

    /// Returns a copy of the index value.
    ///
    /// # Returns
    ///
    /// The index value.
    pub fn index(&self) -> usize {
        self.0
    }

    /// Writes a log entry to a file with the specified extension.
    ///
    /// # Parameters
    ///
    /// - `log`: The name of the log file.
    /// - `extension`: The file extension.
    /// - `data`: The data to be written to the file.
    ///
    /// # Returns
    ///
    /// An `io::Result` indicating the success or failure of the write operation.
    pub fn write_log<C: AsRef<[u8]>>(&self, log: &str, extension: &str, data: C) -> io::Result<()> {
        let mut file = fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open(format!("{log}_{}.{extension}", self.0))?;
        file.write_all(data.as_ref())?;
        file.write_all("\n".as_bytes())
    }
}

impl Default for Index {
    fn default() -> Self {
        // Get the index from the env, if it exists, else get it from the index file.
        let mut index = Index::get();
        // If the env var is not set, increase the index and save the index to the index file
        if env::var(FILE_LOG_INDEX_ENV_VAR).is_err() {
            // Increment the index
            index.next();
            // Save the index to the index file
            index.save();
        }
        index
    }
}

pub fn index() -> usize {
    LOGGER.index()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_index_next() {
        let mut index = Index::default();
        let initial_value = index.index();
        index.next();
        assert_eq!(index.index(), initial_value + 1);
    }

    #[test]
    fn test_index_write_log() {
        let index = Index::default();
        let log_name = "test_log";
        let extension = "txt";
        let data = "Test log data";
        let result = index.write_log(log_name, extension, data);
        assert!(result.is_ok());

        // Verify that the log file was created
        let file_path = format!(
            "{log}_{}.{extension}",
            index.index(),
            log = log_name,
            extension = extension
        );
        assert!(fs::metadata(&file_path).is_ok());

        // Clean up the log file
        fs::remove_file(file_path).unwrap();
    }
}