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
154
155
156
157
158
159
160
161
162
163
164
165
166
//! The `icon_map` module provides functionality for mapping `IconKind` enum variants to their corresponding icons and colors.
//! It contains a `Lazy` static `ICON_MAP` which is a thread-safe `HashMap` that maps `IconKind` enum variants to a tuple of an icon and a color.
//! The `ICON_MAP` is used by the `Whisper` struct to look up the icon and color based on the `IconKind`.
//! The `ICON_MAP` is lazily initialized and contains mappings for both `NerdFont` and Unicode icons.
//!
//! The `IconKind` enum represents different kinds of icons for formatting messages. It supports both Unicode or Nerd Font icons if you have a Nerd Font installed.
//!
use enum_iterator::Sequence;
use once_cell::sync::Lazy;
use std::fmt;
use std::{collections::HashMap, sync::Mutex};

/// `IconKind` is an enum representing different kinds of icons for formatting messages.
///
///  # Examples
/// ```
/// use murmur::{Whisper, IconKind};
///
/// Whisper::new()
///     .icon(IconKind::NfFaTimes)
///     .message("This is a message with a Nerd Font icon.")
///     .whisper()
///     .unwrap();
/// ```
///
/// You must have [NerdFonts](https://www.nerdfonts.com/) installed to use the `Nf` variants.
/// - [Nerfonts github](https://github.com/ryanoasis/nerd-fonts?tab=readme-ov-files)
/// - [NerdFonts cheat-sheet](https://www.nerdfonts.com/cheat-sheet)
#[derive(Debug, Clone, Eq, PartialEq, Hash, Sequence)]
pub enum IconKind {
    NfFaTimes,
    NfFaCheck,
    NfFaInfoCircle,
    NfFaRefresh,
    NfFaWarning,
    NfFaBug,
    NfOctDotFill,

    UnicodeCrossMark,
    UnicodeCheckMark,
    UnicodeInformationSource,
    UnicodeGear,
    UnicodeWarningSign,
    UnicodeBug,
}

/// Implement the `Display` trait for `IconKind`.
impl fmt::Display for IconKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self:?}")
    }
}

/// A static `ICON_MAP` that maps `IconKind` to a tuple of icon and color.
///
/// This map is lazily initialized and thread-safe. It contains mappings for both `NerdFont` and Unicode icons.
/// Each `IconKind` is mapped to a tuple, where the first element is the icon character and the second element is the color.
///
/// The `ICON_MAP` is used by the `Whisper` struct to look up the icon and color based on the `IconKind`.
///
/// If the `tracing` feature is enabled, an informational message will be logged when the `ICON_MAP` is initialized.
pub static ICON_MAP: Lazy<Mutex<HashMap<IconKind, (&'static str, &'static str)>>> =
    Lazy::new(|| {
        let mut icon_map = HashMap::new();
        // Nerd Font icons
        icon_map.insert(IconKind::NfFaTimes, ("\u{f00d} ", "red")); // 
        icon_map.insert(IconKind::NfFaCheck, ("\u{f00c} ", "green")); // 
        icon_map.insert(IconKind::NfFaInfoCircle, ("\u{f05a} ", "white")); // 
        icon_map.insert(IconKind::NfFaRefresh, ("\u{f021} ", "cyan")); // 
        icon_map.insert(IconKind::NfFaWarning, ("\u{f071} ", "yellow")); // 
        icon_map.insert(IconKind::NfFaBug, ("\u{f188} ", "red")); // 
        icon_map.insert(IconKind::NfOctDotFill, ("\u{f444} ", "white")); // 

        // Unicode icons
        icon_map.insert(IconKind::UnicodeCrossMark, ("\u{274C} ", "red")); // ❌
        icon_map.insert(IconKind::UnicodeCheckMark, ("\u{2714}\u{FE0F} ", "green")); // ✔️
        icon_map.insert(
            IconKind::UnicodeInformationSource,
            ("\u{2139}\u{FE0F} ", "white"),
        ); // ℹ️
        icon_map.insert(IconKind::UnicodeGear, ("\u{2699}\u{FE0F} ", "cyan")); // ⚙️
        icon_map.insert(
            IconKind::UnicodeWarningSign,
            ("\u{26A0}\u{FE0F} ", "yellow"),
        ); // ⚠️
        icon_map.insert(IconKind::UnicodeBug, ("\u{1F41B} ", "red")); // 🐛

        Mutex::new(icon_map)
    });

#[cfg(test)]
mod icon_map_tests {
    use super::*;
    use crate::Whisper;
    use color_eyre::Report;
    use enum_iterator::all;

    #[test]
    fn color_eyre_install_setup() -> Result<(), Report> {
        color_eyre::install()?;
        Whisper::new().message("color_eyre installed").whisper()?;
        Ok(())
    }
    /// Test function to print all icons associated with different kinds of messages.
    ///
    /// The function begins by calling `all::<IconKind>().collect::<Vec<_>>()`.
    /// This line of code uses the `all` function from the `enum_iterator` crate
    /// to create an iterator over all variants of the `IconKind` enum.
    /// The `collect::<Vec<_>>()` function is then used to collect these variants into a vector.
    ///
    /// Next, the function calls `iter()` on the vector to create an iterator,
    /// and then uses `for_each` to apply a closure to each `IconKind` variant in the iterator.
    ///
    /// Inside the closure, the function first acquires a lock on the `ICON_MAP` static variable,
    /// which is a thread-safe `HashMap` that maps `IconKind` enum variants to a tuple of an icon and a color.
    /// The `unwrap()` function is used to handle the `Result` returned by `lock()`.
    /// If the lock can't be acquired, the program will panic and terminate.
    ///
    /// Then, the function calls `get(icon_kind)` on the `ICON_MAP` to look up the icon and color associated with the current `IconKind` variant.
    /// The `unwrap().0` at the end extracts the icon from the tuple (ignoring the color),
    /// and if the `get` call returns `None` (i.e., if the `IconKind` variant is not found in the `ICON_MAP`),
    /// the program will panic and terminate.
    ///
    /// Finally, the function prints the `IconKind` variant and the associated icon to the console.
    ///
    /// In summary, this test function is used to print all the icons in the `ICON_MAP` to the console.
    /// It's a simple way to visually check that all the icons are correctly mapped to their corresponding `IconKind` variants.
    #[test]
    fn test_print_all_icons() {
        all::<IconKind>()
            .collect::<Vec<_>>()
            .iter()
            .for_each(|icon_kind| {
                println!(
                    "{}: {}",
                    icon_kind,
                    ICON_MAP.lock().unwrap().get(icon_kind).unwrap().0
                );
            });
    }

    /// This test function checks the spacing after each icon in the `ICON_MAP`.
    ///
    /// It iterates over each `IconKind` and its associated icon in the `ICON_MAP`.
    /// For each icon, it asserts that the icon ends with exactly one space.
    /// If an icon ends with no space or more than one space, the assertion fails and the test function panics.
    ///
    /// # Panics
    ///
    /// This function will panic if any icon in the `ICON_MAP` does not end with exactly one space.
    #[test]
    fn test_spaces_after_icons() {
        let icon_map = {
            let guard = ICON_MAP.lock().unwrap();
            guard.clone()
        };

        for (icon_kind, (icon, _)) in &icon_map {
            // Check that there is only one space after the icon
            assert!(
                icon.ends_with(' ') && !icon.ends_with("  "),
                "Invalid spacing after {icon_kind} icon: '{icon}'",
            );
        }
    }
}