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}