espforge_lib/cli/interactive/
dialoguer.rs1use super::{Prompter, catalog::ExampleCatalog};
2use anyhow::{Result, bail};
3use dialoguer::{Input, Select, theme::ColorfulTheme};
4use espforge_dialogue::{Asker, EnumAsker};
5
6pub struct DialoguerPrompter {
7 theme: ColorfulTheme,
8}
9
10#[derive(Debug, Clone, EnumAsker)]
11#[asker(prompt = "Select Target Chip")]
12pub enum Chip {
13 #[asker(label = "esp32c3")]
14 Esp32c3,
15 #[asker(label = "esp32c6")]
16 Esp32c6,
17 #[asker(label = "esp32s3")]
18 Esp32s3,
19 #[asker(label = "esp32s2")]
20 Esp32s2,
21 #[asker(label = "esp32")]
22 Esp32,
23 #[asker(label = "esp32h2")]
24 Esp32h2,
25}
26
27impl std::fmt::Display for Chip {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 let s = match self {
30 Chip::Esp32c3 => "esp32c3",
31 Chip::Esp32c6 => "esp32c6",
32 Chip::Esp32s3 => "esp32s3",
33 Chip::Esp32s2 => "esp32s2",
34 Chip::Esp32 => "esp32",
35 Chip::Esp32h2 => "esp32h2",
36 };
37 write!(f, "{}", s)
38 }
39}
40
41#[derive(Asker)]
42struct OverwritePrompt {
43 #[confirm]
44 confirm: bool,
45}
46
47impl DialoguerPrompter {
48 pub fn new() -> Self {
49 Self {
50 theme: ColorfulTheme::default(),
51 }
52 }
53
54 pub fn select_from_list<T: ToString + std::fmt::Display>(
55 &self,
56 prompt: impl Into<String>,
57 items: &[T],
58 ) -> Result<usize> {
59 let selection = Select::with_theme(&self.theme)
60 .with_prompt(prompt.into())
61 .default(0)
62 .items(items)
63 .interact()?;
64 Ok(selection)
65 }
66
67 pub fn ensure_not_empty<T>(items: &[T], context: &str) -> Result<()> {
68 if items.is_empty() {
69 bail!("No {} found to select from", context);
70 }
71 Ok(())
72 }
73}
74
75impl Default for DialoguerPrompter {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl Prompter for DialoguerPrompter {
82 fn select_example(&self) -> Result<String> {
83 let examples_by_category = ExampleCatalog::load();
84 let category = examples_by_category.select_category(self)?;
85 let example = examples_by_category.select_example_from_category(&category, self)?;
86 Ok(example)
87 }
88
89 fn prompt_project_name(&self, default: &str) -> Result<String> {
90 let input = Input::with_theme(&self.theme)
91 .with_prompt("Project Name (Destination Folder)")
92 .default(default.to_string())
93 .interact_text()?;
94 Ok(input)
95 }
96
97 fn select_chip(&self) -> Result<String> {
98 let chip = Chip::ask();
99 Ok(chip.to_string())
100 }
101
102 fn confirm_overwrite(&self, dir_name: &str) -> Result<bool> {
103 let prompt_text = format!("Directory '{}' already exists. Overwrite?", dir_name);
104 let prompt = OverwritePrompt::asker().confirm(prompt_text).finish();
105 Ok(prompt.confirm)
106 }
107}