spm_swift_package/presentation/
cli_controller.rs

1use colored::Colorize;
2use demand::{DemandOption, Input, MultiSelect, Spinner, SpinnerStyle};
3use std::process::Command;
4
5use crate::domain::usecase::usecase::*;
6
7pub struct CliController;
8
9impl CliController {
10	pub async fn execute_flow() -> Result<(), String> {
11		let project_name = Self::project_name_input()?;
12		let file_selected = Self::multiselect_files()?;
13		let platform_selected = Self::multiselect_platform()?;
14
15		Self::loading().await?;
16		SpmUseCase::execute(&project_name, file_selected, platform_selected).await?;
17		Self::command_open_xcode(&project_name)?;
18		Ok(())
19	}
20
21	// Internal functions
22
23	fn project_name_input() -> Result<String, String> {
24		let validation_empty = |s: &str| {
25			if s.is_empty() {
26				Err("Library name cannot be empty")
27			} else {
28				Ok(())
29			}
30		};
31
32		let input = Input::new("Library name")
33			.placeholder("Enter the library name")
34			.prompt("Library: ")
35			.validation(validation_empty);
36
37		input.run().map_err(|e| {
38			if e.kind() == std::io::ErrorKind::Interrupted {
39				"Operation interrupted by user.".to_string()
40			} else {
41				format!("Error getting library name: {}", e)
42			}
43		})
44	}
45
46	fn multiselect_options(
47		prompt: &str,
48		description: &str,
49		options: &[&'static str],
50	) -> Result<Vec<&'static str>, String> {
51		loop {
52			let mut multiselect = MultiSelect::new(prompt)
53				.description(description)
54				.filterable(true);
55
56			for &option in options {
57				multiselect = multiselect.option(DemandOption::new(option));
58			}
59
60			let result = match multiselect.run() {
61				Ok(selection) => selection,
62				Err(e) => {
63					if e.kind() == std::io::ErrorKind::Interrupted {
64						return Err("Operation interrupted by user.".to_string());
65					} else {
66						return Err(format!("Error selecting options: {}", e));
67					}
68				}
69			};
70
71			let selected: Vec<&str> = result
72				.iter()
73				.filter(|opt| !opt.is_empty())
74				.copied()
75				.collect();
76
77			if selected.is_empty() {
78				println!(
79					"{}",
80					"You need to choose at least one option to continue".yellow()
81				);
82				continue;
83			}
84
85			return Ok(selected);
86		}
87	}
88
89	fn multiselect_files() -> Result<Vec<&'static str>, String> {
90		Self::multiselect_options(
91			"Add files",
92			"Do you want to add some of these files?",
93			&[
94				"Changelog",
95				"Swift Package Index",
96				"Readme",
97				"SwiftLint with mise",
98			],
99		)
100	}
101
102	fn multiselect_platform() -> Result<Vec<&'static str>, String> {
103		Self::multiselect_options(
104			"Choose platform",
105			"Which platform do you want to choose?",
106			&["iOS", "macOS", "tvOS", "watchOS", "visionOS"],
107		)
108	}
109
110	async fn loading() -> Result<(), String> {
111		Spinner::new("Building the Package...")
112			.style(&SpinnerStyle::line())
113			.run(|_| {
114				std::thread::sleep(std::time::Duration::from_secs(5));
115			})
116			.map_err(|_| "Error running spinner".to_string())
117	}
118
119	fn command_open_xcode(project_name: &str) -> Result<(), String> {
120		let command = format!("cd {} && open Package.swift", project_name);
121		let mut child = Command::new("sh")
122			.arg("-c")
123			.arg(&command)
124			.spawn()
125			.map_err(|e| format!("Failed to open Xcode: {}", e))?;
126
127		child
128			.wait()
129			.map_err(|e| format!("Failed to wait for Xcode: {}", e))?;
130		Ok(())
131	}
132}