mirror of
https://github.com/danbulant/lucide
synced 2026-06-18 05:51:20 +00:00
284 lines
7.5 KiB
Rust
284 lines
7.5 KiB
Rust
use std::{collections::HashSet, error::Error, fs, path::Path};
|
|
|
|
use convert_case::{Case, Casing};
|
|
use log::info;
|
|
use regex::Regex;
|
|
use tempfile::tempdir;
|
|
|
|
use crate::{
|
|
UPSTREAM_GIT_URL,
|
|
framework::Framework,
|
|
frameworks::{dioxus::Dioxus, leptos::Leptos, yew::Yew},
|
|
metadata::Metadata,
|
|
repository::git_checkout,
|
|
};
|
|
|
|
pub fn generate(
|
|
upstream_repository_rev: &str,
|
|
repository_path: &Path,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let frameworks: [Box<dyn Framework>; 3] = [Box::new(Dioxus), Box::new(Leptos), Box::new(Yew)];
|
|
|
|
let upstream_repository_path = tempdir()?;
|
|
let repository_icons_path = upstream_repository_path.path().join("icons");
|
|
|
|
git_checkout(
|
|
&upstream_repository_path,
|
|
UPSTREAM_GIT_URL,
|
|
upstream_repository_rev,
|
|
)?;
|
|
|
|
info!("Generating icons.");
|
|
|
|
let mut paths = vec![];
|
|
for entry in fs::read_dir(repository_icons_path)? {
|
|
let path = entry?.path();
|
|
|
|
if !path.is_file() || path.extension().is_none_or(|extension| extension != "svg") {
|
|
continue;
|
|
}
|
|
|
|
let file_path = path.clone();
|
|
let file_stem = file_path
|
|
.file_stem()
|
|
.expect("File stem should exist.")
|
|
.to_string_lossy()
|
|
.to_string();
|
|
|
|
paths.push((file_path, file_stem));
|
|
}
|
|
|
|
paths.sort_by_key(|(_, file_stem)| file_stem.clone());
|
|
|
|
let mut modules = vec![];
|
|
let mut component_names = vec![];
|
|
let mut metadatas = vec![];
|
|
|
|
for (path, file_stem) in paths {
|
|
let file_contents = fs::read_to_string(&path)?;
|
|
|
|
let mut metadata_path = path.clone();
|
|
metadata_path.set_extension("json");
|
|
let metadata = serde_json::from_str::<Metadata>(&fs::read_to_string(&metadata_path)?)?;
|
|
metadatas.push(metadata.clone());
|
|
|
|
let module = file_stem.to_case(Case::Snake);
|
|
modules.push(module.clone());
|
|
|
|
let component_name = file_stem.to_case(Case::Pascal);
|
|
component_names.push(component_name.clone());
|
|
|
|
info!("{module} - {component_name}");
|
|
|
|
for framework in &frameworks {
|
|
generate_icon(
|
|
repository_path,
|
|
&**framework,
|
|
module.clone(),
|
|
component_name.clone(),
|
|
file_contents.clone(),
|
|
)?;
|
|
}
|
|
}
|
|
|
|
for framework in &frameworks {
|
|
generate_lib(repository_path, &**framework, &modules, &metadatas)?;
|
|
generate_example(repository_path, &**framework, &component_names)?;
|
|
generate_features(repository_path, &**framework, &metadatas)?;
|
|
|
|
framework.format(
|
|
format!("lucide-{}", framework.name()),
|
|
repository_path,
|
|
repository_path
|
|
.join("packages")
|
|
.join(framework.name())
|
|
.join("src"),
|
|
)?;
|
|
|
|
framework.format(
|
|
format!("lucide-{}-book", framework.name()),
|
|
repository_path,
|
|
repository_path
|
|
.join("book-examples")
|
|
.join(framework.name())
|
|
.join("src"),
|
|
)?;
|
|
}
|
|
|
|
update_repository_rev(repository_path, upstream_repository_rev)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_icon(
|
|
path: &Path,
|
|
framework: &dyn Framework,
|
|
module: String,
|
|
component_name: String,
|
|
input: String,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let output_path = path
|
|
.join("packages")
|
|
.join(framework.name())
|
|
.join("src")
|
|
.join(format!("{module}.rs"));
|
|
|
|
let output_tokens = framework.generate(component_name, input)?;
|
|
let output = prettyplease::unparse(&syn::parse2(output_tokens)?);
|
|
|
|
fs::write(output_path, output)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_example(
|
|
path: &Path,
|
|
framework: &dyn Framework,
|
|
component_names: &[String],
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let output_path = path
|
|
.join("book-examples")
|
|
.join(framework.name())
|
|
.join("src")
|
|
.join("icons.rs");
|
|
|
|
let output_tokens = framework.generate_example(component_names)?;
|
|
let output = prettyplease::unparse(&syn::parse2(output_tokens)?);
|
|
|
|
fs::write(output_path, output)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_lib(
|
|
path: &Path,
|
|
framework: &dyn Framework,
|
|
modules: &[String],
|
|
metadatas: &[Metadata],
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let attributes = metadatas
|
|
.iter()
|
|
.map(|metadata| {
|
|
let conditions = metadata
|
|
.categories
|
|
.iter()
|
|
.map(|category| format!("feature = \"{category}\""))
|
|
.collect::<Vec<_>>();
|
|
|
|
if conditions.len() == 1 {
|
|
format!("#[cfg({})]", conditions.join(", "))
|
|
} else {
|
|
format!("#[cfg(any({}))]", conditions.join(", "))
|
|
}
|
|
})
|
|
.collect::<Vec<String>>();
|
|
|
|
let output_path = path
|
|
.join("packages")
|
|
.join(framework.name())
|
|
.join("src")
|
|
.join("lib.rs");
|
|
|
|
let output_modules = modules
|
|
.iter()
|
|
.zip(&attributes)
|
|
.map(|(module, attribute)| {
|
|
format!("{attribute}\nmod {};", sanitize_identifier(module.as_str()))
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
|
|
let output_uses = modules
|
|
.iter()
|
|
.zip(&attributes)
|
|
.map(|(module, attribute)| {
|
|
format!(
|
|
"{attribute}\npub use {}::*;",
|
|
sanitize_identifier(module.as_str())
|
|
)
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
|
|
let output = format!(
|
|
"{}{}\n\n{}\n",
|
|
match framework.lib_header() {
|
|
Some(header) => format!("{header}\n\n"),
|
|
None => "".into(),
|
|
},
|
|
output_modules,
|
|
output_uses
|
|
);
|
|
|
|
fs::write(output_path, output)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_features(
|
|
path: &Path,
|
|
framework: &dyn Framework,
|
|
metadatas: &[Metadata],
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let file_path = path
|
|
.join("packages")
|
|
.join(framework.name())
|
|
.join("Cargo.toml");
|
|
|
|
let mut file_contents = fs::read_to_string(&file_path)?;
|
|
|
|
let index = file_contents.find("[features]");
|
|
if let Some(index) = index {
|
|
file_contents = file_contents[0..index].to_string();
|
|
}
|
|
file_contents = file_contents.trim_end_matches("\n").to_string();
|
|
|
|
let mut features = metadatas
|
|
.iter()
|
|
.flat_map(|metadata| metadata.categories.clone())
|
|
.collect::<HashSet<String>>()
|
|
.into_iter()
|
|
.collect::<Vec<String>>();
|
|
features.sort();
|
|
|
|
let output_all_features = features
|
|
.iter()
|
|
.map(|feature| format!(" \"{feature}\","))
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
let output_features = features
|
|
.into_iter()
|
|
.map(|feature| format!("{feature} = []"))
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
|
|
let output = format!(
|
|
"{file_contents}\n\n[features]\ndefault = []\n{output_features}\nall-icons = [\n{output_all_features}\n]\n"
|
|
);
|
|
|
|
fs::write(&file_path, output)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn update_repository_rev(path: &Path, repository_rev: &str) -> Result<(), Box<dyn Error>> {
|
|
let file_path = path.join("scripts").join("src").join("lib.rs");
|
|
|
|
let file_contents = fs::read_to_string(&file_path)?;
|
|
|
|
let regex = Regex::new(r"\d+\.\d+\.\d+")?;
|
|
let output = regex.replace(&file_contents, repository_rev).to_string();
|
|
|
|
fs::write(&file_path, output)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn sanitize_identifier(identifier: &str) -> &str {
|
|
match identifier {
|
|
"box" => "r#box",
|
|
"move" => "r#move",
|
|
"type" => "r#type",
|
|
identifier => identifier,
|
|
}
|
|
}
|