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