espforge_lib/cli/interactive/
catalog.rs

1use anyhow::Result;
2use std::collections::HashMap;
3
4use crate::cli::interactive::DialoguerPrompter;
5
6pub struct ExampleCatalog {
7    categories: HashMap<String, Vec<String>>,
8}
9
10impl ExampleCatalog {
11    pub fn load() -> Self {
12        use espforge_examples::EXAMPLES_DIR;
13
14        let categories = EXAMPLES_DIR
15            .dirs()
16            .filter_map(|entry| {
17                let category = Self::extract_name(entry.path().file_name());
18                let examples: Vec<String> = entry
19                    .dirs()
20                    .map(|ex| Self::extract_name(ex.path().file_name()))
21                    .collect();
22
23                if examples.is_empty() {
24                    None
25                } else {
26                    Some((category, examples))
27                }
28            })
29            .collect();
30
31        Self { categories }
32    }
33
34    fn extract_name(file_name: Option<&std::ffi::OsStr>) -> String {
35        file_name.unwrap_or_default().to_string_lossy().to_string()
36    }
37
38    pub fn select_category(&self, prompter: &DialoguerPrompter) -> Result<String> {
39        let mut category_names: Vec<_> = self.categories.keys().cloned().collect();
40        category_names.sort();
41
42        DialoguerPrompter::ensure_not_empty(&category_names, "example categories")?;
43
44        let selection = prompter.select_from_list("Select a Category", &category_names)?;
45        Ok(category_names[selection].clone())
46    }
47
48    pub fn select_example_from_category(
49        &self,
50        category: &str,
51        prompter: &DialoguerPrompter,
52    ) -> Result<String> {
53        let examples = self
54            .categories
55            .get(category)
56            .ok_or_else(|| anyhow::anyhow!("Category '{}' not found", category))?;
57
58        DialoguerPrompter::ensure_not_empty(examples, &format!("examples in '{}'", category))?;
59
60        let selection =
61            prompter.select_from_list(format!("Select example from {}", category), examples)?;
62
63        Ok(examples[selection].clone())
64    }
65
66    pub fn categories(&self) -> Vec<&String> {
67        let mut cats: Vec<_> = self.categories.keys().collect();
68        cats.sort();
69        cats
70    }
71
72    pub fn examples_in_category(&self, category: &str) -> Option<&Vec<String>> {
73        self.categories.get(category)
74    }
75}
76
77pub struct ChipCatalog;
78
79impl ChipCatalog {
80    pub fn available_chips() -> Vec<&'static str> {
81        vec![
82            "esp32c3", "esp32c6", "esp32s3", "esp32s2", "esp32", "esp32h2",
83        ]
84    }
85
86    pub fn is_valid_chip(chip: &str) -> bool {
87        Self::available_chips().contains(&chip)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_chip_validation() {
97        assert!(ChipCatalog::is_valid_chip("esp32c3"));
98        assert!(ChipCatalog::is_valid_chip("esp32"));
99        assert!(!ChipCatalog::is_valid_chip("esp32c9"));
100        assert!(!ChipCatalog::is_valid_chip("invalid"));
101    }
102}