spm_swift_package/presentation/
cli_controller.rs1use 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 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}