spm_swift_package/cli/
cli_controller.rs1use colored::Colorize;
2use demand::{Confirm, DemandOption, Input, MultiSelect, Select, Spinner, SpinnerStyle};
3
4use crate::core::spm_builder::*;
5use crate::utils::xcode;
6
7pub struct Cli;
9
10impl Cli {
11 pub async fn execute_flow() -> Result<(), String> {
13 let project_name = Self::project_name_input()?;
14 let file_selected = Self::multiselect_files()?;
15 let platform_selected = Self::select_platform()?;
16 let test_framework = Self::select_test_framework()?;
17
18 Self::loading().await?;
19
20 SpmBuilder::create(
21 &project_name,
22 &file_selected,
23 &[platform_selected],
24 test_framework,
25 )?;
26
27 Self::confirm_open_xcode(project_name)?;
28
29 Ok(())
30 }
31
32 fn project_name_input() -> Result<String, String> {
37 let validation_empty = |s: &str| {
38 if s.is_empty() {
39 Err("Library name cannot be empty")
40 } else {
41 Ok(())
42 }
43 };
44
45 let input = Input::new("Library name")
46 .placeholder("Enter the library name")
47 .prompt("Library: ")
48 .validation(validation_empty);
49
50 input.run().map_err(|e| {
51 if e.kind() == std::io::ErrorKind::Interrupted {
52 "Operation interrupted by user".to_string()
53 } else {
54 format!("Error getting library name: {}", e)
55 }
56 })
57 }
58
59 fn multiselect_options(
62 prompt: &str,
63 description: &str,
64 options: &[&'static str],
65 ) -> Result<Vec<&'static str>, String> {
66 loop {
67 let mut multiselect = MultiSelect::new(prompt)
68 .description(description)
69 .filterable(true);
70
71 for &option in options {
72 multiselect = multiselect.option(DemandOption::new(option));
73 }
74
75 let result = match multiselect.run() {
76 Ok(selection) => selection,
77 Err(e) => {
78 if e.kind() == std::io::ErrorKind::Interrupted {
79 return Err("Operation interrupted by user".to_string());
80 } else {
81 return Err(format!("Error selecting options: {}", e));
82 }
83 }
84 };
85
86 let selected: Vec<&str> = result
87 .iter()
88 .filter(|opt| !opt.is_empty())
89 .copied()
90 .collect();
91
92 if selected.is_empty() {
93 println!(
94 "{}",
95 "You need to choose at least one option to continue".yellow()
96 );
97
98 continue;
99 }
100
101 return Ok(selected);
102 }
103 }
104
105 fn multiselect_files() -> Result<Vec<&'static str>, String> {
107 Self::multiselect_options(
108 "Add files",
109 "Do you want to add some of these files?",
110 &["Changelog", "Swift Package Index", "Readme", "SwiftLint"],
111 )
112 }
113
114 fn select_platform() -> Result<&'static str, String> {
117 let mut select = Select::new("Choose platform")
118 .description("Which platform do you want to choose?")
119 .filterable(true);
120
121 for option in ["iOS", "macOS", "tvOS", "watchOS", "visionOS"].iter() {
122 select = select.option(DemandOption::new(*option));
123 }
124
125 select.run().map_err(|e| {
126 if e.kind() == std::io::ErrorKind::Interrupted {
127 "Operation interrupted by user".to_string()
128 } else {
129 format!("Error selecting platform: {}", e)
130 }
131 })
132 }
133
134 fn select_test_framework() -> Result<&'static str, String> {
136 let mut select = Select::new("Choose test framework")
137 .description("Which test framework do you want to use?")
138 .filterable(true);
139
140 for option in ["XCTest", "Swift Testing"].iter() {
141 select = select.option(DemandOption::new(*option));
142 }
143
144 select.run().map_err(|e| {
145 if e.kind() == std::io::ErrorKind::Interrupted {
146 "Operation interrupted by user".to_string()
147 } else {
148 format!("Error selecting test framework: {}", e)
149 }
150 })
151 }
152
153 async fn loading() -> Result<(), String> {
156 Spinner::new("Building the Package...")
157 .style(&SpinnerStyle::line())
158 .run(|_| {
159 std::thread::sleep(std::time::Duration::from_secs(5));
160 })
161 .map_err(|_| "Error running spinner".to_string())
162 }
163
164 fn confirm_open_xcode(project_name: String) -> Result<(), String> {
167 let is_yes = Confirm::new("Do you want to open the package in Xcode?")
168 .affirmative("Yes")
169 .negative("No")
170 .run()
171 .map_err(|e| {
172 if e.kind() == std::io::ErrorKind::Interrupted {
173 "Operation interrupted by user".to_string()
174 } else {
175 "Error running confirm".to_string()
176 }
177 })?;
178
179 if is_yes {
180 xcode::open_xcode(&project_name)?;
181 }
182
183 Ok(())
184 }
185}