pijul_interaction/
lib.rs

1//! Wrapper functions around `dialoguer` to support Pijul's different modes of interactivity.
2
3mod input;
4mod progress;
5
6use input::{DefaultPrompt, PasswordPrompt, SelectionPrompt, TextPrompt};
7use progress::{ProgressBarTrait, SpinnerTrait};
8use std::sync::OnceLock;
9
10// TODO: these should be replaced with a more sophisticated localization system
11pub const DOWNLOAD_MESSAGE: &str = "Downloading changes";
12pub const APPLY_MESSAGE: &str = "Applying changes";
13pub const UPLOAD_MESSAGE: &str = "Uploading changes";
14pub const COMPLETE_MESSAGE: &str = "Completing changes";
15pub const OUTPUT_MESSAGE: &str = "Outputting repository";
16
17/// Global state for setting interactivity. Should be set to `Option::None`
18/// if no interactivity is possible, for example running Pijul with `--no-prompt`.
19static INTERACTIVE_CONTEXT: OnceLock<InteractiveContext> = OnceLock::new();
20
21/// Get the interactive context. If not set, returns an error.
22pub fn get_context() -> Result<InteractiveContext, InteractionError> {
23    if let Some(context) = INTERACTIVE_CONTEXT.get() {
24        Ok(*context)
25    } else {
26        Err(InteractionError::NoContext)
27    }
28}
29
30/// Set the interactive context, panicking if already set.
31pub fn set_context(value: InteractiveContext) {
32    // There probably isn't any reason for changing contexts at runtime
33    INTERACTIVE_CONTEXT
34        .set(value)
35        .expect("Interactive context is already set!");
36}
37
38/// The different kinds of available prompts
39#[derive(Clone, Copy, Debug)]
40#[non_exhaustive]
41pub enum PromptType {
42    Confirm,
43    Input,
44    Select,
45    Password,
46}
47
48impl core::fmt::Display for PromptType {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        let name = match *self {
51            Self::Confirm => "confirm",
52            Self::Input => "input",
53            Self::Select => "fuzzy selection",
54            Self::Password => "password",
55        };
56
57        write!(f, "{name}")
58    }
59}
60
61/// Errors that can occur while attempting to interact with the user
62#[derive(thiserror::Error, Debug)]
63#[non_exhaustive]
64pub enum InteractionError {
65    #[error("mode of interactivity not set")]
66    NoContext,
67    #[error("unable to provide interactivity in this context, and no valid default value for {0} prompt `{1}`")]
68    NotInteractive(PromptType, String),
69    #[error("I/O error while interacting with terminal")]
70    IO(#[from] std::io::Error),
71}
72
73/// Different contexts for interacting with Pijul, for example terminal or web browser
74#[derive(Clone, Copy, Debug)]
75#[non_exhaustive]
76pub enum InteractiveContext {
77    Terminal,
78    NotInteractive,
79}
80
81/// A prompt that asks the user to select yes or no
82pub struct Confirm(Box<dyn DefaultPrompt<bool>>);
83
84/// A prompt that asks the user to choose from a list of items.
85pub struct Select(Box<dyn SelectionPrompt<usize>>);
86
87/// A prompt that asks the user to enter text input
88pub struct Input(Box<dyn TextPrompt<String>>);
89
90/// A prompt that asks the user to enter a password
91pub struct Password(Box<dyn PasswordPrompt<String>>);
92
93/// A progress bar that is controlled by code
94pub struct ProgressBar(Box<dyn ProgressBarTrait>);
95
96/// An animated progress bar to indicate activity
97pub struct Spinner(Box<dyn SpinnerTrait>);