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 {}. Please check \
56 https://github.com/jordilin/gitar-amps for installation instructions",
57 amps_path
58 ))
59 .into());
60 }
61 let amps: Vec<String> = response.body.split('\n').map(|s| s.to_string()).collect();
62 Ok(amps)
63}
64
65enum AmpPrompts {
66 Args,
67 Help,
68 Exit,
69}
70
71impl From<&String> for AmpPrompts {
72 fn from(s: &String) -> Self {
73 match s.as_str() {
74 "-h" | "--help" | "help" | "h" => AmpPrompts::Help,
75 "exit" | "cancel" | "quit" | "q" => AmpPrompts::Exit,
76 _ => AmpPrompts::Args,
77 }
78 }
79}
80
81struct Amp<'a, A, R> {
82 args_prompter_fn: A,
83 runner: &'a R,
84}
85
86impl<'a, A, R> Amp<'a, A, R> {
87 fn new(args: A, runner: &'a R) -> Self {
88 Self {
89 args_prompter_fn: args,
90 runner,
91 }
92 }
93}
94
95impl<A: Fn() -> String, R: TaskRunner<Response = ShellResponse>> Amp<'_, A, R> {
96 fn exec_amps(&self, amp: String, base_path: &Path) -> Result<()> {
97 let mut args = (self.args_prompter_fn)();
98 loop {
99 match (&args).into() {
100 AmpPrompts::Help => {
101 let cmd_path = base_path.join("amps").join(&);
102 let cmd = vec![cmd_path.to_str().unwrap(), "--help"];
103 self.runner.run(cmd)?;
104 args = (self.args_prompter_fn)();
105 }
106 AmpPrompts::Exit => {
107 return Ok(());
108 }
109 AmpPrompts::Args => break,
110 }
111 }
112 let cmd_path = base_path.join("amps").join(amp);
113 let mut cmd = vec![cmd_path.to_str().unwrap()];
114 for arg in args.split_whitespace() {
115 cmd.push(arg);
116 }
117 self.runner.run(cmd)?;
118 Ok(())
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use std::cell::RefCell;
125
126 use crate::test::utils::MockRunner;
127
128 use super::*;
129
130 #[test]
131 fn test_exec_amp_with_help_and_run() {
132 let response_help = ShellResponse::builder()
133 .status(0)
134 .body("this is the help".to_string())
135 .build()
136 .unwrap();
137 let response = ShellResponse::builder()
138 .status(0)
139 .body("response output".to_string())
140 .build()
141 .unwrap();
142 let amp_script = "list_releases".to_string();
143 let runner = MockRunner::new(vec![response, response_help]);
144 let prompted_args = RefCell::new(vec![
145 "github.com/jordilin/gitar".to_string(),
146 "help".to_string(),
147 ]);
148 let args_prompter_fn = || prompted_args.borrow_mut().pop().unwrap();
149 let amp_runner = Amp::new(args_prompter_fn, &runner);
150 let base_path = Path::new("/tmp");
151 assert!(amp_runner.exec_amps(amp_script, base_path).is_ok());
152 assert_eq!(2, *runner.run_count.borrow());
153 }
154
155 #[test]
156 fn test_exec_amp_with_help_and_exit() {
157 let response_help = ShellResponse::builder()
158 .status(0)
159 .body("this is the help".to_string())
160 .build()
161 .unwrap();
162 let amp_script = "list_releases".to_string();
163 let runner = MockRunner::new(vec![response_help]);
164 let prompted_args = RefCell::new(vec!["exit".to_string(), "help".to_string()]);
165 let args_prompter_fn = || prompted_args.borrow_mut().pop().unwrap();
166 let amp_runner = Amp::new(args_prompter_fn, &runner);
167 let base_path = Path::new("/tmp");
168 assert!(amp_runner.exec_amps(amp_script, base_path).is_ok());
169 assert_eq!(1, *runner.run_count.borrow());
170 }
171
172 #[test]
173 fn test_list_amps_error_if_none_available() {
174 let response = ShellResponse::builder()
175 .status(0)
176 .body("".to_string())
177 .build()
178 .unwrap();
179 let runner = MockRunner::new(vec![response]);
180 let amps_path = "/tmp/amps";
181 let amps = list_amps(runner, amps_path);
182 match amps {
183 Err(err) => match err.downcast_ref::<GRError>() {
184 Some(GRError::PreconditionNotMet(msg)) => {
185 assert_eq!(
186 "No amps are available in /tmp/amps. Please check \
187 https://github.com/jordilin/gitar-amps for installation instructions",
188 msg
189 );
190 }
191 _ => panic!("Expected error"),
192 },
193 Ok(_) => panic!("Expected error"),
194 }
195 }
196}