1use std::{
2 collections::HashMap,
3 fs::{exists, read_to_string, write},
4 sync::LazyLock,
5};
6use tokio::sync::RwLock;
7
8use pathbufd::PathBufD;
9
10pub static ICONS: LazyLock<RwLock<HashMap<String, String>>> =
12 LazyLock::new(|| RwLock::new(HashMap::new()));
13
14pub async fn pull_icon(icon: &str, icons_dir: &str) {
16 let writer = &mut ICONS.write().await;
17
18 let icon_url = format!(
19 "https://raw.githubusercontent.com/lucide-icons/lucide/refs/heads/main/icons/{icon}.svg"
20 );
21
22 let file_path = PathBufD::current().extend(&[icons_dir, &format!("{icon}.svg")]);
23
24 if exists(&file_path).unwrap_or(false) {
25 writer.insert(icon.to_string(), read_to_string(&file_path).unwrap());
26 return;
27 }
28
29 println!("download icon: {icon}");
30 let svg = reqwest::get(icon_url)
31 .await
32 .unwrap()
33 .text()
34 .await
35 .unwrap()
36 .replace("\n", "");
37
38 write(&file_path, &svg).unwrap();
39 writer.insert(icon.to_string(), svg);
40}
41
42pub async fn pull_icons(mut input: String, icon_dir: &str) -> String {
44 let icon_with_class =
46 regex::Regex::new("(\\{\\{)\\s*(icon)\\s*(.*?)\\s*c\\((.*?)\\)\\s*(\\}\\})").unwrap();
47
48 for cap in icon_with_class.captures_iter(&input.clone()) {
49 let cap_str = &cap.get(3).unwrap().as_str().replace("\"", "");
50 let icon = &(if cap_str.contains(" }}") {
51 cap_str.split(" }}").next().unwrap().to_string()
52 } else {
53 cap_str.to_string()
54 });
55
56 pull_icon(icon, icon_dir).await;
57
58 let reader = ICONS.read().await;
59 let icon_text = reader.get(icon).unwrap().replace(
60 "<svg",
61 &format!("<svg class=\"icon {}\"", cap.get(4).unwrap().as_str()),
62 );
63
64 input = input.replace(
65 &format!(
66 "{{{{ icon \"{cap_str}\" c({}) }}}}",
67 cap.get(4).unwrap().as_str()
68 ),
69 &icon_text,
70 );
71 }
72
73 let icon_without_class = regex::Regex::new("(\\{\\{)\\s*(icon)\\s*(.*?)\\s*(\\}\\})").unwrap();
75
76 for cap in icon_without_class.captures_iter(&input.clone()) {
77 let cap_str = &cap.get(3).unwrap().as_str().replace("\"", "");
78 let icon = &(if cap_str.contains(" }}") {
79 cap_str.split(" }}").next().unwrap().to_string()
80 } else {
81 cap_str.to_string()
82 });
83
84 pull_icon(icon, icon_dir).await;
85
86 let reader = ICONS.read().await;
87 let icon_text = reader
88 .get(icon)
89 .unwrap()
90 .replace("<svg", "<svg class=\"icon\"");
91
92 input = input.replace(&format!("{{{{ icon \"{cap_str}\" }}}}",), &icon_text);
93 }
94
95 input
97}