systemprompt_cli/
interactive.rs1use crate::CliConfig;
2use anyhow::{anyhow, Result};
3use dialoguer::theme::ColorfulTheme;
4use dialoguer::{Confirm, Select};
5
6pub fn require_confirmation(
7 message: &str,
8 skip_confirmation: bool,
9 config: &CliConfig,
10) -> Result<()> {
11 if skip_confirmation {
12 return Ok(());
13 }
14
15 if !config.is_interactive() {
16 return Err(anyhow!("--yes is required in non-interactive mode"));
17 }
18
19 let confirmed = Confirm::with_theme(&ColorfulTheme::default())
20 .with_prompt(message)
21 .default(false)
22 .interact()?;
23
24 if confirmed {
25 Ok(())
26 } else {
27 Err(anyhow!("Operation cancelled"))
28 }
29}
30
31pub fn require_confirmation_default_yes(
32 message: &str,
33 skip_confirmation: bool,
34 config: &CliConfig,
35) -> Result<()> {
36 if skip_confirmation {
37 return Ok(());
38 }
39
40 if !config.is_interactive() {
41 return Err(anyhow!("--yes is required in non-interactive mode"));
42 }
43
44 let confirmed = Confirm::with_theme(&ColorfulTheme::default())
45 .with_prompt(message)
46 .default(true)
47 .interact()?;
48
49 if confirmed {
50 Ok(())
51 } else {
52 Err(anyhow!("Operation cancelled"))
53 }
54}
55
56pub fn resolve_required<T, F>(
57 value: Option<T>,
58 flag_name: &str,
59 config: &CliConfig,
60 prompt_fn: F,
61) -> Result<T>
62where
63 F: FnOnce() -> Result<T>,
64{
65 match value {
66 Some(v) => Ok(v),
67 None if config.is_interactive() => prompt_fn(),
68 None => Err(anyhow!(
69 "--{} is required in non-interactive mode",
70 flag_name
71 )),
72 }
73}
74
75pub fn select_from_list<T: ToString + Clone>(
76 prompt: &str,
77 items: &[T],
78 flag_name: &str,
79 config: &CliConfig,
80) -> Result<T> {
81 if items.is_empty() {
82 return Err(anyhow!("No items available for selection"));
83 }
84
85 if !config.is_interactive() {
86 return Err(anyhow!(
87 "--{} is required in non-interactive mode",
88 flag_name
89 ));
90 }
91
92 let display: Vec<String> = items.iter().map(ToString::to_string).collect();
93
94 let idx = Select::with_theme(&ColorfulTheme::default())
95 .with_prompt(prompt)
96 .items(&display)
97 .default(0)
98 .interact()?;
99
100 Ok(items[idx].clone())
101}
102
103pub fn select_index(prompt: &str, items: &[&str], config: &CliConfig) -> Result<Option<usize>> {
104 if !config.is_interactive() {
105 return Ok(None);
106 }
107
108 let idx = Select::with_theme(&ColorfulTheme::default())
109 .with_prompt(prompt)
110 .items(items)
111 .default(0)
112 .interact()?;
113
114 Ok(Some(idx))
115}
116
117pub fn prompt_input(prompt: &str, flag_name: &str, config: &CliConfig) -> Result<String> {
118 if !config.is_interactive() {
119 return Err(anyhow!(
120 "--{} is required in non-interactive mode",
121 flag_name
122 ));
123 }
124
125 let input = dialoguer::Input::<String>::with_theme(&ColorfulTheme::default())
126 .with_prompt(prompt)
127 .interact_text()?;
128
129 Ok(input)
130}
131
132pub fn prompt_input_with_default(
133 prompt: &str,
134 default: &str,
135 config: &CliConfig,
136) -> Result<String> {
137 if !config.is_interactive() {
138 return Ok(default.to_string());
139 }
140
141 let input = dialoguer::Input::<String>::with_theme(&ColorfulTheme::default())
142 .with_prompt(prompt)
143 .default(default.to_string())
144 .interact_text()?;
145
146 Ok(input)
147}
148
149pub fn confirm_optional(message: &str, default: bool, config: &CliConfig) -> Result<bool> {
150 if !config.is_interactive() {
151 return Ok(default);
152 }
153
154 let confirmed = Confirm::with_theme(&ColorfulTheme::default())
155 .with_prompt(message)
156 .default(default)
157 .interact()?;
158
159 Ok(confirmed)
160}