use anyhow::Context as _;
use async_trait::async_trait;
use dialoguer::{Input, Password, Select};
use slumber_core::render::{Prompter, SelectOption};
use slumber_template::Value;
use slumber_util::ResultTracedAnyhow;
use tokio::sync::Mutex;
use tracing::warn;
static CONSOLE_LOCK: Mutex<()> = Mutex::const_new(());
#[derive(Debug)]
pub struct ConsolePrompter;
impl ConsolePrompter {
async fn spawn_prompt<T, F>(&self, f: F) -> Option<T>
where
T: 'static + Send,
F: 'static + FnOnce() -> anyhow::Result<T> + Send,
{
let guard = CONSOLE_LOCK.lock().await;
let result = tokio::task::spawn_blocking(f)
.await
.context("Prompt panicked")
.flatten()
.traced();
drop(guard);
result.ok()
}
}
#[async_trait(?Send)]
impl Prompter for ConsolePrompter {
async fn prompt_text(
&self,
message: String,
default: Option<String>,
sensitive: bool,
) -> Option<String> {
self.spawn_prompt(move || {
if sensitive {
if default.is_some() {
warn!(
"Default value not supported for sensitive CLI prompts"
);
}
Password::new()
.with_prompt(message)
.allow_empty_password(true)
.interact()
} else {
let mut input =
Input::new().with_prompt(message).allow_empty(true);
if let Some(default) = default {
input = input.default(default);
}
input.interact()
}
.context("Error reading value from prompt")
})
.await
}
async fn prompt_select(
&self,
message: String,
mut options: Vec<SelectOption>,
) -> Option<Value> {
self.spawn_prompt(move || {
let index = Select::new()
.with_prompt(message)
.items(&options)
.default(0)
.interact()
.context("Error reading value from select")?;
Ok(options.swap_remove(index).value)
})
.await
}
}