1use core::fmt;2use std::borrow::Cow;3use std::future::Future;4use std::result;56pub mod dbus;7pub mod rofi;89#[derive(thiserror::Error, Debug)]10pub enum Error {11 #[error("user has cancelled input")]12 Cancel,13 #[error("input error: {0}")]14 InputError(String),15}1617pub type Result<T, E = Error> = result::Result<T, E>;1819#[cfg_attr(feature = "dbus", derive(zbus::zvariant::Type))]20#[derive(serde::Serialize, serde::Deserialize, Clone)]21pub struct Source(pub Cow<'static, str>);22impl fmt::Display for Source {23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {24 write!(f, "<u>{}</u>", self.0)25 }26}2728pub trait Prompter {29 fn prompt_radio(30 &self,31 prompt: &str,32 description: &str,33 source: &[Source],34 ) -> impl Future<Output = Result<bool>> + Send {35 let fut = self.prompt_enum(prompt, description, &["No", "Yes"], source);36 async { fut.await.map(|v| v == 1) }37 }38 fn prompt_enum(39 &self,40 prompt: &str,41 description: &str,42 variants: &[&str],43 source: &[Source],44 ) -> impl Future<Output = Result<u32>> + Send;45 fn prompt_text(46 &self,47 echo: bool,48 prompt: &str,49 description: &str,50 source: &[Source],51 ) -> impl Future<Output = Result<String>> + Send;52 fn display_text(53 &self,54 error: bool,55 description: &str,56 source: &[Source],57 ) -> impl Future<Output = Result<()>> + Send;58}59pub trait BlockingPrompter {60 fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {61 self.prompt_enum(prompt, description, &["No", "Yes"], source)62 .map(|v| v == 1)63 }64 fn prompt_enum(65 &self,66 prompt: &str,67 description: &str,68 variants: &[&str],69 source: &[Source],70 ) -> Result<u32>;71 fn prompt_text(72 &self,73 echo: bool,74 prompt: &str,75 description: &str,76 source: &[Source],77 ) -> Result<String>;78 fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;79}8081pub struct PrependSourcePrompter<P> {82 pub prompter: P,83 pub source: Vec<Source>,84 pub description: String,85}86impl<P> PrependSourcePrompter<P> {87 fn source(&self, input: &[Source]) -> Vec<Source> {88 let mut out = self.source.clone();89 out.extend(input.iter().cloned());90 out91 }92 fn description(&self, input: &str) -> String {93 if self.description.is_empty() {94 input.to_owned()95 } else if input.is_empty() {96 self.description.to_owned()97 } else {98 format!("{input}\n\n{}", self.description)99 }100 }101}102impl<P> Prompter for PrependSourcePrompter<P>103where104 P: Prompter + Sync,105{106 async fn prompt_radio(107 &self,108 prompt: &str,109 description: &str,110 source: &[Source],111 ) -> Result<bool> {112 self.prompter113 .prompt_radio(prompt, &self.description(description), &self.source(source))114 .await115 }116117 async fn prompt_enum(118 &self,119 prompt: &str,120 description: &str,121 variants: &[&str],122 source: &[Source],123 ) -> Result<u32> {124 self.prompter125 .prompt_enum(126 prompt,127 dbg!(&self.description(description)),128 variants,129 &self.source(source),130 )131 .await132 }133134 async fn prompt_text(135 &self,136 echo: bool,137 prompt: &str,138 description: &str,139 source: &[Source],140 ) -> Result<String> {141 self.prompter142 .prompt_text(143 echo,144 prompt,145 &self.description(description),146 &self.source(source),147 )148 .await149 }150151 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {152 self.prompter153 .display_text(error, &self.description(description), &self.source(source))154 .await155 }156}