changepacks_cli/
prompter.rs1use anyhow::Result;
2use changepacks_core::Project;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
7#[error("")]
8pub struct UserCancelled;
9
10pub trait Prompter: Send + Sync {
12 fn multi_select<'a>(
13 &self,
14 message: &str,
15 options: Vec<&'a Project>,
16 defaults: Vec<usize>,
17 ) -> Result<Vec<&'a Project>>;
18
19 fn confirm(&self, message: &str) -> Result<bool>;
20
21 fn text(&self, message: &str) -> Result<String>;
22}
23
24#[derive(Default)]
26pub struct InquirePrompter;
27
28impl Prompter for InquirePrompter {
29 fn multi_select<'a>(
30 &self,
31 message: &str,
32 options: Vec<&'a Project>,
33 defaults: Vec<usize>,
34 ) -> Result<Vec<&'a Project>> {
35 let mut selector = inquire::MultiSelect::new(message, options);
36 selector.page_size = 15;
37 selector.default = Some(defaults);
38 selector.scorer = &|_input, option, _string_value, _idx| -> Option<i64> {
39 if option.is_changed() {
40 Some(100)
41 } else {
42 Some(0)
43 }
44 };
45 selector.formatter = &|option| {
46 option
47 .iter()
48 .map(|o| format!("{}", o.value))
49 .collect::<Vec<_>>()
50 .join("\n")
51 };
52 match selector.prompt() {
53 Ok(v) => Ok(v),
54 Err(inquire::InquireError::OperationCanceled)
55 | Err(inquire::InquireError::OperationInterrupted) => Err(UserCancelled.into()),
56 Err(e) => Err(e.into()),
57 }
58 }
59
60 fn confirm(&self, message: &str) -> Result<bool> {
61 match inquire::Confirm::new(message).prompt() {
62 Ok(v) => Ok(v),
63 Err(inquire::InquireError::OperationCanceled)
64 | Err(inquire::InquireError::OperationInterrupted) => Err(UserCancelled.into()),
65 Err(e) => Err(e.into()),
66 }
67 }
68
69 fn text(&self, message: &str) -> Result<String> {
70 match inquire::Text::new(message).prompt() {
71 Ok(v) => Ok(v),
72 Err(inquire::InquireError::OperationCanceled)
73 | Err(inquire::InquireError::OperationInterrupted) => Err(UserCancelled.into()),
74 Err(e) => Err(e.into()),
75 }
76 }
77}
78
79pub struct MockPrompter {
81 pub select_all: bool,
82 pub confirm_value: bool,
83 pub text_value: String,
84}
85
86impl Default for MockPrompter {
87 fn default() -> Self {
88 Self {
89 select_all: true,
90 confirm_value: true,
91 text_value: "test note".to_string(),
92 }
93 }
94}
95
96impl Prompter for MockPrompter {
97 fn multi_select<'a>(
98 &self,
99 _message: &str,
100 options: Vec<&'a Project>,
101 _defaults: Vec<usize>,
102 ) -> Result<Vec<&'a Project>> {
103 if self.select_all {
104 Ok(options)
105 } else {
106 Ok(vec![])
107 }
108 }
109
110 fn confirm(&self, _message: &str) -> Result<bool> {
111 Ok(self.confirm_value)
112 }
113
114 fn text(&self, _message: &str) -> Result<String> {
115 Ok(self.text_value.clone())
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_mock_prompter_default() {
125 let prompter = MockPrompter::default();
126 assert!(prompter.select_all);
127 assert!(prompter.confirm_value);
128 assert_eq!(prompter.text_value, "test note");
129 }
130
131 #[test]
132 fn test_mock_prompter_confirm() {
133 let prompter = MockPrompter {
134 confirm_value: false,
135 ..Default::default()
136 };
137 assert!(!prompter.confirm("test").unwrap());
138 }
139
140 #[test]
141 fn test_mock_prompter_text() {
142 let prompter = MockPrompter {
143 text_value: "custom".to_string(),
144 ..Default::default()
145 };
146 assert_eq!(prompter.text("test").unwrap(), "custom");
147 }
148
149 #[test]
150 fn test_mock_prompter_multi_select_empty() {
151 let prompter = MockPrompter {
152 select_all: false,
153 ..Default::default()
154 };
155 let options: Vec<&Project> = vec![];
156 let result = prompter.multi_select("test", options, vec![]).unwrap();
157 assert!(result.is_empty());
158 }
159}