starship/modules/
haxe.rs

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
11/// Creates a module with the current Haxe version
12pub 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}