git_workspace/
utils.rs

1use anyhow::Context;
2use std::io;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6// From https://docs.rs/clt/latest/src/clt/term.rs.html#277-293
7fn build_prompt_text(
8    text: &str,
9    suffix: &str,
10    show_default: bool,
11    default: Option<&str>,
12) -> String {
13    let prompt_text = if default.is_some() && show_default {
14        format!("{} [{}]", text, default.unwrap())
15    } else {
16        text.to_string()
17    };
18    prompt_text + suffix
19}
20
21fn get_prompt_input(prompt_text: &str) -> String {
22    print!("{}", prompt_text);
23    io::stdout().flush().unwrap();
24    let mut input = String::new();
25    io::stdin()
26        .read_line(&mut input)
27        .expect("Failed to read line");
28    input.trim_end_matches('\n').to_string()
29}
30
31pub fn confirm(text: &str, default: bool, prompt_suffix: &str, show_default: bool) -> bool {
32    let default_string = match default {
33        true => Some("Y/n"),
34        false => Some("y/N"),
35    };
36    let prompt_text = build_prompt_text(text, prompt_suffix, show_default, default_string);
37
38    loop {
39        let prompt_input = get_prompt_input(&prompt_text).to_ascii_lowercase();
40        match prompt_input.trim() {
41            "y" | "yes" => {
42                return true;
43            }
44            "n" | "no" => {
45                return false;
46            }
47            "" => {
48                return default;
49            }
50            _ => {
51                println!("Error: invalid input");
52            }
53        }
54    }
55}
56
57// Convert our workspace path to a PathBuf. We cannot use the value given directly as
58// it could contain a tilde, so we run `expanduser` on it _if_ we are on a Unix platform.
59// On Windows this isn't supported.
60#[cfg(unix)]
61pub fn expand_workspace_path(path: &Path) -> anyhow::Result<PathBuf> {
62    expanduser::expanduser(path.to_string_lossy())
63        .with_context(|| "Error expanding git workspace path")
64}
65
66#[cfg(not(unix))]
67pub fn expand_workspace_path(path: &Path) -> anyhow::Result<PathBuf> {
68    Ok(path.to_path_buf())
69}
70
71pub fn ensure_workspace_dir_exists(path: &PathBuf) -> anyhow::Result<PathBuf> {
72    if !path.exists() {
73        fs_extra::dir::create_all(path, false)
74            .with_context(|| format!("Error creating workspace directory {}", &path.display()))?;
75    }
76    path.canonicalize()
77        .with_context(|| format!("Error canonicalizing workspace path {}", &path.display()))
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_build_prompt_text() {
86        // Test with default and show_default true
87        assert_eq!(
88            build_prompt_text("Continue?", ": ", true, Some("Y/n")),
89            "Continue? [Y/n]: "
90        );
91
92        // Test with default but show_default false
93        assert_eq!(
94            build_prompt_text("Continue?", ": ", false, Some("Y/n")),
95            "Continue?: "
96        );
97
98        // Test without default
99        assert_eq!(
100            build_prompt_text("Continue?", ": ", true, None),
101            "Continue?: "
102        );
103
104        // Test with empty text
105        assert_eq!(build_prompt_text("", ": ", true, Some("Y/n")), " [Y/n]: ");
106    }
107
108    #[test]
109    fn test_expand_workspace_path() {
110        let path = PathBuf::from("/test/path");
111        let result = expand_workspace_path(&path).unwrap();
112        assert_eq!(result, path);
113
114        // Test with relative path
115        let relative_path = PathBuf::from("test/path");
116        let result = expand_workspace_path(&relative_path).unwrap();
117        assert_eq!(result, relative_path);
118    }
119
120    #[test]
121    #[cfg(unix)]
122    fn test_expand_workspace_path_on_unix_platform() {
123        let custom_home = "/custom/home";
124        std::env::set_var("HOME", custom_home);
125
126        let path = PathBuf::from("~/test/path");
127        let result = expand_workspace_path(&path).unwrap();
128        let expected_path = PathBuf::from(format!("{}/test/path", custom_home));
129
130        assert_eq!(result, expected_path);
131        std::env::remove_var("HOME"); // Clean up
132    }
133
134    #[test]
135    fn test_ensure_workspace_exists() {
136        // Test with temporary directory
137        let temp_dir = tempfile::tempdir().unwrap();
138        let path = temp_dir.path().to_path_buf();
139
140        // Test existing directory
141        let result = ensure_workspace_dir_exists(&path).unwrap();
142        assert_eq!(result, path.canonicalize().unwrap());
143
144        // Test non-existing directory
145        let new_path = path.join("new_dir");
146        let result = ensure_workspace_dir_exists(&new_path).unwrap();
147        assert!(new_path.exists());
148        assert_eq!(result, new_path.canonicalize().unwrap());
149    }
150}