use colored::Colorize;
use demand::{Confirm, DemandOption, Input, MultiSelect, Select, Spinner, SpinnerStyle};
use crate::core::spm_builder::*;
use crate::utils::xcode;
pub struct Cli;
impl Cli {
pub async fn execute_flow() -> Result<(), String> {
let project_name = Self::project_name_input()?;
let file_selected = Self::multiselect_files()?;
let platform_selected = Self::select_platform()?;
let test_framework = Self::select_test_framework()?;
Self::loading().await?;
SpmBuilder::create(
&project_name,
&file_selected,
&[platform_selected],
test_framework,
)?;
Self::confirm_open_xcode(project_name)?;
Ok(())
}
fn project_name_input() -> Result<String, String> {
let validation_empty = |s: &str| {
if s.is_empty() {
Err("Library name cannot be empty")
} else {
Ok(())
}
};
let input = Input::new("Library name")
.placeholder("Enter the library name")
.prompt("Library: ")
.validation(validation_empty);
input.run().map_err(|e| {
if e.kind() == std::io::ErrorKind::Interrupted {
"Operation interrupted by user".to_string()
} else {
format!("Error getting library name: {}", e)
}
})
}
fn multiselect_options(
prompt: &str,
description: &str,
options: &[&'static str],
) -> Result<Vec<&'static str>, String> {
loop {
let mut multiselect = MultiSelect::new(prompt)
.description(description)
.filterable(true);
for &option in options {
multiselect = multiselect.option(DemandOption::new(option));
}
let result = match multiselect.run() {
Ok(selection) => selection,
Err(e) => {
if e.kind() == std::io::ErrorKind::Interrupted {
return Err("Operation interrupted by user".to_string());
} else {
return Err(format!("Error selecting options: {}", e));
}
}
};
let selected: Vec<&str> = result
.iter()
.filter(|opt| !opt.is_empty())
.copied()
.collect();
if selected.is_empty() {
println!(
"{}",
"You need to choose at least one option to continue".yellow()
);
continue;
}
return Ok(selected);
}
}
fn multiselect_files() -> Result<Vec<&'static str>, String> {
Self::multiselect_options(
"Add files",
"Do you want to add some of these files?",
&["Changelog", "Swift Package Index", "Readme", "SwiftLint"],
)
}
fn select_platform() -> Result<&'static str, String> {
let mut select = Select::new("Choose platform")
.description("Which platform do you want to choose?")
.filterable(true);
for option in ["iOS", "macOS", "tvOS", "watchOS", "visionOS"].iter() {
select = select.option(DemandOption::new(*option));
}
select.run().map_err(|e| {
if e.kind() == std::io::ErrorKind::Interrupted {
"Operation interrupted by user".to_string()
} else {
format!("Error selecting platform: {}", e)
}
})
}
fn select_test_framework() -> Result<&'static str, String> {
let mut select = Select::new("Choose test framework")
.description("Which test framework do you want to use?")
.filterable(true);
for option in ["XCTest", "Swift Testing"].iter() {
select = select.option(DemandOption::new(*option));
}
select.run().map_err(|e| {
if e.kind() == std::io::ErrorKind::Interrupted {
"Operation interrupted by user".to_string()
} else {
format!("Error selecting test framework: {}", e)
}
})
}
async fn loading() -> Result<(), String> {
Spinner::new("Building the Package...")
.style(&SpinnerStyle::line())
.run(|_| {
std::thread::sleep(std::time::Duration::from_secs(5));
})
.map_err(|_| "Error running spinner".to_string())
}
fn confirm_open_xcode(project_name: String) -> Result<(), String> {
let is_yes = Confirm::new("Do you want to open the package in Xcode?")
.affirmative("Yes")
.negative("No")
.run()
.map_err(|e| {
if e.kind() == std::io::ErrorKind::Interrupted {
"Operation interrupted by user".to_string()
} else {
"Error running confirm".to_string()
}
})?;
if is_yes {
xcode::open_xcode(&project_name)?;
}
Ok(())
}
}