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 {}. 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(&amp);
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}