gr/cmds/
amps.rs

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![&amp_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(&amp);
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}