tetratto-core 17.0.0

The core behind Tetratto
Documentation
use std::{
    collections::HashMap,
    fs::{exists, read_to_string, write},
    sync::LazyLock,
};
use tokio::sync::RwLock;

use pathbufd::PathBufD;

/// A container for all loaded icons.
pub static ICONS: LazyLock<RwLock<HashMap<String, String>>> =
    LazyLock::new(|| RwLock::new(HashMap::new()));

/// Pull an icon given its name and insert it into [`ICONS`].
pub async fn pull_icon(icon: &str, icons_dir: &str) {
    let writer = &mut ICONS.write().await;

    let icon_url = format!(
        "https://raw.githubusercontent.com/lucide-icons/lucide/refs/heads/main/icons/{icon}.svg"
    );

    let file_path = PathBufD::current().extend(&[icons_dir, &format!("{icon}.svg")]);

    if exists(&file_path).unwrap_or(false) {
        writer.insert(icon.to_string(), read_to_string(&file_path).unwrap());
        return;
    }

    println!("download icon: {icon}");
    let svg = reqwest::get(icon_url)
        .await
        .unwrap()
        .text()
        .await
        .unwrap()
        .replace("\n", "");

    write(&file_path, &svg).unwrap();
    writer.insert(icon.to_string(), svg);
}

/// Read a string and pull all icons found within it.
pub async fn pull_icons(mut input: String, icon_dir: &str) -> String {
    // icon (with class)
    let icon_with_class =
        regex::Regex::new("(\\{\\{)\\s*(icon)\\s*(.*?)\\s*c\\((.*?)\\)\\s*(\\}\\})").unwrap();

    for cap in icon_with_class.captures_iter(&input.clone()) {
        let cap_str = &cap.get(3).unwrap().as_str().replace("\"", "");
        let icon = &(if cap_str.contains(" }}") {
            cap_str.split(" }}").next().unwrap().to_string()
        } else {
            cap_str.to_string()
        });

        pull_icon(icon, icon_dir).await;

        let reader = ICONS.read().await;
        let icon_text = reader.get(icon).unwrap().replace(
            "<svg",
            &format!("<svg class=\"icon {}\"", cap.get(4).unwrap().as_str()),
        );

        input = input.replace(
            &format!(
                "{{{{ icon \"{cap_str}\" c({}) }}}}",
                cap.get(4).unwrap().as_str()
            ),
            &icon_text,
        );
    }

    // icon (without class)
    let icon_without_class = regex::Regex::new("(\\{\\{)\\s*(icon)\\s*(.*?)\\s*(\\}\\})").unwrap();

    for cap in icon_without_class.captures_iter(&input.clone()) {
        let cap_str = &cap.get(3).unwrap().as_str().replace("\"", "");
        let icon = &(if cap_str.contains(" }}") {
            cap_str.split(" }}").next().unwrap().to_string()
        } else {
            cap_str.to_string()
        });

        pull_icon(icon, icon_dir).await;

        let reader = ICONS.read().await;
        let icon_text = reader
            .get(icon)
            .unwrap()
            .replace("<svg", "<svg class=\"icon\"");

        input = input.replace(&format!("{{{{ icon \"{cap_str}\" }}}}",), &icon_text);
    }

    // ...
    input
}