quanttide_devops/source/
config_file.rs1use crate::contract::Language;
27
28use std::path::Path;
29
30type VersionExtract = fn(&str) -> Option<String>;
31
32#[deprecated(note = "请使用 detect_languages,它独立检测所有语言而非按优先级返回首个")]
51pub fn detect_language(dir: &Path) -> Language {
52 if dir.join("Cargo.toml").exists() {
53 Language::Rust
54 } else if dir.join("pyproject.toml").exists() || dir.join("requirements.txt").exists() {
55 Language::Python
56 } else if dir.join("go.mod").exists() {
57 Language::Go
58 } else if dir.join("pubspec.yaml").exists() {
59 Language::Dart
60 } else if dir.join("package.json").exists() {
61 Language::TypeScript
62 } else {
63 Language::Unknown("无法识别".into())
64 }
65}
66
67pub fn detect_languages(dir: &Path) -> Vec<Language> {
80 let mut result = Vec::new();
81 if dir.join("Cargo.toml").exists() {
82 result.push(Language::Rust);
83 }
84 if dir.join("pyproject.toml").exists() || dir.join("requirements.txt").exists() {
85 if !result.contains(&Language::Python) {
86 result.push(Language::Python);
87 }
88 }
89 if dir.join("go.mod").exists() {
90 result.push(Language::Go);
91 }
92 if dir.join("pubspec.yaml").exists() {
93 result.push(Language::Dart);
94 }
95 if dir.join("package.json").exists() {
96 result.push(Language::TypeScript);
97 }
98 result
99}
100
101pub fn read_config_versions(dir: &Path) -> Vec<(String, Option<String>)> {
116 let checks: &[(&str, VersionExtract)] = &[
117 ("Cargo.toml", |c| extract_kv_version(c, "version")),
118 ("pyproject.toml", |c| extract_kv_version(c, "version")),
119 ("package.json", extract_json_version),
120 ("pubspec.yaml", |c| extract_kv_yaml_version(c)),
121 ];
122 checks
123 .iter()
124 .filter_map(|(name, extract)| {
125 let path = dir.join(name);
126 if path.exists() {
127 let content = std::fs::read_to_string(&path).ok()?;
128 Some((name.to_string(), extract(&content)))
129 } else {
130 None
131 }
132 })
133 .collect()
134}
135
136fn extract_kv_version(content: &str, key: &str) -> Option<String> {
137 let p = format!("{} = \"", key);
138 for line in content.lines() {
139 let t = line.trim();
140 if let Some(r) = t.strip_prefix(&p)
141 && let Some(end) = r.find('"')
142 {
143 let v = r[..end].to_string();
144 if !v.is_empty() {
145 return Some(v);
146 }
147 }
148 }
149 None
150}
151
152fn extract_json_version(content: &str) -> Option<String> {
153 let key = r#""version":"#;
154 for line in content.lines() {
155 if let Some(pos) = line.find(key) {
156 let after_key = line[pos + 10..].trim();
157 if let Some(start) = after_key.find('"') {
158 let after_open = &after_key[start + 1..];
159 if let Some(end) = after_open.find('"') {
160 let v = &after_open[..end];
161 if !v.is_empty() {
162 return Some(v.to_string());
163 }
164 }
165 }
166 }
167 }
168 None
169}
170
171fn extract_kv_yaml_version(content: &str) -> Option<String> {
172 for line in content.lines() {
173 let t = line.trim();
174 if let Some(r) = t.strip_prefix("version:") {
175 let v = r.trim();
176 if !v.is_empty() && !v.starts_with('#') {
177 return Some(v.to_string());
178 }
179 }
180 }
181 None
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[allow(deprecated)]
191 #[test]
192 fn test_detect_language_rust() {
193 let d = tempfile::tempdir().unwrap();
194 std::fs::write(d.path().join("Cargo.toml"), "").unwrap();
195 assert_eq!(detect_language(d.path()), Language::Rust);
196 }
197
198 #[allow(deprecated)]
199 #[test]
200 fn test_detect_language_unknown() {
201 let d = tempfile::tempdir().unwrap();
202 assert!(matches!(detect_language(d.path()), Language::Unknown(_)));
203 }
204
205 #[allow(deprecated)]
206 #[test]
207 fn test_detect_language_python() {
208 let d = tempfile::tempdir().unwrap();
209 std::fs::write(d.path().join("pyproject.toml"), "").unwrap();
210 assert_eq!(detect_language(d.path()), Language::Python);
211 }
212
213 #[allow(deprecated)]
214 #[test]
215 fn test_detect_language_python_requirements() {
216 let d = tempfile::tempdir().unwrap();
217 std::fs::write(d.path().join("requirements.txt"), "").unwrap();
218 assert_eq!(detect_language(d.path()), Language::Python);
219 }
220
221 #[allow(deprecated)]
222 #[test]
223 fn test_detect_language_go() {
224 let d = tempfile::tempdir().unwrap();
225 std::fs::write(d.path().join("go.mod"), "").unwrap();
226 assert_eq!(detect_language(d.path()), Language::Go);
227 }
228
229 #[allow(deprecated)]
230 #[test]
231 fn test_detect_language_dart() {
232 let d = tempfile::tempdir().unwrap();
233 std::fs::write(d.path().join("pubspec.yaml"), "").unwrap();
234 assert_eq!(detect_language(d.path()), Language::Dart);
235 }
236
237 #[allow(deprecated)]
238 #[test]
239 fn test_detect_language_typescript() {
240 let d = tempfile::tempdir().unwrap();
241 std::fs::write(d.path().join("package.json"), "").unwrap();
242 assert_eq!(detect_language(d.path()), Language::TypeScript);
243 }
244
245 #[test]
248 fn test_detect_languages_single() {
249 let d = tempfile::tempdir().unwrap();
250 std::fs::write(d.path().join("Cargo.toml"), "").unwrap();
251 assert_eq!(detect_languages(d.path()), vec![Language::Rust]);
252 }
253
254 #[test]
255 fn test_detect_languages_multi() {
256 let d = tempfile::tempdir().unwrap();
257 std::fs::write(d.path().join("Cargo.toml"), "").unwrap();
258 std::fs::write(d.path().join("pyproject.toml"), "").unwrap();
259 let langs = detect_languages(d.path());
260 assert!(langs.contains(&Language::Rust));
261 assert!(langs.contains(&Language::Python));
262 assert_eq!(langs.len(), 2);
263 }
264
265 #[test]
266 fn test_detect_languages_empty() {
267 let d = tempfile::tempdir().unwrap();
268 assert!(detect_languages(d.path()).is_empty());
269 }
270
271 #[test]
272 fn test_detect_languages_python_dedup() {
273 let d = tempfile::tempdir().unwrap();
275 std::fs::write(d.path().join("pyproject.toml"), "").unwrap();
276 std::fs::write(d.path().join("requirements.txt"), "").unwrap();
277 assert_eq!(detect_languages(d.path()), vec![Language::Python]);
278 }
279
280 #[test]
283 fn test_extract_kv_version_found() {
284 let c = "[package]\nname = \"test\"\nversion = \"1.2.3\"\n";
285 assert_eq!(extract_kv_version(c, "version"), Some("1.2.3".into()));
286 }
287
288 #[test]
289 fn test_extract_kv_version_not_found() {
290 assert_eq!(extract_kv_version("", "version"), None);
291 }
292
293 #[test]
294 fn test_extract_json_version_found() {
295 assert_eq!(
296 extract_json_version(r#"{"version": "1.0.0"}"#),
297 Some("1.0.0".into())
298 );
299 }
300
301 #[test]
302 fn test_extract_json_version_not_found() {
303 assert_eq!(extract_json_version("{}"), None);
304 }
305
306 #[test]
307 fn test_extract_kv_yaml_version_found() {
308 assert_eq!(
309 extract_kv_yaml_version("version: 0.2.0"),
310 Some("0.2.0".into())
311 );
312 }
313
314 #[test]
315 fn test_extract_kv_yaml_version_commented() {
316 assert_eq!(extract_kv_yaml_version("# version: 0.2.0"), None);
317 }
318
319 #[test]
320 fn test_read_config_versions_empty_dir() {
321 let d = tempfile::tempdir().unwrap();
322 assert!(read_config_versions(d.path()).is_empty());
323 }
324
325 #[test]
326 fn test_read_config_versions_cargo() {
327 let d = tempfile::tempdir().unwrap();
328 std::fs::write(
329 d.path().join("Cargo.toml"),
330 "[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
331 )
332 .unwrap();
333 let versions = read_config_versions(d.path());
334 assert_eq!(versions.len(), 1);
335 assert_eq!(versions[0].1.as_deref(), Some("0.1.0"));
336 }
337}