normalize_manifest/
nimble.rs1use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
7
8pub struct NimbleParser;
15
16impl ManifestParser for NimbleParser {
17 fn filename(&self) -> &'static str {
18 "*.nimble"
19 }
20
21 fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
22 let mut name = None;
23 let mut version = None;
24 let mut deps = Vec::new();
25
26 for line in content.lines() {
27 let trimmed = line.trim();
28 if trimmed.is_empty() || trimmed.starts_with('#') {
29 continue;
30 }
31
32 if let Some(rest) = trimmed.strip_prefix("name")
34 && let Some(val) = extract_assignment_string(rest)
35 {
36 name = Some(val);
37 continue;
38 }
39 if let Some(rest) = trimmed.strip_prefix("version")
40 && let Some(val) = extract_assignment_string(rest)
41 {
42 version = Some(val);
43 continue;
44 }
45
46 if let Some(rest) = trimmed.strip_prefix("requires") {
48 extract_requires_strings(rest, &mut deps);
50 }
51 }
52
53 Ok(ParsedManifest {
54 ecosystem: "nimble",
55 name,
56 version,
57 dependencies: deps,
58 })
59 }
60}
61
62fn extract_assignment_string(rest: &str) -> Option<String> {
63 let rest = rest.trim().strip_prefix('=')?.trim();
64 extract_quoted(rest)
65}
66
67fn extract_quoted(s: &str) -> Option<String> {
68 let inner = s.strip_prefix('"')?;
69 let end = inner.find('"')?;
70 Some(inner[..end].to_string())
71}
72
73fn extract_requires_strings(rest: &str, out: &mut Vec<DeclaredDep>) {
74 let mut s = rest;
76 while let Some(start) = s.find('"') {
77 s = &s[start + 1..];
78 if let Some(end) = s.find('"') {
79 let spec = s[..end].trim();
80 if let Some(dep) = parse_nimble_spec(spec) {
81 out.push(dep);
82 }
83 s = &s[end + 1..];
84 } else {
85 break;
86 }
87 }
88}
89
90fn parse_nimble_spec(spec: &str) -> Option<DeclaredDep> {
91 let spec = spec.trim();
92 if spec.is_empty() {
93 return None;
94 }
95
96 const OPS: &[&str] = &[">=", "<=", "!=", ">", "<", "==", "~="];
98
99 for op in OPS {
100 if let Some(idx) = spec.find(op) {
101 let name = spec[..idx].trim().to_string();
102 if name.is_empty() || name == "nim" {
103 return None; }
105 let version_req = spec[idx..].trim().to_string();
106 return Some(DeclaredDep {
107 name,
108 version_req: Some(version_req),
109 kind: DepKind::Normal,
110 });
111 }
112 }
113
114 if spec == "nim" {
116 return None;
117 }
118 Some(DeclaredDep {
119 name: spec.to_string(),
120 version_req: None,
121 kind: DepKind::Normal,
122 })
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::ManifestParser;
129
130 #[test]
131 fn test_parse_nimble() {
132 let content = r#"# Package
133name = "mypkg"
134version = "0.1.0"
135author = "Alice"
136description = "My package"
137license = "MIT"
138
139# Dependencies
140requires "nim >= 1.6.0"
141requires "httpclient >= 1.0"
142requires "json >= 0.9", "os"
143"#;
144 let m = NimbleParser.parse(content).unwrap();
145 assert_eq!(m.ecosystem, "nimble");
146 assert_eq!(m.name.as_deref(), Some("mypkg"));
147 assert_eq!(m.version.as_deref(), Some("0.1.0"));
148
149 assert!(!m.dependencies.iter().any(|d| d.name == "nim"));
151 assert!(m.dependencies.iter().any(|d| d.name == "httpclient"));
152 assert!(m.dependencies.iter().any(|d| d.name == "json"));
153 assert!(m.dependencies.iter().any(|d| d.name == "os"));
154 }
155}