interactive_actions/
data.rs1use anyhow::Result;
5use requestty::{Answer, Question};
6use std::collections::BTreeMap;
7
8use requestty_ui::backend::{Size, TestBackend};
9use requestty_ui::events::{KeyEvent, TestEvents};
10use serde_derive::{Deserialize, Serialize};
11use std::vec::IntoIter;
12
13fn default<T: Default + PartialEq>(t: &T) -> bool {
14 *t == Default::default()
15}
16
17#[doc(hidden)]
18pub type VarBag = BTreeMap<String, String>;
19
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
24pub enum ActionHook {
25 #[default]
27 #[serde(rename = "after")]
28 After,
29
30 #[serde(rename = "before")]
32 Before,
33}
34#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct Action {
42 pub name: String,
44
45 #[serde(default)]
47 pub interaction: Option<Interaction>,
48
49 #[serde(default)]
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub run: Option<String>,
53
54 #[serde(default)]
57 #[serde(skip_serializing_if = "default")]
58 pub ignore_exit: bool,
59
60 #[serde(default)]
62 #[serde(skip_serializing_if = "default")]
63 pub break_if_cancel: bool,
64
65 #[serde(default)]
67 #[serde(skip_serializing_if = "default")]
68 pub capture: bool,
69
70 #[serde(default)]
72 #[serde(skip_serializing_if = "default")]
73 pub hook: ActionHook,
74}
75#[derive(Clone, Debug, Serialize, Deserialize)]
79pub struct ActionResult {
80 pub name: String,
82 pub run: Option<RunResult>,
84 pub response: Response,
86}
87
88#[allow(missing_docs)]
89#[derive(Clone, Debug, Serialize, Deserialize)]
90pub struct RunResult {
91 pub script: String,
92 pub code: i32,
93 pub out: String,
94 pub err: String,
95}
96
97#[allow(missing_docs)]
98#[derive(Clone, Debug, Serialize, Deserialize)]
99pub enum InteractionKind {
100 #[serde(rename = "confirm")]
101 Confirm,
102 #[serde(rename = "input")]
103 Input,
104 #[serde(rename = "select")]
105 Select,
106}
107
108#[allow(missing_docs)]
109#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
110pub enum Response {
111 Text(String),
112 Cancel,
113 None,
114}
115
116#[derive(Clone, Debug, Serialize, Deserialize)]
120pub struct Interaction {
121 pub kind: InteractionKind,
123 pub prompt: String,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub out: Option<String>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub options: Option<Vec<String>>,
133}
134impl Interaction {
135 fn update_varbag(&self, input: &str, varbag: Option<&mut VarBag>) {
136 varbag.map(|bag| {
137 self.out
138 .as_ref()
139 .map(|out| bag.insert(out.to_string(), input.to_string()))
140 });
141 }
142
143 pub fn play(
149 &self,
150 varbag: Option<&mut VarBag>,
151 events: Option<&mut TestEvents<IntoIter<KeyEvent>>>,
152 ) -> Result<Response> {
153 let question = self.to_question();
154 let answer = if let Some(events) = events {
155 let mut backend = TestBackend::new(Size::from((50, 20)));
156 requestty::prompt_one_with(question, &mut backend, events)
157 } else {
158 requestty::prompt_one(question)
159 }?;
160
161 Ok(match answer {
162 Answer::String(input) => {
163 self.update_varbag(&input, varbag);
164
165 Response::Text(input)
166 }
167 Answer::ListItem(selected) => {
168 self.update_varbag(&selected.text, varbag);
169 Response::Text(selected.text)
170 }
171 Answer::Bool(confirmed) if confirmed => {
172 let as_string = "true".to_string();
173 self.update_varbag(&as_string, varbag);
174 Response::Text(as_string)
175 }
176 _ => {
177 Response::Cancel
178 }
180 })
181 }
182
183 pub fn to_question(&self) -> Question<'_> {
185 match self.kind {
186 InteractionKind::Input => Question::input("question")
187 .message(self.prompt.clone())
188 .build(),
189 InteractionKind::Select => Question::select("question")
190 .message(self.prompt.clone())
191 .choices(self.options.clone().unwrap_or_default())
192 .build(),
193 InteractionKind::Confirm => Question::confirm("question")
194 .message(self.prompt.clone())
195 .build(),
196 }
197 }
198}