use anyhow::Result;
use requestty::{Answer, Question};
use std::collections::BTreeMap;
use requestty_ui::backend::{Size, TestBackend};
use requestty_ui::events::{KeyEvent, TestEvents};
use serde_derive::{Deserialize, Serialize};
use std::vec::IntoIter;
fn default<T: Default + PartialEq>(t: &T) -> bool {
*t == Default::default()
}
#[doc(hidden)]
pub type VarBag = BTreeMap<String, String>;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum ActionHook {
#[default]
#[serde(rename = "after")]
After,
#[serde(rename = "before")]
Before,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Action {
pub name: String,
#[serde(default)]
pub interaction: Option<Interaction>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub run: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "default")]
pub ignore_exit: bool,
#[serde(default)]
#[serde(skip_serializing_if = "default")]
pub break_if_cancel: bool,
#[serde(default)]
#[serde(skip_serializing_if = "default")]
pub capture: bool,
#[serde(default)]
#[serde(skip_serializing_if = "default")]
pub hook: ActionHook,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ActionResult {
pub name: String,
pub run: Option<RunResult>,
pub response: Response,
}
#[allow(missing_docs)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RunResult {
pub script: String,
pub code: i32,
pub out: String,
pub err: String,
}
#[allow(missing_docs)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InteractionKind {
#[serde(rename = "confirm")]
Confirm,
#[serde(rename = "input")]
Input,
#[serde(rename = "select")]
Select,
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Response {
Text(String),
Cancel,
None,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Interaction {
pub kind: InteractionKind,
pub prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub out: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Vec<String>>,
}
impl Interaction {
fn update_varbag(&self, input: &str, varbag: Option<&mut VarBag>) {
varbag.map(|bag| {
self.out
.as_ref()
.map(|out| bag.insert(out.to_string(), input.to_string()))
});
}
pub fn play(
&self,
varbag: Option<&mut VarBag>,
events: Option<&mut TestEvents<IntoIter<KeyEvent>>>,
) -> Result<Response> {
let question = self.to_question();
let answer = if let Some(events) = events {
let mut backend = TestBackend::new(Size::from((50, 20)));
requestty::prompt_one_with(question, &mut backend, events)
} else {
requestty::prompt_one(question)
}?;
Ok(match answer {
Answer::String(input) => {
self.update_varbag(&input, varbag);
Response::Text(input)
}
Answer::ListItem(selected) => {
self.update_varbag(&selected.text, varbag);
Response::Text(selected.text)
}
Answer::Bool(confirmed) if confirmed => {
let as_string = "true".to_string();
self.update_varbag(&as_string, varbag);
Response::Text(as_string)
}
_ => {
Response::Cancel
}
})
}
pub fn to_question(&self) -> Question<'_> {
match self.kind {
InteractionKind::Input => Question::input("question")
.message(self.prompt.clone())
.build(),
InteractionKind::Select => Question::select("question")
.message(self.prompt.clone())
.choices(self.options.clone().unwrap_or_default())
.build(),
InteractionKind::Confirm => Question::confirm("question")
.message(self.prompt.clone())
.build(),
}
}
}