Skip to main content

cargo_dev_install/
tui_select.rs

1use std::io::{self, BufRead, Write};
2
3pub fn select_bin<R: BufRead, W: Write>(
4    bin_names: &[String],
5    reader: &mut R,
6    writer: &mut W,
7) -> io::Result<String> {
8    if bin_names.len() == 1 {
9        return Ok(bin_names[0].clone());
10    }
11
12    if bin_names.is_empty() {
13        return Err(io::Error::new(
14            io::ErrorKind::InvalidInput,
15            "no binaries available",
16        ));
17    }
18
19    writeln!(writer, "Select a binary:")?;
20    for (idx, name) in bin_names.iter().enumerate() {
21        writeln!(writer, "  {}) {}", idx + 1, name)?;
22    }
23
24    loop {
25        write!(writer, "Enter choice (1-{}): ", bin_names.len())?;
26        writer.flush()?;
27
28        let mut input = String::new();
29        if reader.read_line(&mut input)? == 0 {
30            return Err(io::Error::new(
31                io::ErrorKind::UnexpectedEof,
32                "no selection provided",
33            ));
34        }
35
36        let trimmed = input.trim();
37        if let Ok(choice) = trimmed.parse::<usize>() {
38            if (1..=bin_names.len()).contains(&choice) {
39                return Ok(bin_names[choice - 1].clone());
40            }
41        }
42
43        writeln!(writer, "Invalid selection. Try again.")?;
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use std::io::Cursor;
51
52    #[test]
53    fn select_bin_returns_single_entry() {
54        let mut input = Cursor::new("");
55        let mut output = Vec::new();
56        let bins = vec!["demo".to_string()];
57        let selected = select_bin(&bins, &mut input, &mut output).expect("select bin");
58        assert_eq!(selected, "demo");
59        assert!(output.is_empty());
60    }
61
62    #[test]
63    fn select_bin_prompts_and_selects_choice() {
64        let mut input = Cursor::new("2\n");
65        let mut output = Vec::new();
66        let bins = vec!["alpha".to_string(), "beta".to_string()];
67        let selected = select_bin(&bins, &mut input, &mut output).expect("select bin");
68        assert_eq!(selected, "beta");
69        let output_str = String::from_utf8(output).expect("utf8");
70        assert!(output_str.contains("Select a binary:"));
71        assert!(output_str.contains("1) alpha"));
72        assert!(output_str.contains("2) beta"));
73    }
74
75    #[test]
76    fn select_bin_reprompts_on_invalid_input() {
77        let mut input = Cursor::new("foo\n1\n");
78        let mut output = Vec::new();
79        let bins = vec!["alpha".to_string(), "beta".to_string()];
80        let selected = select_bin(&bins, &mut input, &mut output).expect("select bin");
81        assert_eq!(selected, "alpha");
82        let output_str = String::from_utf8(output).expect("utf8");
83        assert!(output_str.contains("Invalid selection"));
84    }
85
86    #[test]
87    fn select_bin_errors_on_empty_list() {
88        let mut input = Cursor::new("");
89        let mut output = Vec::new();
90        let bins = Vec::<String>::new();
91        let err = select_bin(&bins, &mut input, &mut output).expect_err("expected error");
92        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
93    }
94}