1use std::path::Path;
2
3use crate::{
4 cli::amps::AmpsOptions::{self, Exec},
5 dialog,
6 error::GRError,
7 io::{ShellResponse, TaskRunner},
8 remote::ConfigFilePath,
9 shell, Result,
10};
11
12pub fn execute(options: AmpsOptions, config_file: ConfigFilePath) -> Result<()> {
13 match options {
14 Exec(amp_name_args) => {
15 let base_path = config_file.directory();
16 let amps_scripts = base_path.join("amps");
17 if amp_name_args.is_empty() {
18 let runner = shell::BlockingCommand;
19 let amps = list_amps(runner, amps_scripts.to_str().unwrap())?;
20 let amp_script = dialog::fuzzy_select(amps)?;
21 let stream_runner = shell::StreamingCommand;
22 let amp_runner = Amp::new(dialog::prompt_args, &stream_runner);
23 amp_runner.exec_amps(amp_script, base_path)?;
24 return Ok(());
25 }
26 let stream_runner = shell::StreamingCommand;
27 let amp_name_args: Vec<&str> = amp_name_args.split(' ').collect();
28 let amp_name = amp_name_args[0];
29 let amp_path = amps_scripts.join(amp_name);
30 let args = amp_name_args[1..].join(" ");
31 stream_runner.run(vec![&_path.to_str().unwrap(), &args.as_str()])?;
32 Ok(())
33 }
34 _ => {
35 let base_path = config_file.directory();
36 let amps_scripts = base_path.join("amps");
37 let runner = shell::BlockingCommand;
38 let amps = list_amps(runner, amps_scripts.to_str().unwrap())?;
39 for amp in amps {
40 println!("{amp}");
41 }
42 Ok(())
43 }
44 }
45}
46
47fn list_amps(
48 runner: impl TaskRunner<Response = ShellResponse>,
49 amps_path: &str,
50) -> Result<Vec<String>> {
51 let cmd = vec!["ls", amps_path];
52 let response = runner.run(cmd)?;
53 if response.body.is_empty() {
54 return Err(GRError::PreconditionNotMet(format!(
55 "No amps are available in {amps_path}. Please check \
56 https://github.com/jordilin/gitar-amps for installation instructions"
57 ))
58 .into());
59 }
60 let amps: Vec<String> = response.body.split('\n').map(|s| s.to_string()).collect();
61 Ok(amps)
62}
63
64enum AmpPrompts {
65 Args,
66 Help,
67 Exit,
68}
69
70impl From<&String> for AmpPrompts {
71 fn from(s: &String) -> Self {
72 match s.as_str() {
73 "-h" | "--help" | "help" | "h" => AmpPrompts::Help,
74 "exit" | "cancel" | "quit" | "q" => AmpPrompts::Exit,
75 _ => AmpPrompts::Args,
76 }
77 }
78}
79
80struct Amp<'a, A, R> {
81 args_prompter_fn: A,
82 runner: &'a R,
83}
84
85impl<'a, A, R> Amp<'a, A, R> {
86 fn new(args: A, runner: &'a R) -> Self {
87 Self {
88 args_prompter_fn: args,
89 runner,
90 }
91 }
92}
93
94impl<A: Fn() -> String, R: TaskRunner<Response = ShellResponse>> Amp<'_, A, R> {
95 fn exec_amps(&self, amp: String, base_path: &Path) -> Result<()> {
96 let mut args = (self.args_prompter_fn)();
97 loop {
98 match (&args).into() {
99 AmpPrompts::Help => {
100 let cmd_path = base_path.join("amps").join(&);
101 let cmd = vec![cmd_path.to_str().unwrap(), "--help"];
102 self.runner.run(cmd)?;
103 args = (self.args_prompter_fn)();
104 }
105 AmpPrompts::Exit => {
106 return Ok(());
107 }
108 AmpPrompts::Args => break,
109 }
110 }
111 let cmd_path = base_path.join("amps").join(amp);
112 let mut cmd = vec![cmd_path.to_str().unwrap()];
113 for arg in args.split_whitespace() {
114 cmd.push(arg);
115 }
116 self.runner.run(cmd)?;
117 Ok(())
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use std::cell::RefCell;
124
125 use crate::test::utils::MockRunner;
126
127 use super::*;
128
129 #[test]
130 fn test_exec_amp_with_help_and_run() {
131 let response_help = ShellResponse::builder()
132 .status(0)
133 .body("this is the help".to_string())
134 .build()
135 .unwrap();
136 let response = ShellResponse::builder()
137 .status(0)
138 .body("response output".to_string())
139 .build()
140 .unwrap();
141 let amp_script = "list_releases".to_string();
142 let runner = MockRunner::new(vec![response, response_help]);
143 let prompted_args = RefCell::new(vec![
144 "github.com/jordilin/gitar".to_string(),
145 "help".to_string(),
146 ]);
147 let args_prompter_fn = || prompted_args.borrow_mut().pop().unwrap();
148 let amp_runner = Amp::new(args_prompter_fn, &runner);
149 let base_path = Path::new("/tmp");
150 assert!(amp_runner.exec_amps(amp_script, base_path).is_ok());
151 assert_eq!(2, *runner.run_count.borrow());
152 }
153
154 #[test]
155 fn test_exec_amp_with_help_and_exit() {
156 let response_help = ShellResponse::builder()
157 .status(0)
158 .body("this is the help".to_string())
159 .build()
160 .unwrap();
161 let amp_script = "list_releases".to_string();
162 let runner = MockRunner::new(vec![response_help]);
163 let prompted_args = RefCell::new(vec!["exit".to_string(), "help".to_string()]);
164 let args_prompter_fn = || prompted_args.borrow_mut().pop().unwrap();
165 let amp_runner = Amp::new(args_prompter_fn, &runner);
166 let base_path = Path::new("/tmp");
167 assert!(amp_runner.exec_amps(amp_script, base_path).is_ok());
168 assert_eq!(1, *runner.run_count.borrow());
169 }
170
171 #[test]
172 fn test_list_amps_error_if_none_available() {
173 let response = ShellResponse::builder()
174 .status(0)
175 .body("".to_string())
176 .build()
177 .unwrap();
178 let runner = MockRunner::new(vec![response]);
179 let amps_path = "/tmp/amps";
180 let amps = list_amps(runner, amps_path);
181 match amps {
182 Err(err) => match err.downcast_ref::<GRError>() {
183 Some(GRError::PreconditionNotMet(msg)) => {
184 assert_eq!(
185 "No amps are available in /tmp/amps. Please check \
186 https://github.com/jordilin/gitar-amps for installation instructions",
187 msg
188 );
189 }
190 _ => panic!("Expected error"),
191 },
192 Ok(_) => panic!("Expected error"),
193 }
194 }
195}