1pub mod constants {
2 pub const CANDIDATES_DIR: &str = "candidates";
3 pub const CANDIDATES_FILE: &str = "candidates";
4 pub const CURRENT_DIR: &str = "current";
5 pub const DEFAULT_SDKMAN_HOME: &str = ".sdkman";
6 pub const SDKMAN_DIR_ENV_VAR: &str = "SDKMAN_DIR";
7 pub const TMP_DIR: &str = "tmp";
8 pub const VAR_DIR: &str = "var";
9}
10
11pub mod helpers {
12 use colored::Colorize;
13 use directories::UserDirs;
14 use std::path::PathBuf;
15 use std::{env, fs, process};
16
17 use crate::constants::{
18 CANDIDATES_DIR, CANDIDATES_FILE, DEFAULT_SDKMAN_HOME, SDKMAN_DIR_ENV_VAR, VAR_DIR,
19 };
20
21 pub fn infer_sdkman_dir() -> PathBuf {
22 match env::var(SDKMAN_DIR_ENV_VAR) {
23 Ok(s) => PathBuf::from(s),
24 Err(_) => fallback_sdkman_dir(),
25 }
26 }
27
28 fn fallback_sdkman_dir() -> PathBuf {
29 UserDirs::new()
30 .map(|dir| dir.home_dir().join(DEFAULT_SDKMAN_HOME))
31 .unwrap()
32 }
33
34 pub fn check_file_exists(path: PathBuf) -> PathBuf {
35 if path.exists() && path.is_file() {
36 path
37 } else {
38 panic!("not a valid path: {}", path.to_str().unwrap())
39 }
40 }
41
42 pub fn read_file_content(path: PathBuf) -> Option<String> {
43 match fs::read_to_string(path) {
44 Ok(s) => Some(s),
45 Err(_) => None,
46 }
47 .filter(|s| !s.trim().is_empty())
48 .map(|s| s.trim().to_string())
49 }
50
51 pub fn known_candidates<'a>(sdkman_dir: PathBuf) -> Vec<&'static str> {
52 let absolute_path = sdkman_dir.join(VAR_DIR).join(CANDIDATES_FILE);
53 let verified_path = check_file_exists(absolute_path);
54 let panic = format!(
55 "the candidates file is missing: {}",
56 verified_path.to_str().unwrap()
57 );
58 let content = read_file_content(verified_path).expect(&panic);
59 let line_str: &'static str = Box::leak(content.into_boxed_str());
60 let mut fields = Vec::new();
61 for field in line_str.split(',') {
62 fields.push(field.trim());
63 }
64
65 fields
66 }
67
68 pub fn validate_candidate(all_candidates: Vec<&str>, candidate: &str) -> String {
69 if !all_candidates.contains(&candidate) {
70 eprintln!("{} is not a valid candidate.", candidate.bold());
71 process::exit(1);
72 } else {
73 candidate.to_string()
74 }
75 }
76
77 pub fn validate_version_path(base_dir: PathBuf, candidate: &str, version: &str) -> PathBuf {
78 let version_path = base_dir.join(CANDIDATES_DIR).join(candidate).join(version);
79 if version_path.exists() && version_path.is_dir() {
80 version_path
81 } else {
82 eprintln!(
83 "{} {} is not installed on your system",
84 candidate.bold(),
85 version.bold()
86 );
87 process::exit(1)
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use std::env;
95 use std::io::Write;
96 use std::path::PathBuf;
97
98 use serial_test::serial;
99 use tempfile::NamedTempFile;
100
101 use crate::constants::SDKMAN_DIR_ENV_VAR;
102 use crate::helpers::infer_sdkman_dir;
103 use crate::helpers::read_file_content;
104
105 #[test]
106 #[serial]
107 fn should_infer_sdkman_dir_from_env_var() {
108 let sdkman_dir = PathBuf::from("/home/someone/.sdkman");
109 env::set_var(SDKMAN_DIR_ENV_VAR, sdkman_dir.to_owned());
110 assert_eq!(sdkman_dir, infer_sdkman_dir());
111 }
112
113 #[test]
114 #[serial]
115 fn should_infer_fallback_dir() {
116 env::remove_var(SDKMAN_DIR_ENV_VAR);
117 let actual_sdkman_dir = dirs::home_dir().unwrap().join(".sdkman");
118 assert_eq!(actual_sdkman_dir, infer_sdkman_dir());
119 }
120
121 #[test]
122 #[serial]
123 fn should_read_content_from_file() {
124 let expected_version = "5.0.0";
125 let mut file = NamedTempFile::new().unwrap();
126 file.write(expected_version.as_bytes()).unwrap();
127 let path = file.path().to_path_buf();
128 let maybe_version = read_file_content(path);
129 assert_eq!(maybe_version, Some(expected_version.to_string()));
130 }
131
132 #[test]
133 #[serial]
134 fn should_fail_reading_file_content_from_empty_file() {
135 let file = NamedTempFile::new().unwrap();
136 let path = file.path().to_path_buf();
137 let maybe_version = read_file_content(path);
138 assert_eq!(maybe_version, None);
139 }
140}