murmur/
icon_map.rs

1//! The `icon_map` module provides functionality for mapping `IconKind` enum variants to their corresponding icons and colors.
2//! 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.
3//! The `ICON_MAP` is used by the `Whisper` struct to look up the icon and color based on the `IconKind`.
4//! The `ICON_MAP` is lazily initialized and contains mappings for both `NerdFont` and Unicode icons.
5//!
6//! 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.
7//!
8#![allow(deprecated)]
9
10use std::collections::HashMap;
11use std::fmt;
12use std::sync::RwLock;
13
14use enum_iterator::Sequence;
15use once_cell::sync::Lazy;
16
17/// `IconKind` is an enum representing different kinds of icons for formatting messages.
18///
19///  # Examples
20/// ```
21/// use murmur::{Whisper, IconKind};
22///
23/// Whisper::new()
24///     .icon(IconKind::NfFaTimes)
25///     .message("This is a message with a Nerd Font icon.")
26///     .whisper()
27///     .unwrap();
28/// ```
29///
30/// You must have [NerdFonts](https://www.nerdfonts.com/) installed to use the `Nf` variants.
31/// - [NerdFonts github](https://github.com/ryanoasis/nerd-fonts?tab=readme-ov-files)
32/// - [NerdFonts cheat-sheet](https://www.nerdfonts.com/cheat-sheet)
33#[non_exhaustive]
34#[derive(Debug, Clone, Eq, PartialEq, Hash, Sequence)]
35pub enum IconKind {
36    NfFaTimes,
37    NfFaCheck,
38    NfFaInfoCircle,
39    NfFaRefresh,
40    NfFaWarning,
41    NfFaBug,
42    NfFaQuestion,
43    NfFaQuestionCircle,
44    NfFaTerminal,
45    NfFaTrash,
46
47    NfFaAngleRight,
48    NfFaAngleLeft,
49    NfFaAngleUp,
50    NfFaAngleDown,
51    NfFaThumbsUp,
52    NfFaThumbsDown,
53    NfFaFolder,
54    NfFaFolderOpen,
55    NfFaeCcCc,
56    NfFaeEqual,
57
58    NfOctDotFill,
59
60    UnicodeCrossMark,
61    UnicodeCheckMark,
62    UnicodeInformationSource,
63    UnicodeGear,
64    UnicodeWarningSign,
65    UnicodeBug,
66}
67
68impl fmt::Display for IconKind {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        if let Some((icon, _)) = ICON_MAP.read().unwrap().get(self) {
71            write!(f, "{icon}")
72        } else {
73            write!(f, "Icon not found")
74        }
75    }
76}
77
78/// Red color.
79const RED: &str = "red";
80/// Green color.
81const GREEN: &str = "green";
82/// White color.
83const WHITE: &str = "white";
84/// Yellow color.
85const YELLOW: &str = "yellow";
86/// Cyan color.
87const CYAN: &str = "cyan";
88
89// pub static ICON_MAP: Lazy<Mutex<HashMap<IconKind, (&'static str, &'static str)>>>
90
91/// A static `ICON_MAP` that maps `IconKind` to a tuple of icon and color.
92///
93/// This map is lazily initialized and thread-safe. It contains mappings for both `NerdFont` and Unicode icons.
94/// Each `IconKind` is mapped to a tuple, where the first element is the icon character and the second element is the color.
95///
96/// The `ICON_MAP` is used by the `Whisper` struct to look up the icon and color based on the `IconKind`.
97///
98/// If the `tracing` feature is enabled, an informational message will be logged when the `ICON_MAP` is initialized.
99pub static ICON_MAP: Lazy<RwLock<HashMap<IconKind, (&'static str, &'static str)>>> =
100    Lazy::new(|| {
101        let mut i_map = HashMap::new();
102
103        // Nerd Font Font Awesome icons
104        i_map.insert(IconKind::NfFaTimes, ("\u{f00d} ", RED)); // 
105        i_map.insert(IconKind::NfFaCheck, ("\u{f00c} ", GREEN)); // 
106        i_map.insert(IconKind::NfFaInfoCircle, ("\u{f05a} ", WHITE)); // 
107        i_map.insert(IconKind::NfFaRefresh, ("\u{f021} ", CYAN)); // 
108        i_map.insert(IconKind::NfFaWarning, ("\u{f071} ", YELLOW)); // 
109        i_map.insert(IconKind::NfFaBug, ("\u{f188} ", RED)); // 
110        i_map.insert(IconKind::NfFaQuestion, ("\u{f128} ", RED)); // 
111        i_map.insert(IconKind::NfFaQuestionCircle, ("\u{f059} ", RED)); // 
112        i_map.insert(IconKind::NfFaTerminal, ("\u{f120} ", WHITE)); // 
113        i_map.insert(IconKind::NfFaTrash, ("\u{f1f8} ", WHITE)); // 
114        i_map.insert(IconKind::NfFaAngleRight, ("\u{f105} ", WHITE)); // 
115        i_map.insert(IconKind::NfFaAngleLeft, ("\u{f104} ", WHITE)); // 
116        i_map.insert(IconKind::NfFaAngleUp, ("\u{f106} ", WHITE)); // 
117        i_map.insert(IconKind::NfFaAngleDown, ("\u{f107} ", WHITE)); // 
118        i_map.insert(IconKind::NfFaThumbsUp, ("\u{f164} ", GREEN)); // 
119        i_map.insert(IconKind::NfFaThumbsDown, ("\u{f165} ", RED)); // 
120        i_map.insert(IconKind::NfFaFolder, ("\u{f07b} ", WHITE)); // 
121        i_map.insert(IconKind::NfFaFolderOpen, ("\u{f07c} ", WHITE)); // 
122
123        // Nerd Font Font Awesome Extension icons
124        i_map.insert(IconKind::NfFaeCcCc, ("\u{e291} ", WHITE)); // 
125        i_map.insert(IconKind::NfFaeEqual, ("\u{e279} ", WHITE)); // 
126
127        // Nerd Font Oct-icons
128        i_map.insert(IconKind::NfOctDotFill, ("\u{f444} ", WHITE)); // 
129
130        // Unicode icons
131        #[rustfmt::skip]
132        i_map.insert(IconKind::UnicodeInformationSource, ("\u{2139}\u{fe0f} ", WHITE)); // ℹ️
133        i_map.insert(IconKind::UnicodeGear, ("\u{2699}\u{FE0F} ", CYAN)); // ⚙️
134        i_map.insert(IconKind::UnicodeWarningSign, ("\u{26A0}\u{FE0F} ", YELLOW)); // ⚠️
135        i_map.insert(IconKind::UnicodeBug, ("\u{1F41B} ", RED)); // 🐛
136        i_map.insert(IconKind::UnicodeCrossMark, ("\u{274C} ", RED)); // ❌
137        i_map.insert(IconKind::UnicodeCheckMark, ("\u{2714}\u{FE0F} ", GREEN)); // ✔️
138
139        RwLock::new(i_map)
140    });
141
142#[cfg(test)]
143mod icon_map_tests {
144    use color_eyre::Report;
145    use enum_iterator::all;
146
147    use crate::Whisper;
148
149    use super::*;
150
151    #[test]
152    fn test_color_eyre_install_setup() -> Result<(), Report> {
153        color_eyre::install()?;
154        Whisper::new().message("color_eyre installed").whisper()?;
155        Ok(())
156    }
157    /// Test function to print all icons associated with different kinds of messages.
158    ///
159    /// The function begins by calling `all::<IconKind>().collect::<Vec<_>>()`.
160    /// This line of code uses the `all` function from the `enum_iterator` crate
161    /// to create an iterator over all variants of the `IconKind` enum.
162    /// The `collect::<Vec<_>>()` function is then used to collect these variants into a vector.
163    ///
164    /// Next, the function calls `iter()` on the vector to create an iterator,
165    /// and then uses `for_each` to apply a closure to each `IconKind` variant in the iterator.
166    ///
167    /// Inside the closure, the function first acquires a lock on the `ICON_MAP` static variable,
168    /// which is a thread-safe `HashMap` that maps `IconKind` enum variants to a tuple of an icon and a color.
169    /// The `unwrap()` function is used to handle the `Result` returned by `lock()`.
170    /// If the lock can't be acquired, the program will panic and terminate.
171    ///
172    /// Then, the function calls `get(icon_kind)` on the `ICON_MAP` to look up the icon and color associated with the current `IconKind` variant.
173    /// The `unwrap().0` at the end extracts the icon from the tuple (ignoring the color),
174    /// and if the `get` call returns `None` (i.e., if the `IconKind` variant is not found in the `ICON_MAP`),
175    /// the program will panic and terminate.
176    ///
177    /// Finally, the function prints the `IconKind` variant and the associated icon to the console.
178    ///
179    /// In summary, this test function is used to print all the icons in the `ICON_MAP` to the console.
180    /// It's a simple way to visually check that all the icons are correctly mapped to their corresponding `IconKind` variants.
181    #[test]
182    fn test_print_all_icons() {
183        all::<IconKind>()
184            .collect::<Vec<_>>()
185            .iter()
186            .for_each(|icon_kind| {
187                println!(
188                    "{}: {}",
189                    icon_kind,
190                    ICON_MAP.read().unwrap().get(icon_kind).unwrap().0
191                );
192            });
193    }
194
195    #[test]
196    fn test_whisper_all_icons() {
197        all::<IconKind>()
198            .collect::<Vec<_>>()
199            .iter()
200            .for_each(|icon_kind| {
201                #[rustfmt::skip]
202                Whisper::new()
203                    .icon(icon_kind.clone())
204                    .message(format!("{}: {}", icon_kind, ICON_MAP.read().unwrap().get(icon_kind).unwrap().0))
205                    .whisper()
206                    .unwrap();
207            });
208    }
209
210    /// This test function checks the spacing after each icon in the `ICON_MAP`.
211    ///
212    /// It iterates over each `IconKind` and its associated icon in the `ICON_MAP`.
213    /// For each icon, it asserts that the icon ends with exactly one space.
214    /// If an icon ends with no space or more than one space, the assertion fails and the test function panics.
215    ///
216    /// # Panics
217    ///
218    /// This function will panic if any icon in the `ICON_MAP` does not end with exactly one space.
219    #[test]
220    fn test_spaces_after_icons() {
221        let icon_map = {
222            let guard = ICON_MAP.read().unwrap();
223            guard.clone()
224        };
225
226        for (icon_kind, (icon, _)) in &icon_map {
227            // Check that there is only one space after the icon
228            assert!(
229                icon.ends_with(' ') && !icon.ends_with("  "),
230                "Invalid spacing after {icon_kind} icon: '{icon}'",
231            );
232        }
233    }
234}