use log::{error, info};
use rust_embed::Embed;
use std::fs;
use std::fs::File;
use std::io::{Error, Write};
use std::path::Path;
use std::sync::LazyLock;
use tera::Tera;
#[derive(Embed, Debug)]
#[folder = "$CARGO_MANIFEST_DIR/example/static/"]
pub struct Static;
#[derive(Embed, Debug)]
#[folder = "$CARGO_MANIFEST_DIR/example/templates/"]
pub struct Templates;
#[derive(Embed, Debug)]
#[folder = "$CARGO_MANIFEST_DIR/example/theme_template/"]
pub struct ThemeTemplate;
#[derive(Embed, Debug)]
#[folder = "$CARGO_MANIFEST_DIR/example/shortcodes/"]
pub struct Shortcodes;
#[derive(Embed, Debug)]
#[folder = "$CARGO_MANIFEST_DIR/.agents/"]
pub struct AgentSkills;
pub static EMBEDDED_SHORTCODES: LazyLock<Vec<(String, Vec<u8>)>> = LazyLock::new(|| {
let mut files: Vec<(String, Vec<u8>)> = Vec::new();
for name in Shortcodes::iter() {
let shortcode = Shortcodes::get(name.as_ref())
.expect("Failed to get embedded shortcode - this is a build-time error");
let file_data = shortcode.data;
files.push((name.clone().to_string(), file_data.clone().to_vec()));
}
files
});
pub static EMBEDDED_TERA: LazyLock<Tera> = LazyLock::new(|| {
let mut tera = Tera::default();
tera.autoescape_on(vec![]);
for name in Templates::iter() {
let template = Templates::get(name.as_ref())
.expect("Failed to get embedded template - this is a build-time error");
let template_str = std::str::from_utf8(template.data.as_ref())
.expect("Embedded template contains invalid UTF-8 - this is a build-time error");
tera.add_raw_template(&name, template_str)
.expect("Failed to add embedded template to Tera - this is a build-time error");
}
tera
});
pub static EMBEDDED_AGENT_SKILLS: LazyLock<Vec<(String, Vec<u8>)>> = LazyLock::new(|| {
let mut files: Vec<(String, Vec<u8>)> = Vec::new();
for name in AgentSkills::iter() {
let file = AgentSkills::get(name.as_ref())
.expect("Failed to get embedded agent skill file - this is a build-time error");
let file_data = file.data;
files.push((name.clone().to_string(), file_data.clone().to_vec()));
}
files
});
pub static EMBEDDED_STATIC: LazyLock<Vec<(String, Vec<u8>)>> = LazyLock::new(|| {
let mut files: Vec<(String, Vec<u8>)> = Vec::new();
for name in Static::iter() {
let static_file = Static::get(name.as_ref())
.expect("Failed to get embedded static file - this is a build-time error");
let file_data = static_file.data;
files.push((name.clone().to_string(), file_data.clone().to_vec()));
}
files
});
pub fn generate_static(static_folder: &Path) {
if let Err(e) = fs::create_dir_all(static_folder) {
error!("Unable to create static directory: {e:?}");
return;
}
for (name, file_data) in EMBEDDED_STATIC.iter() {
let file_path = static_folder.join(name); if let Some(parent) = file_path.parent() {
if let Err(e) = fs::create_dir_all(parent) {
error!("Unable to create directory: {e:?}");
return;
}
}
match write_bytes_to_file(file_path.as_path(), file_data) {
Ok(()) => info!("Generated {}", &file_path.display()),
Err(e) => error!("Error writing file: {e:?}"),
}
}
}
pub fn get_skill_content() -> Option<String> {
AgentSkills::get("skills/marmite/SKILL.md").and_then(|file| {
std::str::from_utf8(file.data.as_ref())
.ok()
.map(String::from)
})
}
pub fn install_skills_to_agents(target_folder: &Path) {
install_skills_to_dir(&target_folder.join(".agents"));
}
pub fn install_skills_to_claude(target_folder: &Path) {
install_skills_to_dir(&target_folder.join(".claude"));
}
fn install_skills_to_dir(base_dir: &Path) {
if let Err(e) = fs::create_dir_all(base_dir) {
error!("Unable to create directory: {e:?}");
return;
}
for (name, file_data) in EMBEDDED_AGENT_SKILLS.iter() {
let file_path = base_dir.join(name);
if let Some(parent) = file_path.parent() {
if let Err(e) = fs::create_dir_all(parent) {
error!("Unable to create directory: {e:?}");
return;
}
}
match write_bytes_to_file(file_path.as_path(), file_data) {
Ok(()) => info!("Generated {}", &file_path.display()),
Err(e) => error!("Error writing file: {e:?}"),
}
}
}
fn write_bytes_to_file(filename: &Path, data: &[u8]) -> Result<(), Error> {
let mut file = File::create(filename)?;
file.write_all(data)?;
Ok(())
}
#[cfg(test)]
#[path = "tests/embedded.rs"]
mod tests;