normalize_manifest/
stack.rs1use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
9
10pub struct StackParser;
12
13impl ManifestParser for StackParser {
14 fn filename(&self) -> &'static str {
15 "stack.yaml"
16 }
17
18 fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
19 let mut deps = Vec::new();
20 let mut in_extra_deps = false;
21 let mut list_indent = 0usize;
22
23 for line in content.lines() {
24 let trimmed = line.trim();
25 if trimmed.is_empty() || trimmed.starts_with('#') {
26 continue;
27 }
28
29 let indent = line.len() - line.trim_start().len();
30
31 if indent == 0 {
33 in_extra_deps = trimmed.starts_with("extra-deps:");
34 list_indent = 0;
35 if in_extra_deps && trimmed.contains('[') {
37 in_extra_deps = false;
39 }
40 continue;
41 }
42
43 if !in_extra_deps {
44 continue;
45 }
46
47 if trimmed.starts_with("- ") || trimmed.starts_with('-') {
49 if list_indent == 0 {
50 list_indent = indent;
51 }
52
53 let item = trimmed.trim_start_matches('-').trim();
54
55 if let Some(dep) = parse_stack_dep(item) {
56 deps.push(dep);
57 }
58 }
59 }
60
61 Ok(ParsedManifest {
62 ecosystem: "stackage",
63 name: None,
64 version: None,
65 dependencies: deps,
66 })
67 }
68}
69
70fn parse_stack_dep(item: &str) -> Option<DeclaredDep> {
71 let item = item.trim().trim_matches('"').trim_matches('\'');
72 if item.is_empty() {
73 return None;
74 }
75
76 if item == "git:" || item.starts_with("git:") {
78 return None;
79 }
80
81 let base = item.split('@').next().unwrap_or(item);
84
85 let name;
87 let version_req;
88
89 if let Some(ver_start) = find_version_start(base) {
90 name = base[..ver_start - 1].to_string(); version_req = Some(base[ver_start..].to_string());
92 } else {
93 name = base.to_string();
94 version_req = None;
95 }
96
97 if name.is_empty() {
98 return None;
99 }
100
101 Some(DeclaredDep {
102 name,
103 version_req,
104 kind: DepKind::Normal,
105 })
106}
107
108fn find_version_start(s: &str) -> Option<usize> {
111 let bytes = s.as_bytes();
112 (1..bytes.len())
114 .rev()
115 .find(|&i| bytes[i - 1] == b'-' && bytes[i].is_ascii_digit())
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::ManifestParser;
122
123 #[test]
124 fn test_parse_stack_yaml() {
125 let content = r#"resolver: lts-21.0
126
127packages:
128 - .
129
130extra-deps:
131 - acme-pkg-1.2.3
132 - aeson-2.1.2.1
133 - text-2.0.2@sha256:abc123
134"#;
135 let m = StackParser.parse(content).unwrap();
136 assert_eq!(m.ecosystem, "stackage");
137 assert_eq!(m.dependencies.len(), 3);
138
139 let acme = m
140 .dependencies
141 .iter()
142 .find(|d| d.name == "acme-pkg")
143 .unwrap();
144 assert_eq!(acme.version_req.as_deref(), Some("1.2.3"));
145
146 let text = m.dependencies.iter().find(|d| d.name == "text").unwrap();
147 assert_eq!(text.version_req.as_deref(), Some("2.0.2"));
148 }
149}