use serde::Serialize;
#[derive(Serialize)]
pub struct SearchEntry {
pub title: String,
pub path: String,
pub tags: Vec<String>,
pub ingredients: Vec<String>,
}
pub fn build_search_index(tree: &cooklang_find::RecipeTree) -> Vec<SearchEntry> {
let mut out = Vec::new();
collect(tree, String::new(), &mut out);
out
}
fn collect(tree: &cooklang_find::RecipeTree, prefix: String, out: &mut Vec<SearchEntry>) {
for (name, child) in &tree.children {
if child.children.is_empty() {
let Some(recipe) = child.recipe.as_ref() else {
continue;
};
let stem = recipe
.file_name()
.as_deref()
.map(|f| {
f.trim_end_matches(".cook")
.trim_end_matches(".menu")
.to_string()
})
.unwrap_or_else(|| name.clone());
let sub = if prefix.is_empty() {
stem
} else {
format!("{prefix}/{stem}")
};
let url_path = if recipe.is_menu() {
format!("menu/{sub}.html")
} else {
format!("recipe/{sub}.html")
};
let tags = recipe.tags();
let ingredients = match crate::util::parse_recipe_from_entry(recipe, 1.0) {
Ok(parsed) => parsed
.group_ingredients(crate::util::PARSER.converter())
.into_iter()
.map(|e| e.ingredient.display_name().to_string())
.collect(),
Err(e) => {
tracing::warn!("Skipping ingredients for search index entry {url_path}: {e:#}");
Vec::new()
}
};
out.push(SearchEntry {
title: name.clone(),
path: url_path,
tags,
ingredients,
});
} else {
let sub = if prefix.is_empty() {
name.to_string()
} else {
format!("{prefix}/{name}")
};
collect(child, sub, out);
}
}
}