use std::io::{self, IsTerminal};
use dialoguer::{Confirm, Input, Select, theme::ColorfulTheme};
use directories::ProjectDirs;
use cotis_cli::routines::{installed_routine_names, installed_versions_for_routine};
pub fn require_interactive() -> Result<(), String> {
if io::stdin().is_terminal() && io::stdout().is_terminal() {
Ok(())
} else {
Err(
"A TTY is required to prompt for missing arguments; re-run with full arguments (see --help)."
.to_string(),
)
}
}
pub fn prompt_install_spec() -> Result<String, String> {
require_interactive()?;
let theme = ColorfulTheme::default();
let kind = Select::with_theme(&theme)
.with_prompt("Install from")
.items([
"Local Cargo project (path:...)",
"crates.io (crate:...)",
"GitHub release (gh:...)",
])
.default(0)
.interact()
.map_err(|e| e.to_string())?;
match kind {
0 => {
let path: String = Input::with_theme(&theme)
.with_prompt("Path to the Cargo project directory")
.interact_text()
.map_err(|e| e.to_string())?;
let path = path.trim();
if path.is_empty() {
return Err("Path must not be empty.".to_string());
}
Ok(format!("path:{path}"))
}
1 => {
let name: String = Input::with_theme(&theme)
.with_prompt("Crate name")
.interact_text()
.map_err(|e| e.to_string())?;
let version: String = Input::with_theme(&theme)
.with_prompt("Crate version")
.interact_text()
.map_err(|e| e.to_string())?;
let name = name.trim();
let version = version.trim();
if name.is_empty() || version.is_empty() {
return Err("Crate name and version are required.".to_string());
}
Ok(format!("crate:{name}@{version}"))
}
2 => {
let repo: String = Input::with_theme(&theme)
.with_prompt("Repository (owner/repo)")
.interact_text()
.map_err(|e| e.to_string())?;
let tag: String = Input::with_theme(&theme)
.with_prompt("Release tag, or \"latest\"")
.interact_text()
.map_err(|e| e.to_string())?;
let repo = repo.trim();
let tag = tag.trim();
if repo.is_empty() || tag.is_empty() {
return Err("owner/repo and tag are required.".to_string());
}
Ok(format!("gh:{repo}@{tag}"))
}
_ => Err("Invalid selection.".to_string()),
}
}
pub fn prompt_optional_alias() -> Result<Option<String>, String> {
require_interactive()?;
let theme = ColorfulTheme::default();
let use_alias = Confirm::with_theme(&theme)
.with_prompt("Install under a different routine name (alias)?")
.default(false)
.interact()
.map_err(|e| e.to_string())?;
if !use_alias {
return Ok(None);
}
let alias: String = Input::with_theme(&theme)
.with_prompt("Routine name alias")
.interact_text()
.map_err(|e| e.to_string())?;
let alias = alias.trim().to_string();
if alias.is_empty() {
Ok(None)
} else {
Ok(Some(alias))
}
}
pub fn prompt_routine(proj_dirs: &ProjectDirs, prompt: &str) -> Result<String, String> {
require_interactive()?;
let names = installed_routine_names(proj_dirs);
if names.is_empty() {
return Err("No routines are installed yet. Use `cotis-cli install` first.".to_string());
}
let theme = ColorfulTheme::default();
let items: Vec<&str> = names.iter().map(String::as_str).collect();
let i = Select::with_theme(&theme)
.with_prompt(prompt)
.items(&items)
.default(0)
.interact()
.map_err(|e| e.to_string())?;
Ok(names[i].clone())
}
pub fn prompt_routine_spec(proj_dirs: &ProjectDirs, prompt: &str) -> Result<String, String> {
require_interactive()?;
let name = prompt_routine(proj_dirs, prompt)?;
let versions = installed_versions_for_routine(proj_dirs, &name);
if versions.is_empty() {
return Err(format!("Routine `{name}` has no version directories."));
}
if versions.len() == 1 {
return Ok(format!("{}@{}", name, versions[0]));
}
let theme = ColorfulTheme::default();
let labels: Vec<String> = versions.iter().map(|v| format!("{name}@{v}")).collect();
let items: Vec<&str> = labels.iter().map(String::as_str).collect();
let i = Select::with_theme(&theme)
.with_prompt("Which version?")
.items(&items)
.default(labels.len().saturating_sub(1))
.interact()
.map_err(|e| e.to_string())?;
Ok(labels[i].clone())
}