spm_swift_package/presentation/
cli_controller.rs

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