use std::str::FromStr;
use crate::ComplexArg;
use camino::Utf8PathBuf;
use color_eyre::Result;
use dialoguer::{theme::ColorfulTheme, Input};
use episko_lib::metadata::{BuildSystem, Category, Ide, Language};
const MAX_ROUNDS: i8 = 25;
pub fn directory_prompt(default: Option<Utf8PathBuf>) -> Result<Utf8PathBuf> {
if let Some(dir) = default {
return Ok(dir);
}
text_prompt("Directory", false, default)
}
pub fn title_prompt(default: Option<String>) -> Result<String> {
if let Some(title) = default {
return Ok(title);
}
text_prompt("Title", false, default)
}
pub fn description_prompt(default: Option<String>) -> Result<Option<String>> {
if let Some(description) = default {
return Ok(Some(description));
}
optional_text_prompt("Description", default)
}
pub fn categories_prompt(defaults: &[String]) -> Result<Vec<Category>> {
let mut categories = Vec::with_capacity(defaults.len());
let mut defaults = defaults.iter();
if defaults.len() > 0 {
for default in defaults {
categories.push(Category::from_str(default)?);
}
return Ok(categories);
}
for i in 1..MAX_ROUNDS {
let default = defaults.next().map(ToString::to_string);
let input: String = text_prompt(&format!("Category {i}"), true, default)?;
if input.is_empty() {
break;
}
categories.push(Category::from_str(&input)?);
}
Ok(categories)
}
pub fn languages_prompt(defaults: &[String]) -> Result<Vec<Language>> {
looping_prompt_with_version("Language", defaults)
}
pub fn build_systems_prompt(defaults: &[String]) -> Result<Vec<BuildSystem>> {
looping_prompt_with_version("Build System", defaults)
}
pub fn ide_prompt(default: Option<String>) -> Result<Option<Ide>> {
if let Some(ide) = default {
return Ok(Some(Ide::from_str(&ide)?));
}
optional_text_prompt("Preferred Ide", default)
}
pub fn repository_url_prompt(default: Option<String>) -> Result<Option<String>> {
if let Some(url) = default {
return Ok(Some(url));
}
optional_text_prompt("Repository Url", default)
}
fn text_prompt<T>(prompt: &str, allow_empty: bool, default: Option<T>) -> Result<T>
where
T: Clone + FromStr + std::fmt::Display,
<T as FromStr>::Err: std::fmt::Display,
{
let theme = ColorfulTheme::default();
let mut input = Input::<T>::with_theme(&theme)
.with_prompt(prompt)
.allow_empty(allow_empty);
if let Some(default) = default {
input = input.default(default);
}
Ok(input.interact_text()?)
}
fn optional_text_prompt<T>(prompt: &str, default: Option<String>) -> Result<Option<T>>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
let data: String = text_prompt(prompt, true, default)?;
if data.is_empty() {
Ok(None)
} else {
Ok(Some(T::from_str(&data)?))
}
}
fn looping_prompt_with_version<T>(prompt: &str, defaults: &[String]) -> Result<Vec<T>>
where
T: TryFrom<(String, String)>,
<T as TryFrom<(String, String)>>::Error: std::error::Error + Send + Sync + 'static,
{
let mut data = vec![];
if !defaults.is_empty() {
let mut data: Vec<T> = Vec::with_capacity(defaults.len());
for default in defaults {
data.push(T::try_from(default.clone().parse_tuple()?)?);
}
return Ok(data);
}
for i in 1..MAX_ROUNDS {
let name: String = text_prompt(&format!("{prompt} {i} Name"), true, None)?;
if name.is_empty() {
break;
}
let version: String = text_prompt(&format!("{prompt} {i} Version"), true, None)?;
data.push((name, version).try_into()?);
}
Ok(data)
}