1use core::fmt;2use std::borrow::Cow;3use std::future::Future;4use std::result;56pub mod auto;7pub mod bifrost;8pub mod dbus;9pub mod rofi;1011#[derive(thiserror::Error, Debug, serde::Serialize, serde::Deserialize)]12pub enum Error {13 #[error("user has cancelled input")]14 Cancel,15 #[error("input error: {0}")]16 InputError(String),17 #[error("unknown remote error: {0}")]18 Remote(String),19}2021pub type Result<T, E = Error> = result::Result<T, E>;2223#[cfg_attr(feature = "dbus", derive(zbus::zvariant::Type))]24#[derive(serde::Serialize, serde::Deserialize, Clone)]25pub struct Source(pub Cow<'static, str>);26impl fmt::Display for Source {27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {28 write!(f, "<u>{}</u>", self.0)29 }30}3132pub trait Prompter: Send + Sync {33 fn prompt_radio(34 &self,35 prompt: &str,36 description: &str,37 source: &[Source],38 ) -> impl Future<Output = Result<bool>> + Send {39 let fut = self.prompt_enum(prompt, description, &["No", "Yes"], source);40 async { fut.await.map(|v| v == 1) }41 }42 fn prompt_enum(43 &self,44 prompt: &str,45 description: &str,46 variants: &[&str],47 source: &[Source],48 ) -> impl Future<Output = Result<u32>> + Send;49 fn prompt_text(50 &self,51 echo: bool,52 prompt: &str,53 description: &str,54 source: &[Source],55 ) -> impl Future<Output = Result<String>> + Send;56 fn display_text(57 &self,58 error: bool,59 description: &str,60 source: &[Source],61 ) -> impl Future<Output = Result<()>> + Send;62}63pub trait BlockingPrompter {64 fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {65 self.prompt_enum(prompt, description, &["No", "Yes"], source)66 .map(|v| v == 1)67 }68 fn prompt_enum(69 &self,70 prompt: &str,71 description: &str,72 variants: &[&str],73 source: &[Source],74 ) -> Result<u32>;75 fn prompt_text(76 &self,77 echo: bool,78 prompt: &str,79 description: &str,80 source: &[Source],81 ) -> Result<String>;82 fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;83}84impl<P> Prompter for &P85where86 P: Prompter,87{88 fn prompt_radio(89 &self,90 prompt: &str,91 description: &str,92 source: &[Source],93 ) -> impl Future<Output = Result<bool>> + Send {94 (*self).prompt_radio(prompt, description, source)95 }9697 fn prompt_enum(98 &self,99 prompt: &str,100 description: &str,101 variants: &[&str],102 source: &[Source],103 ) -> impl Future<Output = Result<u32>> + Send {104 (*self).prompt_enum(prompt, description, variants, source)105 }106107 fn prompt_text(108 &self,109 echo: bool,110 prompt: &str,111 description: &str,112 source: &[Source],113 ) -> impl Future<Output = Result<String>> + Send {114 (*self).prompt_text(echo, prompt, description, source)115 }116117 fn display_text(118 &self,119 error: bool,120 description: &str,121 source: &[Source],122 ) -> impl Future<Output = Result<()>> + Send {123 (*self).display_text(error, description, source)124 }125}126127pub struct PrependSourcePrompter<P> {128 pub prompter: P,129 pub source: Vec<Source>,130 pub description: String,131}132impl<P> PrependSourcePrompter<P> {133 fn source(&self, input: &[Source]) -> Vec<Source> {134 let mut out = self.source.clone();135 out.extend(input.iter().cloned());136 out137 }138 fn description(&self, input: &str) -> String {139 if self.description.is_empty() {140 input.to_owned()141 } else if input.is_empty() {142 self.description.to_owned()143 } else {144 format!("{input}\n\n{}", self.description)145 }146 }147}148impl<P> Prompter for PrependSourcePrompter<P>149where150 P: Prompter + Sync,151{152 async fn prompt_radio(153 &self,154 prompt: &str,155 description: &str,156 source: &[Source],157 ) -> Result<bool> {158 self.prompter159 .prompt_radio(prompt, &self.description(description), &self.source(source))160 .await161 }162163 async fn prompt_enum(164 &self,165 prompt: &str,166 description: &str,167 variants: &[&str],168 source: &[Source],169 ) -> Result<u32> {170 self.prompter171 .prompt_enum(172 prompt,173 &self.description(description),174 variants,175 &self.source(source),176 )177 .await178 }179180 async fn prompt_text(181 &self,182 echo: bool,183 prompt: &str,184 description: &str,185 source: &[Source],186 ) -> Result<String> {187 self.prompter188 .prompt_text(189 echo,190 prompt,191 &self.description(description),192 &self.source(source),193 )194 .await195 }196197 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {198 self.prompter199 .display_text(error, &self.description(description), &self.source(source))200 .await201 }202}