1use super::{Context, Module, ModuleConfig};
2
3use crate::configs::haxe::HaxeConfig;
4use crate::formatter::StringFormatter;
5use crate::formatter::VersionFormatter;
6use serde_json as json;
7
8use regex::Regex;
9const HAXERC_VERSION_PATTERN: &str = "(?:[0-9a-zA-Z][-+0-9.a-zA-Z]+)";
10
11pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
13 let mut module = context.new_module("haxe");
14 let config = HaxeConfig::try_load(module.config);
15
16 let is_haxe_project = context
17 .try_begin_scan()?
18 .set_files(&config.detect_files)
19 .set_extensions(&config.detect_extensions)
20 .set_folders(&config.detect_folders)
21 .is_match();
22
23 if !is_haxe_project {
24 return None;
25 }
26
27 let parsed = StringFormatter::new(config.format).and_then(|formatter| {
28 formatter
29 .map_meta(|var, _| match var {
30 "symbol" => Some(config.symbol),
31 _ => None,
32 })
33 .map_style(|variable| match variable {
34 "style" => Some(Ok(config.style)),
35 _ => None,
36 })
37 .map(|variable| match variable {
38 "version" => {
39 let haxe_version = get_haxe_version(context)?;
40 VersionFormatter::format_module_version(
41 module.get_name(),
42 &haxe_version,
43 config.version_format,
44 )
45 .map(Ok)
46 }
47 _ => None,
48 })
49 .parse(None, Some(context))
50 });
51
52 module.set_segments(match parsed {
53 Ok(segments) => segments,
54 Err(error) => {
55 log::warn!("Error in module `haxe`:\n{error}");
56 return None;
57 }
58 });
59
60 Some(module)
61}
62
63fn get_haxe_version(context: &Context) -> Option<String> {
64 get_haxerc_version(context).or_else(|| {
65 let cmd_output = context.exec_cmd("haxe", &["--version"])?;
66 parse_haxe_version(cmd_output.stdout.as_str())
67 })
68}
69
70fn get_haxerc_version(context: &Context) -> Option<String> {
71 let raw_json = context.read_file_from_pwd(".haxerc")?;
72 let package_json: json::Value = json::from_str(&raw_json).ok()?;
73
74 let raw_version = package_json.get("version")?.as_str()?;
75 if raw_version.contains('/') || raw_version.contains('\\') {
76 return None;
77 }
78 Some(raw_version.to_string())
79}
80
81fn parse_haxe_version(raw_version: &str) -> Option<String> {
82 let re = Regex::new(HAXERC_VERSION_PATTERN).ok()?;
83 if !re.is_match(raw_version) {
84 return None;
85 }
86 Some(raw_version.trim().to_string())
87}
88
89#[cfg(test)]
90mod tests {
91 use super::parse_haxe_version;
92 use crate::{test::ModuleRenderer, utils::CommandOutput};
93 use nu_ansi_term::Color;
94 use serde_json as json;
95 use std::fs::File;
96 use std::io;
97 use std::io::Write;
98 use tempfile::TempDir;
99
100 #[test]
101 fn haxe_version() {
102 let ok_versions = [
103 "4.2.5",
104 "4.3.0-rc.1+",
105 "3.4.7abcdf",
106 "779b005",
107 "beta",
108 "alpha",
109 "latest",
110 "/git/779b005/bin/haxe",
111 "git/779b005/bin/haxe",
112 ];
113
114 let all_some = ok_versions.iter().all(|&v| parse_haxe_version(v).is_some());
115
116 assert!(all_some);
117
118 let sample_haxe_output = "4.3.0-rc.1+\n";
119
120 assert_eq!(
121 Some("4.3.0-rc.1+".to_string()),
122 parse_haxe_version(sample_haxe_output)
123 );
124 }
125
126 #[test]
127 fn folder_without_haxe() -> io::Result<()> {
128 let dir = tempfile::tempdir()?;
129 File::create(dir.path().join("haxe.txt"))?.sync_all()?;
130 let actual = ModuleRenderer::new("haxe")
131 .cmd(
132 "haxe --version",
133 Some(CommandOutput {
134 stdout: "4.3.0-rc.1+\n".to_owned(),
135 stderr: String::new(),
136 }),
137 )
138 .path(dir.path())
139 .collect();
140 let expected = None;
141 assert_eq!(expected, actual);
142 dir.close()
143 }
144
145 #[test]
146 fn folder_with_hxml_file() -> io::Result<()> {
147 let dir = tempfile::tempdir()?;
148 File::create(dir.path().join("build.hxml"))?.sync_all()?;
149 let actual = ModuleRenderer::new("haxe")
150 .cmd(
151 "haxe --version",
152 Some(CommandOutput {
153 stdout: "4.3.0-rc.1+\n".to_owned(),
154 stderr: String::new(),
155 }),
156 )
157 .path(dir.path())
158 .collect();
159 let expected = Some(format!(
160 "via {}",
161 Color::Fixed(202).bold().paint("⌘ v4.3.0-rc.1+ ")
162 ));
163 assert_eq!(expected, actual);
164 dir.close()
165 }
166
167 #[test]
168 fn folder_with_haxe_file() -> io::Result<()> {
169 let dir = tempfile::tempdir()?;
170 File::create(dir.path().join("Main.hx"))?.sync_all()?;
171 let actual = ModuleRenderer::new("haxe")
172 .cmd(
173 "haxe --version",
174 Some(CommandOutput {
175 stdout: "4.3.0-rc.1+\n".to_owned(),
176 stderr: String::new(),
177 }),
178 )
179 .path(dir.path())
180 .collect();
181 let expected = Some(format!(
182 "via {}",
183 Color::Fixed(202).bold().paint("⌘ v4.3.0-rc.1+ ")
184 ));
185 assert_eq!(expected, actual);
186 dir.close()
187 }
188
189 #[test]
190 fn folder_with_invalid_haxerc_file() -> io::Result<()> {
191 let dir = tempfile::tempdir()?;
192
193 let haxerc_name = ".haxerc";
194 let haxerc_content = json::json!({
195 "resolveLibs": "scoped"
196 })
197 .to_string();
198 fill_config(&dir, haxerc_name, Some(&haxerc_content))?;
199 let actual = ModuleRenderer::new("haxe")
200 .cmd(
201 "haxe --version",
202 Some(CommandOutput {
203 stdout: "4.3.0-rc.1+\n".to_owned(),
204 stderr: String::new(),
205 }),
206 )
207 .path(dir.path())
208 .collect();
209 let expected = Some(format!(
210 "via {}",
211 Color::Fixed(202).bold().paint("⌘ v4.3.0-rc.1+ ")
212 ));
213 assert_eq!(expected, actual);
214 dir.close()
215 }
216
217 #[test]
218 fn folder_with_haxerc_file() -> io::Result<()> {
219 let dir = tempfile::tempdir()?;
220
221 let haxerc_name = ".haxerc";
222 let haxerc_content = json::json!({
223 "version": "4.2.5",
224 "resolveLibs": "scoped"
225 })
226 .to_string();
227 fill_config(&dir, haxerc_name, Some(&haxerc_content))?;
228 let actual = ModuleRenderer::new("haxe")
229 .cmd(
230 "haxe --version",
231 Some(CommandOutput {
232 stdout: "4.3.0-rc.1+\n".to_owned(),
233 stderr: String::new(),
234 }),
235 )
236 .path(dir.path())
237 .collect();
238 let expected = Some(format!(
239 "via {}",
240 Color::Fixed(202).bold().paint("⌘ v4.2.5 ")
241 ));
242 assert_eq!(expected, actual);
243 dir.close()
244 }
245
246 #[test]
247 fn folder_with_haxerc_nightly_file() -> io::Result<()> {
248 let dir = tempfile::tempdir()?;
249
250 let haxerc_name = ".haxerc";
251 let haxerc_content = json::json!({
252 "version": "779b005",
253 "resolveLibs": "scoped"
254 })
255 .to_string();
256
257 fill_config(&dir, haxerc_name, Some(&haxerc_content))?;
258 let actual = ModuleRenderer::new("haxe")
259 .cmd(
260 "haxe --version",
261 Some(CommandOutput {
262 stdout: "4.3.0-rc.1+\n".to_owned(),
263 stderr: String::new(),
264 }),
265 )
266 .path(dir.path())
267 .collect();
268 let expected = Some(format!(
269 "via {}",
270 Color::Fixed(202).bold().paint("⌘ v779b005 ")
271 ));
272 assert_eq!(expected, actual);
273 dir.close()
274 }
275
276 #[test]
277 fn folder_with_haxerc_with_path() -> io::Result<()> {
278 let dir = tempfile::tempdir()?;
279
280 let haxerc_name = ".haxerc";
281 let haxerc_content = json::json!({
282 "version": "/home/git/haxe/haxe.executable",
283 "resolveLibs": "scoped"
284 })
285 .to_string();
286
287 fill_config(&dir, haxerc_name, Some(&haxerc_content))?;
288 let actual = ModuleRenderer::new("haxe")
289 .cmd(
290 "haxe --version",
291 Some(CommandOutput {
292 stdout: "4.3.0-rc.1+\n".to_owned(),
293 stderr: String::new(),
294 }),
295 )
296 .path(dir.path())
297 .collect();
298 let expected = Some(format!(
299 "via {}",
300 Color::Fixed(202).bold().paint("⌘ v4.3.0-rc.1+ ")
301 ));
302 assert_eq!(expected, actual);
303 dir.close()
304 }
305
306 fn fill_config(
307 project_dir: &TempDir,
308 file_name: &str,
309 contents: Option<&str>,
310 ) -> io::Result<()> {
311 let mut file = File::create(project_dir.path().join(file_name))?;
312 file.write_all(contents.unwrap_or("").as_bytes())?;
313 file.sync_all()
314 }
315}