quanttide_devops/source/
config_file.rs1use crate::contract::Language;
27
28use std::path::Path;
29
30type VersionExtract = fn(&str) -> Option<String>;
31
32pub fn detect_language(dir: &Path) -> Language {
46 if dir.join("Cargo.toml").exists() {
47 Language::Rust
48 } else if dir.join("pyproject.toml").exists() || dir.join("requirements.txt").exists() {
49 Language::Python
50 } else if dir.join("go.mod").exists() {
51 Language::Go
52 } else if dir.join("pubspec.yaml").exists() {
53 Language::Dart
54 } else if dir.join("package.json").exists() {
55 Language::TypeScript
56 } else {
57 Language::Unknown("无法识别".into())
58 }
59}
60
61pub fn read_config_versions(dir: &Path) -> Vec<(String, Option<String>)> {
76 let checks: &[(&str, VersionExtract)] = &[
77 ("Cargo.toml", |c| extract_kv_version(c, "version")),
78 ("pyproject.toml", |c| extract_kv_version(c, "version")),
79 ("package.json", extract_json_version),
80 ("pubspec.yaml", |c| extract_kv_yaml_version(c)),
81 ];
82 checks
83 .iter()
84 .filter_map(|(name, extract)| {
85 let path = dir.join(name);
86 if path.exists() {
87 let content = std::fs::read_to_string(&path).ok()?;
88 Some((name.to_string(), extract(&content)))
89 } else {
90 None
91 }
92 })
93 .collect()
94}
95
96fn extract_kv_version(content: &str, key: &str) -> Option<String> {
97 let p = format!("{} = \"", key);
98 for line in content.lines() {
99 let t = line.trim();
100 if let Some(r) = t.strip_prefix(&p)
101 && let Some(end) = r.find('"')
102 {
103 let v = r[..end].to_string();
104 if !v.is_empty() {
105 return Some(v);
106 }
107 }
108 }
109 None
110}
111
112fn extract_json_version(content: &str) -> Option<String> {
113 let key = r#""version":"#;
114 for line in content.lines() {
115 if let Some(pos) = line.find(key) {
116 let after_key = line[pos + 10..].trim();
117 if let Some(start) = after_key.find('"') {
118 let after_open = &after_key[start + 1..];
119 if let Some(end) = after_open.find('"') {
120 let v = &after_open[..end];
121 if !v.is_empty() {
122 return Some(v.to_string());
123 }
124 }
125 }
126 }
127 }
128 None
129}
130
131fn extract_kv_yaml_version(content: &str) -> Option<String> {
132 for line in content.lines() {
133 let t = line.trim();
134 if let Some(r) = t.strip_prefix("version:") {
135 let v = r.trim();
136 if !v.is_empty() && !v.starts_with('#') {
137 return Some(v.to_string());
138 }
139 }
140 }
141 None
142}
143
144#[cfg(test)]
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
152 fn test_detect_language_rust() {
153 let d = tempfile::tempdir().unwrap();
154 std::fs::write(d.path().join("Cargo.toml"), "").unwrap();
155 assert_eq!(detect_language(d.path()), Language::Rust);
156 }
157
158 #[test]
159 fn test_detect_language_unknown() {
160 let d = tempfile::tempdir().unwrap();
161 assert!(matches!(detect_language(d.path()), Language::Unknown(_)));
162 }
163
164 #[test]
165 fn test_detect_language_python() {
166 let d = tempfile::tempdir().unwrap();
167 std::fs::write(d.path().join("pyproject.toml"), "").unwrap();
168 assert_eq!(detect_language(d.path()), Language::Python);
169 }
170
171 #[test]
172 fn test_detect_language_python_requirements() {
173 let d = tempfile::tempdir().unwrap();
174 std::fs::write(d.path().join("requirements.txt"), "").unwrap();
175 assert_eq!(detect_language(d.path()), Language::Python);
176 }
177
178 #[test]
179 fn test_detect_language_go() {
180 let d = tempfile::tempdir().unwrap();
181 std::fs::write(d.path().join("go.mod"), "").unwrap();
182 assert_eq!(detect_language(d.path()), Language::Go);
183 }
184
185 #[test]
186 fn test_detect_language_dart() {
187 let d = tempfile::tempdir().unwrap();
188 std::fs::write(d.path().join("pubspec.yaml"), "").unwrap();
189 assert_eq!(detect_language(d.path()), Language::Dart);
190 }
191
192 #[test]
193 fn test_detect_language_typescript() {
194 let d = tempfile::tempdir().unwrap();
195 std::fs::write(d.path().join("package.json"), "").unwrap();
196 assert_eq!(detect_language(d.path()), Language::TypeScript);
197 }
198
199 #[test]
202 fn test_extract_kv_version_found() {
203 let c = "[package]\nname = \"test\"\nversion = \"1.2.3\"\n";
204 assert_eq!(extract_kv_version(c, "version"), Some("1.2.3".into()));
205 }
206
207 #[test]
208 fn test_extract_kv_version_not_found() {
209 assert_eq!(extract_kv_version("", "version"), None);
210 }
211
212 #[test]
213 fn test_extract_json_version_found() {
214 assert_eq!(
215 extract_json_version(r#"{"version": "1.0.0"}"#),
216 Some("1.0.0".into())
217 );
218 }
219
220 #[test]
221 fn test_extract_json_version_not_found() {
222 assert_eq!(extract_json_version("{}"), None);
223 }
224
225 #[test]
226 fn test_extract_kv_yaml_version_found() {
227 assert_eq!(
228 extract_kv_yaml_version("version: 0.2.0"),
229 Some("0.2.0".into())
230 );
231 }
232
233 #[test]
234 fn test_extract_kv_yaml_version_commented() {
235 assert_eq!(extract_kv_yaml_version("# version: 0.2.0"), None);
236 }
237
238 #[test]
239 fn test_read_config_versions_empty_dir() {
240 let d = tempfile::tempdir().unwrap();
241 assert!(read_config_versions(d.path()).is_empty());
242 }
243
244 #[test]
245 fn test_read_config_versions_cargo() {
246 let d = tempfile::tempdir().unwrap();
247 std::fs::write(
248 d.path().join("Cargo.toml"),
249 "[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
250 )
251 .unwrap();
252 let versions = read_config_versions(d.path());
253 assert_eq!(versions.len(), 1);
254 assert_eq!(versions[0].1.as_deref(), Some("0.1.0"));
255 }
256}