1use core::fmt;2use std::borrow::Cow;3use std::future::Future;4use std::result;56pub mod auto;7pub mod bifrost;8pub mod rofi;910#[derive(thiserror::Error, Debug, serde::Serialize, serde::Deserialize)]11pub enum Error {12 #[error("user has cancelled input")]13 Cancel,14 #[error("input error: {0}")]15 InputError(String),16 #[error("unknown remote error: {0}")]17 Remote(String),18}1920pub type Result<T, E = Error> = result::Result<T, E>;2122#[derive(serde::Serialize, serde::Deserialize, Clone)]23pub struct Source(pub Cow<'static, str>);24impl fmt::Display for Source {25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {26 write!(f, "<u>{}</u>", self.0)27 }28}2930pub trait Prompter: Send + Sync {31 fn prompt_radio(32 &self,33 prompt: &str,34 description: &str,35 source: &[Source],36 ) -> impl Future<Output = Result<bool>> + Send {37 let fut = self.prompt_enum(prompt, description, &["No", "Yes"], source);38 async { fut.await.map(|v| v == 1) }39 }40 fn prompt_enum(41 &self,42 prompt: &str,43 description: &str,44 variants: &[&str],45 source: &[Source],46 ) -> impl Future<Output = Result<u32>> + Send;47 fn prompt_text(48 &self,49 echo: bool,50 prompt: &str,51 description: &str,52 source: &[Source],53 ) -> impl Future<Output = Result<String>> + Send;54 fn display_text(55 &self,56 error: bool,57 description: &str,58 source: &[Source],59 ) -> impl Future<Output = Result<()>> + Send;60}61pub trait BlockingPrompter {62 fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {63 self.prompt_enum(prompt, description, &["No", "Yes"], source)64 .map(|v| v == 1)65 }66 fn prompt_enum(67 &self,68 prompt: &str,69 description: &str,70 variants: &[&str],71 source: &[Source],72 ) -> Result<u32>;73 fn prompt_text(74 &self,75 echo: bool,76 prompt: &str,77 description: &str,78 source: &[Source],79 ) -> Result<String>;80 fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;81}82impl<P> Prompter for &P83where84 P: Prompter,85{86 fn prompt_radio(87 &self,88 prompt: &str,89 description: &str,90 source: &[Source],91 ) -> impl Future<Output = Result<bool>> + Send {92 (*self).prompt_radio(prompt, description, source)93 }9495 fn prompt_enum(96 &self,97 prompt: &str,98 description: &str,99 variants: &[&str],100 source: &[Source],101 ) -> impl Future<Output = Result<u32>> + Send {102 (*self).prompt_enum(prompt, description, variants, source)103 }104105 fn prompt_text(106 &self,107 echo: bool,108 prompt: &str,109 description: &str,110 source: &[Source],111 ) -> impl Future<Output = Result<String>> + Send {112 (*self).prompt_text(echo, prompt, description, source)113 }114115 fn display_text(116 &self,117 error: bool,118 description: &str,119 source: &[Source],120 ) -> impl Future<Output = Result<()>> + Send {121 (*self).display_text(error, description, source)122 }123}124125pub struct PrependSourcePrompter<P> {126 pub prompter: P,127 pub source: Vec<Source>,128 pub description: String,129}130impl<P> PrependSourcePrompter<P> {131 fn source(&self, input: &[Source]) -> Vec<Source> {132 let mut out = self.source.clone();133 out.extend(input.iter().cloned());134 out135 }136 fn description(&self, input: &str) -> String {137 if self.description.is_empty() {138 input.to_owned()139 } else if input.is_empty() {140 self.description.to_owned()141 } else {142 format!("{input}\n\n{}", self.description)143 }144 }145}146impl<P> Prompter for PrependSourcePrompter<P>147where148 P: Prompter + Sync,149{150 async fn prompt_radio(151 &self,152 prompt: &str,153 description: &str,154 source: &[Source],155 ) -> Result<bool> {156 self.prompter157 .prompt_radio(prompt, &self.description(description), &self.source(source))158 .await159 }160161 async fn prompt_enum(162 &self,163 prompt: &str,164 description: &str,165 variants: &[&str],166 source: &[Source],167 ) -> Result<u32> {168 self.prompter169 .prompt_enum(170 prompt,171 &self.description(description),172 variants,173 &self.source(source),174 )175 .await176 }177178 async fn prompt_text(179 &self,180 echo: bool,181 prompt: &str,182 description: &str,183 source: &[Source],184 ) -> Result<String> {185 self.prompter186 .prompt_text(187 echo,188 prompt,189 &self.description(description),190 &self.source(source),191 )192 .await193 }194195 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {196 self.prompter197 .display_text(error, &self.description(description), &self.source(source))198 .await199 }200}