use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Recipe {
pub recipe: String,
pub version: u32,
pub backend: String,
#[serde(flatten)]
pub extra: HashMap<String, serde_yaml::Value>,
}
fn recipe_dirs() -> Vec<PathBuf> {
let mut out = Vec::new();
if let Ok(home) = std::env::var("HANZO_HOME") {
let p = PathBuf::from(home).join("recipes");
if p.is_dir() {
out.push(p);
}
}
if let Ok(p) = std::env::var("HANZO_BRAIN_RECIPES") {
let pb = PathBuf::from(p);
if pb.is_dir() {
out.push(pb);
}
}
out
}
pub fn list_recipes() -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for dir in recipe_dirs() {
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) == Some("yaml") {
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
if seen.insert(stem.to_string()) {
out.push(stem.to_string());
}
}
}
}
}
}
out
}
pub fn load_recipe(name: &str) -> anyhow::Result<Recipe> {
for dir in recipe_dirs() {
let path = dir.join(format!("{}.yaml", name));
if path.is_file() {
let raw = std::fs::read_to_string(&path)?;
let recipe: Recipe = serde_yaml::from_str(&raw)?;
return Ok(recipe);
}
}
anyhow::bail!(
"recipe `{}` not found. Set HANZO_HOME or HANZO_BRAIN_RECIPES to a directory containing <name>.yaml files.",
name
)
}
pub fn builtin_email_recipe_yaml() -> &'static str {
include_str!("../../recipes/email.yaml")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_email_recipe_parses() {
let raw = builtin_email_recipe_yaml();
let recipe: Recipe = serde_yaml::from_str(raw).expect("parse");
assert_eq!(recipe.recipe, "email");
assert_eq!(recipe.version, 1);
assert_eq!(recipe.backend, "gmail");
}
}