Skip to main content

actr_cli/
project_language.rs

1use std::{fmt, path::Path};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum DetectedProjectLanguage {
5    Rust,
6    Swift,
7    Kotlin,
8    Python,
9    TypeScript,
10    Ambiguous,
11    Unknown,
12}
13
14impl DetectedProjectLanguage {
15    pub fn detect(root: &Path) -> Self {
16        let has_swift = root.join("Package.swift").exists() || root.join("project.yml").exists();
17        let has_kotlin =
18            root.join("build.gradle.kts").exists() || root.join("build.gradle").exists();
19        let has_python =
20            root.join("pyproject.toml").exists() || root.join("requirements.txt").exists();
21        let has_typescript =
22            root.join("tsconfig.json").exists() || root.join("package.json").exists();
23        let has_rust = root.join("Cargo.toml").exists();
24
25        let matched_count = [has_swift, has_kotlin, has_python, has_typescript, has_rust]
26            .into_iter()
27            .filter(|matched| *matched)
28            .count();
29
30        match matched_count {
31            0 => Self::Unknown,
32            1 => {
33                if has_swift {
34                    Self::Swift
35                } else if has_kotlin {
36                    Self::Kotlin
37                } else if has_python {
38                    Self::Python
39                } else if has_typescript {
40                    Self::TypeScript
41                } else {
42                    Self::Rust
43                }
44            }
45            _ => Self::Ambiguous,
46        }
47    }
48
49    pub fn cli_name(self) -> &'static str {
50        match self {
51            Self::Rust => "rust",
52            Self::Swift => "swift",
53            Self::Kotlin => "kotlin",
54            Self::Python => "python",
55            Self::TypeScript => "typescript",
56            Self::Ambiguous => "ambiguous",
57            Self::Unknown => "unknown",
58        }
59    }
60}
61
62impl fmt::Display for DetectedProjectLanguage {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.write_str(self.cli_name())
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::DetectedProjectLanguage;
71    use std::fs;
72    use tempfile::TempDir;
73
74    #[test]
75    fn detects_rust_from_cargo_toml() {
76        let dir = TempDir::new().unwrap();
77        fs::write(
78            dir.path().join("Cargo.toml"),
79            "[package]\nname = \"demo\"\n",
80        )
81        .unwrap();
82
83        assert_eq!(
84            DetectedProjectLanguage::detect(dir.path()),
85            DetectedProjectLanguage::Rust
86        );
87    }
88
89    #[test]
90    fn returns_unknown_without_language_markers() {
91        let dir = TempDir::new().unwrap();
92
93        assert_eq!(
94            DetectedProjectLanguage::detect(dir.path()),
95            DetectedProjectLanguage::Unknown
96        );
97    }
98
99    #[test]
100    fn returns_ambiguous_with_multiple_language_markers() {
101        let dir = TempDir::new().unwrap();
102        fs::write(
103            dir.path().join("Cargo.toml"),
104            "[package]\nname = \"demo\"\n",
105        )
106        .unwrap();
107        fs::write(
108            dir.path().join("package.json"),
109            "{\n  \"name\": \"demo\"\n}\n",
110        )
111        .unwrap();
112
113        assert_eq!(
114            DetectedProjectLanguage::detect(dir.path()),
115            DetectedProjectLanguage::Ambiguous
116        );
117    }
118}