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