use anyhow::Context;
use colored::Colorize;
use dialoguer::{theme::ColorfulTheme, Select};
use wasmer_backend_api::WasmerClient;
use wasmer_config::package::NamedPackageIdent;
pub fn prompt_for_ident(message: &str, default: Option<&str>) -> Result<String, anyhow::Error> {
loop {
let theme = ColorfulTheme::default();
let diag = dialoguer::Input::with_theme(&theme)
.with_prompt(message)
.with_initial_text(default.unwrap_or_default());
let raw: String = diag.interact_text()?;
let val = raw.trim();
if !val.is_empty() {
break Ok(val.to_string());
}
}
}
pub fn prompt_for_app_ident(message: &str, default: Option<&str>) -> Result<String, anyhow::Error> {
loop {
let theme = ColorfulTheme::default();
let diag = dialoguer::Input::with_theme(&theme)
.with_prompt(message)
.with_initial_text(default.unwrap_or_default());
let raw: String = diag.interact_text()?;
let val = raw.trim();
if val.is_empty() {
continue;
}
if val.contains('.') || val.contains(' ') {
eprintln!("The name must not contain dots or spaces. Please try again.");
continue;
}
return Ok(val.to_string());
}
}
pub fn prompt_for_package_ident(
message: &str,
default: Option<&str>,
) -> Result<NamedPackageIdent, anyhow::Error> {
loop {
let theme = ColorfulTheme::default();
let raw: String = dialoguer::Input::with_theme(&theme)
.with_prompt(message)
.with_initial_text(default.unwrap_or_default())
.interact_text()
.context("could not read user input")?;
match raw.parse::<NamedPackageIdent>() {
Ok(p) => break Ok(p),
Err(err) => {
eprintln!("invalid package name: {err}");
}
}
}
}
pub enum PackageCheckMode {
MustExist,
#[allow(dead_code)]
MustNotExist,
}
pub fn prompt_for_package_version(
message: &str,
default: Option<&str>,
) -> Result<semver::Version, anyhow::Error> {
loop {
let theme = ColorfulTheme::default();
let raw: String = dialoguer::Input::with_theme(&theme)
.with_prompt(message)
.with_initial_text(default.unwrap_or_default())
.interact_text()
.context("could not read user input")?;
match raw.parse::<semver::Version>() {
Ok(p) => break Ok(p),
Err(err) => {
eprintln!("invalid package version: {err}");
}
}
}
}
pub async fn prompt_for_package(
message: &str,
default: Option<&str>,
check: Option<PackageCheckMode>,
client: Option<&WasmerClient>,
) -> Result<
(
NamedPackageIdent,
Option<wasmer_backend_api::types::Package>,
),
anyhow::Error,
> {
loop {
let ident = prompt_for_package_ident(message, default)?;
if let Some(check) = &check {
let api = client.expect("Check mode specified, but no API provided");
let pkg = if let Some(v) = ident.version_opt() {
wasmer_backend_api::query::get_package_version(
api,
ident.full_name(),
v.to_string(),
)
.await
.context("could not query backend for package")?
.map(|p| p.package)
} else {
wasmer_backend_api::query::get_package(api, ident.to_string())
.await
.context("could not query backend for package")?
};
match check {
PackageCheckMode::MustExist => {
if let Some(pkg) = pkg {
let mut ident = ident;
if let Some(v) = &pkg.last_version {
ident.tag =
Some(wasmer_config::package::Tag::VersionReq(v.version.parse()?));
}
break Ok((ident, Some(pkg)));
} else {
eprintln!("Package '{ident}' does not exist");
}
}
PackageCheckMode::MustNotExist => {
if pkg.is_none() {
break Ok((ident, None));
} else {
eprintln!("Package '{ident}' already exists");
}
}
}
} else {
break Ok((ident, None));
}
}
}
pub fn prompt_for_namespace(
message: &str,
default: Option<&str>,
user: Option<&wasmer_backend_api::types::UserWithNamespaces>,
) -> Result<String, anyhow::Error> {
if let Some(user) = user {
let namespaces = user
.namespaces
.edges
.clone()
.into_iter()
.flatten()
.filter_map(|e| e.node)
.collect::<Vec<_>>();
let labels = [user.username.clone()]
.into_iter()
.chain(namespaces.iter().map(|ns| ns.global_name.clone()))
.collect::<Vec<_>>();
let selection_index = Select::with_theme(&ColorfulTheme::default())
.with_prompt(message)
.default(0)
.items(&labels)
.interact()
.context("could not read user input")?;
Ok(labels[selection_index].clone())
} else {
loop {
let theme = ColorfulTheme::default();
let value = dialoguer::Input::<String>::with_theme(&theme)
.with_prompt(message)
.with_initial_text(default.map(|x| x.trim().to_string()).unwrap_or_default())
.interact_text()
.context("could not read user input")?
.trim()
.to_string();
if !value.is_empty() {
break Ok(value);
}
}
}
}
#[allow(dead_code)]
pub async fn prompt_new_app_name(
message: &str,
default: Option<&str>,
namespace: &str,
api: Option<&WasmerClient>,
) -> Result<String, anyhow::Error> {
loop {
let ident = prompt_for_ident(message, default)?;
if ident.len() < 5 {
eprintln!(
"{}: Name is too short. It must be longer than 5 characters.",
"WARN".bold().yellow()
)
} else if let Some(api) = &api {
let app = wasmer_backend_api::query::get_app(api, namespace.to_string(), ident.clone())
.await?;
eprint!("Checking name availability... ");
if app.is_some() {
eprintln!(
"{}",
format!(
"app {} already exists in namespace {}",
ident.bold(),
namespace.bold()
)
.yellow()
);
} else {
eprintln!("{}", "available!".bold().green());
break Ok(ident);
}
}
}
}
#[allow(dead_code)]
pub async fn prompt_new_app_alias(
message: &str,
default: Option<&str>,
api: Option<&WasmerClient>,
) -> Result<String, anyhow::Error> {
loop {
let ident = prompt_for_ident(message, default)?;
if let Some(api) = &api {
let app = wasmer_backend_api::query::get_app_by_alias(api, ident.clone()).await?;
eprintln!("Checking name availability...");
if app.is_some() {
eprintln!(
"{}: alias '{}' already exists - pick a different name",
"WARN:".yellow(),
ident
);
} else {
break Ok(ident);
}
}
}
}