pijul_interaction/input/
mod.rs

1//! Implement the various prompt types defined in `lib.rs`
2mod non_interactive;
3mod terminal;
4
5use crate::{Confirm, Input, Password, Select};
6use crate::{InteractionError, InteractiveContext, PromptType};
7use dialoguer::theme;
8use duplicate::duplicate_item;
9use lazy_static::lazy_static;
10use non_interactive::PseudoInteractive;
11
12lazy_static! {
13    static ref THEME: Box<dyn theme::Theme + Send + Sync> = {
14        use dialoguer::theme;
15        use pijul_config::{self as config, Choice};
16
17        if let Ok((config, _)) = config::Global::load() {
18            let color_choice = config.colors.unwrap_or_default();
19
20            match color_choice {
21                Choice::Auto | Choice::Always => Box::<theme::ColorfulTheme>::default(),
22                Choice::Never => Box::new(theme::SimpleTheme),
23            }
24        } else {
25            Box::<theme::ColorfulTheme>::default()
26        }
27    };
28}
29
30/// A common interface shared by every prompt type.
31/// May be useful if you wish to abstract over different kinds of prompt.
32pub trait BasePrompt<T> {
33    fn set_prompt(&mut self, prompt: String);
34    fn interact(&mut self) -> Result<T, InteractionError>;
35}
36
37/// A trait for prompts that allow a default selection.
38pub trait DefaultPrompt<T>: BasePrompt<T> {
39    fn set_default(&mut self, value: T);
40}
41
42/// A trait for prompts that may need validation of user input.
43///
44/// This is mostly useful in contexts such as plain-text input or passwords,
45/// rather than on controlled input such as confirmation prompts.
46pub trait ValidationPrompt<T>: BasePrompt<T> {
47    fn allow_empty(&mut self, empty: bool);
48    fn set_validator(&mut self, validator: Box<dyn Fn(&T) -> Result<(), String>>);
49}
50
51/// A trait for prompts that accept a password.
52pub trait PasswordPrompt<T>: ValidationPrompt<T> {
53    fn set_confirmation(&mut self, confirm_prompt: String, mismatch_err: String);
54}
55
56/// A trait for prompts that accept text with a default value.
57/// Notably, this does NOT include passwords.
58pub trait TextPrompt<T>: ValidationPrompt<T> + DefaultPrompt<T> {
59    fn set_inital_text(&mut self, text: String);
60}
61
62/// A trait for prompts where the user may choose from a selection of items.
63pub trait SelectionPrompt<T>: DefaultPrompt<T> {
64    fn add_items(&mut self, items: &[String]);
65}
66
67#[duplicate_item(
68    handler         prompt_type                 return_type;
69    [Confirm]       [PromptType::Confirm]       [bool];
70    [Input]         [PromptType::Input]         [String];
71    [Select]        [PromptType::Select]        [usize];
72    [Password]      [PromptType::Password]      [String];
73)]
74impl handler {
75    /// Create the prompt, returning an error if interactive context is incorrectly set.
76    pub fn new() -> Result<Self, InteractionError> {
77        Ok(Self(match crate::get_context()? {
78            InteractiveContext::Terminal => Box::new(terminal::handler::with_theme(THEME.as_ref())),
79            InteractiveContext::NotInteractive => Box::new(PseudoInteractive::new(prompt_type)),
80        }))
81    }
82
83    /// Set the prompt.
84    pub fn set_prompt(&mut self, prompt: String) {
85        self.0.set_prompt(prompt);
86    }
87
88    /// Builder pattern for [`Self::set_prompt`]
89    pub fn with_prompt<S: ToString>(&mut self, prompt: S) -> &mut Self {
90        self.set_prompt(prompt.to_string());
91        self
92    }
93
94    /// Present the prompt to the user. May return an error if in a non-interactive context,
95    /// or interaction fails for any other reason
96    pub fn interact(&mut self) -> Result<return_type, InteractionError> {
97        self.0.interact()
98    }
99}
100
101#[duplicate_item(
102    handler         return_type;
103    [Confirm]       [bool];
104    [Input]         [String];
105    [Select]   [usize];
106)]
107impl handler {
108    /// Set the default selection. If the user does not input anything, this value will be used instead.
109    pub fn set_default(&mut self, value: return_type) {
110        self.0.set_default(value);
111    }
112
113    /// Builder pattern for [`Self::set_default`]
114    pub fn with_default<I: Into<return_type>>(&mut self, value: I) -> &mut Self {
115        self.set_default(value.into());
116        self
117    }
118}
119
120impl Select {
121    /// Add items to be displayed in the selection prompt.
122    pub fn add_items<S: ToString>(&mut self, items: &[S]) {
123        let string_items: Vec<String> = items.iter().map(ToString::to_string).collect();
124        self.0.add_items(string_items.as_slice());
125    }
126
127    /// Builder pattern for [`Self::add_items`].
128    ///
129    /// NOTE: if this function is called multiple times, it will add ALL items to the builder.
130    pub fn with_items<S: ToString>(&mut self, items: &[S]) -> &mut Self {
131        self.add_items(items);
132        self
133    }
134}
135
136impl Password {
137    /// Ask the user to confirm the password with the provided prompt & error message.
138    pub fn set_confirmation<S: ToString>(&mut self, confirm_prompt: S, mismatch_err: S) {
139        self.0
140            .set_confirmation(confirm_prompt.to_string(), mismatch_err.to_string());
141    }
142
143    /// Builder pattern for [`Self::set_confirmation`]
144    pub fn with_confirmation<S: ToString>(
145        &mut self,
146        confirm_prompt: S,
147        mismatch_err: S,
148    ) -> &mut Self {
149        self.set_confirmation(confirm_prompt, mismatch_err);
150        self
151    }
152}
153
154#[duplicate_item(
155    handler         prompt_type;
156    [Input]         [PromptType::Input];
157    [Password]      [PromptType::Password];
158)]
159impl handler {
160    /// Sets if no input is a valid input. Default: `false`.
161    pub fn set_allow_empty(&mut self, empty: bool) {
162        self.0.allow_empty(empty);
163    }
164
165    /// Builder pattern for [`Self::set_allow_empty`]
166    pub fn with_allow_empty(&mut self, empty: bool) -> &mut Self {
167        self.set_allow_empty(empty);
168        self
169    }
170
171    /// Set a validator to be run on input. If the validator returns [`Ok`], the input will be deemed
172    /// valid. If the validator returns [`Err`], the prompt will display the error message
173    pub fn set_validator<V, E>(&mut self, validator: V)
174    where
175        V: Fn(&String) -> Result<(), E> + 'static,
176        E: ToString,
177    {
178        self.0
179            .set_validator(Box::new(move |input| match validator(input) {
180                Ok(()) => Ok(()),
181                Err(e) => Err(e.to_string()),
182            }));
183    }
184
185    /// Builder pattern for [`Self::set_validator`]
186    pub fn with_validator<V, E>(&mut self, validator: V) -> &mut Self
187    where
188        V: Fn(&String) -> Result<(), E> + 'static,
189        E: ToString,
190    {
191        self.set_validator(validator);
192        self
193    }
194}
195
196impl Input {
197    pub fn set_inital_text<S: ToString>(&mut self, text: S) {
198        self.0.set_inital_text(text.to_string());
199    }
200
201    pub fn with_initial_text<S: ToString>(&mut self, text: S) -> &mut Self {
202        self.set_inital_text(text);
203        self
204    }
205}