1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::Path;
6
7#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum SourceLanguage {
11 Python,
13 TypeScript,
15 JavaScript,
17 Rust,
19 Go,
21 Other(String),
23}
24
25impl SourceLanguage {
26 pub fn as_str(&self) -> &str {
28 match self {
29 Self::Python => "python",
30 Self::TypeScript => "typescript",
31 Self::JavaScript => "javascript",
32 Self::Rust => "rust",
33 Self::Go => "go",
34 Self::Other(s) => s.as_str(),
35 }
36 }
37
38 pub fn parse(s: &str) -> Option<Self> {
40 match s.to_ascii_lowercase().as_str() {
41 "python" | "py" => Some(Self::Python),
42 "typescript" | "ts" => Some(Self::TypeScript),
43 "javascript" | "js" | "node" => Some(Self::JavaScript),
44 "rust" | "rs" => Some(Self::Rust),
45 "go" | "golang" => Some(Self::Go),
46 other if !other.is_empty() => Some(Self::Other(other.to_string())),
48 _ => None,
49 }
50 }
51}
52
53impl std::fmt::Display for SourceLanguage {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "{}", self.as_str())
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum TargetLanguage {
63 Rust,
65 Go,
67 Python,
69 TypeScript,
71 JavaScript,
73 Other(String),
75}
76
77impl TargetLanguage {
78 pub fn as_str(&self) -> &str {
80 match self {
81 Self::Rust => "rust",
82 Self::Go => "go",
83 Self::Python => "python",
84 Self::TypeScript => "typescript",
85 Self::JavaScript => "javascript",
86 Self::Other(s) => s.as_str(),
87 }
88 }
89
90 pub fn parse(s: &str) -> Option<Self> {
92 match s.to_ascii_lowercase().as_str() {
93 "rust" | "rs" => Some(Self::Rust),
94 "go" | "golang" => Some(Self::Go),
95 "python" | "py" => Some(Self::Python),
96 "typescript" | "ts" => Some(Self::TypeScript),
97 "javascript" | "js" => Some(Self::JavaScript),
98 other if !other.is_empty() => Some(Self::Other(other.to_string())),
99 _ => None,
100 }
101 }
102}
103
104impl std::fmt::Display for TargetLanguage {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 write!(f, "{}", self.as_str())
107 }
108}
109
110pub fn detect_languages(
113 paths: &[impl AsRef<Path>],
114) -> (Option<SourceLanguage>, HashMap<String, f64>) {
115 let mut counts: HashMap<String, usize> = HashMap::new();
116 for p in paths {
117 let ext = p
118 .as_ref()
119 .extension()
120 .and_then(|e| e.to_str())
121 .unwrap_or("")
122 .to_ascii_lowercase();
123 let lang = match ext.as_str() {
124 "ts" | "tsx" => "typescript",
125 "js" | "jsx" | "mjs" | "cjs" => "javascript",
126 "py" | "pyi" => "python",
127 "rs" => "rust",
128 "go" => "go",
129 "java" => "java",
130 "kt" | "kts" => "kotlin",
131 "scala" | "sc" => "scala",
132 "cs" | "csx" => "csharp",
133 "fs" | "fsx" => "fsharp",
134 "rb" => "ruby",
135 "php" => "php",
136 "pl" | "pm" => "perl",
137 "lua" => "lua",
138 "r" => "r",
139 "jl" => "julia",
140 "swift" => "swift",
141 "dart" => "dart",
142 "ex" | "exs" => "elixir",
143 "erl" | "hrl" => "erlang",
144 "hs" | "lhs" => "haskell",
145 "ml" | "mli" => "ocaml",
146 "clj" | "cljs" | "cljc" => "clojure",
147 "c" | "h" => "c",
148 "cpp" | "cc" | "cxx" | "hpp" | "hh" => "cpp",
149 "zig" => "zig",
150 "nim" => "nim",
151 "cr" => "crystal",
152 "sol" => "solidity",
153 "sql" => "sql",
154 "sh" | "bash" | "zsh" => "shell",
155 "ps1" | "psm1" => "powershell",
156 "html" | "htm" => "html",
157 "css" => "css",
158 _ => continue,
159 };
160 *counts.entry(lang.to_string()).or_default() += 1;
161 }
162 let total: usize = counts.values().sum();
163 let mut mix = HashMap::new();
164 if total > 0 {
165 for (k, v) in &counts {
166 mix.insert(k.clone(), (*v as f64) * 100.0 / total as f64);
167 }
168 }
169 let priority = [
171 "typescript",
172 "javascript",
173 "python",
174 "rust",
175 "go",
176 "java",
177 "kotlin",
178 "csharp",
179 "ruby",
180 "php",
181 "swift",
182 "dart",
183 "c",
184 "cpp",
185 "scala",
186 "elixir",
187 "haskell",
188 ];
189 let primary = priority
190 .iter()
191 .filter_map(|k| counts.get(*k).map(|c| (*k, *c)))
192 .max_by_key(|(_, c)| *c)
193 .and_then(|(k, _)| SourceLanguage::parse(k));
194 (primary, mix)
195}