lepticons_data/
lucide_icon_impl.rs1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::OnceLock;
4
5use convert_case::{Case, Casing};
6use lucide_icon_data::LucideGlyph;
7use strum::{EnumProperty, IntoEnumIterator};
8
9use crate::lucide_icon_data;
10
11pub trait Glyph: Copy {
13 fn svg(&self) -> &'static str;
15}
16
17impl Glyph for LucideGlyph {
18 fn svg(&self) -> &'static str {
19 self.get_str("svg").unwrap_or("")
20 }
21}
22
23fn split_csv(raw: &'static str) -> impl Iterator<Item = &'static str> {
25 raw.split(',').map(str::trim).filter(|s| !s.is_empty())
26}
27
28struct SearchEntry {
30 glyph: LucideGlyph,
31 text: String,
32}
33
34static SEARCH_INDEX: OnceLock<Vec<SearchEntry>> = OnceLock::new();
36
37static CATEGORIES: OnceLock<BTreeMap<String, u16>> = OnceLock::new();
39
40static COUNT: OnceLock<usize> = OnceLock::new();
42
43fn build_search_index() -> Vec<SearchEntry> {
44 LucideGlyph::iter()
45 .map(|glyph| {
46 let name: &'static str = glyph.into();
47 let name_lower = name.to_lowercase();
48 let tags = glyph.get_str("tags").unwrap_or("").to_lowercase();
49 let categories = glyph.get_str("categories").unwrap_or("").to_lowercase();
50 let text = format!("{},{},{}", name_lower, tags, categories);
51 SearchEntry { glyph, text }
52 })
53 .collect()
54}
55
56fn build_categories() -> BTreeMap<String, u16> {
57 let mut categories: BTreeMap<String, u16> = BTreeMap::new();
58 for icon in LucideGlyph::iter() {
59 let cats = icon.get_str("categories").unwrap_or("");
60 for cat in cats.split(',') {
61 let cat = cat.trim();
62 if !cat.is_empty() {
63 let count = categories
64 .entry(cat.to_case(Case::Title).to_string())
65 .or_insert(0);
66 *count += 1;
67 }
68 }
69 }
70 categories
71}
72
73impl LucideGlyph {
74 pub fn name(&self) -> &'static str {
77 (*self).into()
78 }
79
80 pub fn kebab_name(&self) -> String {
82 self.name().to_case(Case::Kebab)
83 }
84
85 pub fn by_name(name: &str) -> Option<LucideGlyph> {
88 LucideGlyph::from_str(name).ok()
89 }
90
91 pub fn count() -> usize {
93 *COUNT.get_or_init(|| LucideGlyph::iter().count())
94 }
95
96 pub fn categories_str(&self) -> &'static str {
98 self.get_str("categories").unwrap_or("")
99 }
100
101 pub fn tags_str(&self) -> &'static str {
103 self.get_str("tags").unwrap_or("")
104 }
105
106 pub fn categories(&self) -> impl Iterator<Item = &'static str> {
108 split_csv(self.categories_str())
109 }
110
111 pub fn tags(&self) -> impl Iterator<Item = &'static str> {
113 split_csv(self.tags_str())
114 }
115
116 pub fn contributors(&self) -> impl Iterator<Item = &'static str> {
118 split_csv(self.get_str("contributors").unwrap_or(""))
119 }
120
121 pub fn all_categories() -> &'static BTreeMap<String, u16> {
124 CATEGORIES.get_or_init(build_categories)
125 }
126
127 pub fn find(filter: &str) -> Vec<LucideGlyph> {
131 if filter.is_empty() {
132 return LucideGlyph::iter().collect();
133 }
134
135 let index = SEARCH_INDEX.get_or_init(build_search_index);
136 let filter_lower = filter.to_lowercase();
137 let terms: Vec<&str> = filter_lower.split_whitespace().collect();
138
139 index
140 .iter()
141 .filter(|entry| {
142 terms
143 .iter()
144 .all(|term| entry.text.contains(term))
145 })
146 .map(|entry| entry.glyph)
147 .collect()
148 }
149}