normalize_manifest/
purescript.rs1use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
8
9pub struct SpagoParser;
11
12impl ManifestParser for SpagoParser {
13 fn filename(&self) -> &'static str {
14 "spago.yaml"
15 }
16
17 fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
18 let mut name: Option<String> = None;
19 let mut version: Option<String> = None;
20 let mut deps: Vec<DeclaredDep> = Vec::new();
21
22 #[derive(PartialEq, Clone, Copy)]
23 enum Section {
24 None,
25 Package,
26 Dependencies,
27 }
28
29 let mut section = Section::None;
30
31 for line in content.lines() {
32 let trimmed = line.trim();
34
35 if trimmed.is_empty() || trimmed.starts_with('#') {
36 continue;
37 }
38
39 if !line.starts_with(' ') && !line.starts_with('\t') {
41 if trimmed.starts_with("package:") {
42 section = Section::Package;
43 } else {
44 section = Section::None;
45 }
46 continue;
47 }
48
49 if section == Section::Package {
51 if let Some(rest) = trimmed.strip_prefix("name:") {
53 let v = rest.trim().trim_matches('"').trim_matches('\'').to_string();
54 if !v.is_empty() && name.is_none() {
55 name = Some(v);
56 }
57 continue;
58 }
59 if let Some(rest) = trimmed.strip_prefix("version:") {
60 let v = rest.trim().trim_matches('"').trim_matches('\'').to_string();
61 if !v.is_empty() && version.is_none() {
62 version = Some(v);
63 }
64 continue;
65 }
66 if trimmed.starts_with("dependencies:") {
67 section = Section::Dependencies;
68 continue;
70 }
71 continue;
73 }
74
75 if section == Section::Dependencies {
76 if let Some(rest) = trimmed.strip_prefix("- ") {
78 if let Some(dep) = parse_spago_dep(rest) {
79 deps.push(dep);
80 }
81 continue;
82 }
83 if !trimmed.starts_with('-') {
87 section = Section::Package;
89 }
90 continue;
91 }
92 }
93
94 Ok(ParsedManifest {
95 ecosystem: "pursuit",
96 name,
97 version,
98 dependencies: deps,
99 })
100 }
101}
102
103fn parse_spago_dep(rest: &str) -> Option<DeclaredDep> {
105 let rest = rest.trim();
106 if rest.is_empty() {
107 return None;
108 }
109
110 if let Some(colon_pos) = rest.find(':') {
112 let pkg_name = rest[..colon_pos].trim().to_string();
113 let ver_str = rest[colon_pos + 1..].trim();
114 let version_req = if ver_str.is_empty() {
115 None
116 } else {
117 Some(ver_str.trim_matches('"').trim_matches('\'').to_string())
118 };
119 if !pkg_name.is_empty() {
120 return Some(DeclaredDep {
121 name: pkg_name,
122 version_req,
123 kind: DepKind::Normal,
124 });
125 }
126 }
127
128 let pkg_name = rest.trim_matches('"').trim_matches('\'').trim().to_string();
130 if pkg_name.is_empty() {
131 return None;
132 }
133 Some(DeclaredDep {
134 name: pkg_name,
135 version_req: None,
136 kind: DepKind::Normal,
137 })
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::ManifestParser;
144
145 const SAMPLE: &str = r#"package:
146 name: my-project
147 version: 0.1.0
148 dependencies:
149 - prelude
150 - effect
151 - console
152 - aff: ">=7.0.0 <8.0.0"
153
154workspace:
155 extra_packages: {}
156"#;
157
158 #[test]
159 fn test_parse_spago() {
160 let m = SpagoParser.parse(SAMPLE).unwrap();
161 assert_eq!(m.ecosystem, "pursuit");
162 assert_eq!(m.name.as_deref(), Some("my-project"));
163 assert_eq!(m.version.as_deref(), Some("0.1.0"));
164
165 let names: Vec<&str> = m.dependencies.iter().map(|d| d.name.as_str()).collect();
166 assert!(names.contains(&"prelude"), "{names:?}");
167 assert!(names.contains(&"effect"), "{names:?}");
168 assert!(names.contains(&"console"), "{names:?}");
169 assert!(names.contains(&"aff"), "{names:?}");
170
171 let aff = m.dependencies.iter().find(|d| d.name == "aff").unwrap();
172 assert_eq!(aff.version_req.as_deref(), Some(">=7.0.0 <8.0.0"));
173 }
174
175 #[test]
176 fn test_workspace_excluded() {
177 let content = r#"package:
178 name: lib
179 version: 1.0.0
180 dependencies:
181 - prelude
182
183workspace:
184 extra_packages: {}
185"#;
186 let m = SpagoParser.parse(content).unwrap();
187 assert_eq!(m.name.as_deref(), Some("lib"));
188 assert_eq!(m.dependencies.len(), 1);
189 }
190}