normalize_manifest/
dub.rs1use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
7use serde_json::Value;
8
9pub struct DubJsonParser;
11
12impl ManifestParser for DubJsonParser {
13 fn filename(&self) -> &'static str {
14 "dub.json"
15 }
16
17 fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
18 let json: Value =
19 serde_json::from_str(content).map_err(|e| ManifestError(e.to_string()))?;
20
21 let name = json
22 .get("name")
23 .and_then(|v| v.as_str())
24 .map(|s| s.to_string());
25 let version = json
26 .get("version")
27 .and_then(|v| v.as_str())
28 .map(|s| s.to_string());
29
30 let mut deps = Vec::new();
31 extract_dub_deps(&json, "dependencies", DepKind::Normal, &mut deps);
32 extract_dub_deps(&json, "devDependencies", DepKind::Dev, &mut deps);
33 extract_dub_deps(&json, "optionalDependencies", DepKind::Optional, &mut deps);
34
35 Ok(ParsedManifest {
36 ecosystem: "dub",
37 name,
38 version,
39 dependencies: deps,
40 })
41 }
42}
43
44fn extract_dub_deps(json: &Value, field: &str, kind: DepKind, out: &mut Vec<DeclaredDep>) {
45 let Some(obj) = json.get(field) else { return };
46
47 if let Some(map) = obj.as_object() {
48 for (name, ver) in map {
49 let version_req = if let Some(s) = ver.as_str() {
50 Some(s.to_string())
51 } else if let Some(obj) = ver.as_object() {
52 obj.get("version")
53 .and_then(|v| v.as_str())
54 .map(|s| s.to_string())
55 } else {
56 None
57 };
58 out.push(DeclaredDep {
59 name: name.clone(),
60 version_req,
61 kind,
62 });
63 }
64 }
65}
66
67pub struct DubSdlParser;
69
70impl ManifestParser for DubSdlParser {
71 fn filename(&self) -> &'static str {
72 "dub.sdl"
73 }
74
75 fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
76 let mut name = None;
77 let mut version = None;
78 let mut deps = Vec::new();
79
80 for line in content.lines() {
81 let trimmed = line.trim();
82 if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with('#') {
83 continue;
84 }
85
86 if trimmed.starts_with("name ") && name.is_none() {
88 name = extract_sdl_string(trimmed);
89 continue;
90 }
91 if trimmed.starts_with("version ") && version.is_none() {
93 version = extract_sdl_string(trimmed);
94 continue;
95 }
96
97 if trimmed.starts_with("dependency ")
100 && let Some(dep) = parse_sdl_dep(trimmed)
101 {
102 deps.push(dep);
103 }
104 }
105
106 Ok(ParsedManifest {
107 ecosystem: "dub",
108 name,
109 version,
110 dependencies: deps,
111 })
112 }
113}
114
115fn extract_sdl_string(line: &str) -> Option<String> {
116 let start = line.find('"')? + 1;
117 let end = line[start..].find('"')?;
118 Some(line[start..start + end].to_string())
119}
120
121fn parse_sdl_dep(line: &str) -> Option<DeclaredDep> {
122 let pkg_name = extract_sdl_string(line)?;
124
125 let version_req = if let Some(ver_start) = line.find("version=\"") {
127 let rest = &line[ver_start + 9..];
128 rest.find('"').map(|end| rest[..end].to_string())
129 } else {
130 None
131 };
132
133 let kind = if line.contains("optional=true") {
134 DepKind::Optional
135 } else {
136 DepKind::Normal
137 };
138
139 Some(DeclaredDep {
140 name: pkg_name,
141 version_req,
142 kind,
143 })
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::ManifestParser;
150
151 #[test]
152 fn test_dub_json() {
153 let content = r#"{
154 "name": "my-d-project",
155 "version": "0.1.0",
156 "dependencies": {
157 "vibe-d": "~>0.9",
158 "mir-algorithm": {"version": ">=3.10.0"}
159 }
160}"#;
161 let m = DubJsonParser.parse(content).unwrap();
162 assert_eq!(m.ecosystem, "dub");
163 assert_eq!(m.name.as_deref(), Some("my-d-project"));
164 assert_eq!(m.version.as_deref(), Some("0.1.0"));
165 assert_eq!(m.dependencies.len(), 2);
166
167 let vibe = m.dependencies.iter().find(|d| d.name == "vibe-d").unwrap();
168 assert_eq!(vibe.version_req.as_deref(), Some("~>0.9"));
169 }
170
171 #[test]
172 fn test_dub_sdl() {
173 let content = r#"name "my-d-project"
174version "0.1.0"
175dependency "vibe-d" version="~>0.9"
176dependency "mir-algorithm" version=">=3.10.0"
177"#;
178 let m = DubSdlParser.parse(content).unwrap();
179 assert_eq!(m.ecosystem, "dub");
180 assert_eq!(m.name.as_deref(), Some("my-d-project"));
181 assert_eq!(m.dependencies.len(), 2);
182
183 let mir = m
184 .dependencies
185 .iter()
186 .find(|d| d.name == "mir-algorithm")
187 .unwrap();
188 assert_eq!(mir.version_req.as_deref(), Some(">=3.10.0"));
189 }
190}