normalize_manifest/
julia.rs1use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
4use toml::Value;
5
6pub struct JuliaParser;
13
14impl ManifestParser for JuliaParser {
15 fn filename(&self) -> &'static str {
16 "Project.toml"
17 }
18
19 fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
20 let toml: Value = content
21 .parse::<Value>()
22 .map_err(|e| ManifestError(e.to_string()))?;
23
24 let name = toml
25 .get("name")
26 .and_then(|v| v.as_str())
27 .map(|s| s.to_string());
28 let version = toml
29 .get("version")
30 .and_then(|v| v.as_str())
31 .map(|s| s.to_string());
32
33 let compat: std::collections::HashMap<String, String> = toml
35 .get("compat")
36 .and_then(|v| v.as_table())
37 .map(|t| {
38 t.iter()
39 .filter(|(k, _)| k.as_str() != "julia")
40 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
41 .collect()
42 })
43 .unwrap_or_default();
44
45 let mut deps = Vec::new();
46
47 if let Some(table) = toml.get("deps").and_then(|v| v.as_table()) {
49 for (pkg_name, _uuid) in table {
50 let version_req = compat.get(pkg_name).cloned();
51 deps.push(DeclaredDep {
52 name: pkg_name.clone(),
53 version_req,
54 kind: DepKind::Normal,
55 });
56 }
57 }
58
59 if let Some(table) = toml.get("weakdeps").and_then(|v| v.as_table()) {
61 for (pkg_name, _uuid) in table {
62 let version_req = compat.get(pkg_name).cloned();
63 deps.push(DeclaredDep {
64 name: pkg_name.clone(),
65 version_req,
66 kind: DepKind::Optional,
67 });
68 }
69 }
70
71 Ok(ParsedManifest {
72 ecosystem: "julia",
73 name,
74 version,
75 dependencies: deps,
76 })
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::ManifestParser;
84
85 #[test]
86 fn test_julia_project_toml() {
87 let content = r#"
88name = "MyPackage"
89uuid = "a93c6f00-e57d-5684-b466-afe8fa294f38"
90version = "0.1.0"
91
92[deps]
93DataFrames = "a93c6f00-e57d-5684-b466-afe8fa294f38"
94HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
95
96[weakdeps]
97CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
98
99[compat]
100julia = "1.6"
101DataFrames = "1"
102HTTP = "0.9, 1"
103"#;
104 let m = JuliaParser.parse(content).unwrap();
105 assert_eq!(m.ecosystem, "julia");
106 assert_eq!(m.name.as_deref(), Some("MyPackage"));
107 assert_eq!(m.version.as_deref(), Some("0.1.0"));
108 assert_eq!(m.dependencies.len(), 3);
109
110 let df = m
111 .dependencies
112 .iter()
113 .find(|d| d.name == "DataFrames")
114 .unwrap();
115 assert_eq!(df.kind, DepKind::Normal);
116 assert_eq!(df.version_req.as_deref(), Some("1"));
117
118 let http = m.dependencies.iter().find(|d| d.name == "HTTP").unwrap();
119 assert_eq!(http.kind, DepKind::Normal);
120 assert_eq!(http.version_req.as_deref(), Some("0.9, 1"));
121
122 let cuda = m.dependencies.iter().find(|d| d.name == "CUDA").unwrap();
123 assert_eq!(cuda.kind, DepKind::Optional);
124 assert!(cuda.version_req.is_none());
125 }
126
127 #[test]
128 fn test_julia_no_compat() {
129 let content = r#"
130name = "Simple"
131version = "0.1.0"
132
133[deps]
134JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
135"#;
136 let m = JuliaParser.parse(content).unwrap();
137 assert_eq!(m.dependencies.len(), 1);
138 assert_eq!(m.dependencies[0].name, "JSON");
139 assert!(m.dependencies[0].version_req.is_none());
140 assert_eq!(m.dependencies[0].kind, DepKind::Normal);
141 }
142
143 #[test]
144 fn test_julia_compat_julia_skipped() {
145 let content = r#"
147name = "OnlyJuliaCompat"
148version = "0.1.0"
149
150[deps]
151Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
152
153[compat]
154julia = "1.9"
155Plots = "1"
156"#;
157 let m = JuliaParser.parse(content).unwrap();
158 assert_eq!(m.dependencies.len(), 1);
160 assert_eq!(m.dependencies[0].name, "Plots");
161 }
162}