use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
pub struct NimbleParser;
impl ManifestParser for NimbleParser {
fn filename(&self) -> &'static str {
"*.nimble"
}
fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
let mut name = None;
let mut version = None;
let mut deps = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some(rest) = trimmed.strip_prefix("name")
&& let Some(val) = extract_assignment_string(rest)
{
name = Some(val);
continue;
}
if let Some(rest) = trimmed.strip_prefix("version")
&& let Some(val) = extract_assignment_string(rest)
{
version = Some(val);
continue;
}
if let Some(rest) = trimmed.strip_prefix("requires") {
extract_requires_strings(rest, &mut deps);
}
}
Ok(ParsedManifest {
ecosystem: "nimble",
name,
version,
dependencies: deps,
})
}
}
fn extract_assignment_string(rest: &str) -> Option<String> {
let rest = rest.trim().strip_prefix('=')?.trim();
extract_quoted(rest)
}
fn extract_quoted(s: &str) -> Option<String> {
let inner = s.strip_prefix('"')?;
let end = inner.find('"')?;
Some(inner[..end].to_string())
}
fn extract_requires_strings(rest: &str, out: &mut Vec<DeclaredDep>) {
let mut s = rest;
while let Some(start) = s.find('"') {
s = &s[start + 1..];
if let Some(end) = s.find('"') {
let spec = s[..end].trim();
if let Some(dep) = parse_nimble_spec(spec) {
out.push(dep);
}
s = &s[end + 1..];
} else {
break;
}
}
}
fn parse_nimble_spec(spec: &str) -> Option<DeclaredDep> {
let spec = spec.trim();
if spec.is_empty() {
return None;
}
const OPS: &[&str] = &[">=", "<=", "!=", ">", "<", "==", "~="];
for op in OPS {
if let Some(idx) = spec.find(op) {
let name = spec[..idx].trim().to_string();
if name.is_empty() || name == "nim" {
return None; }
let version_req = spec[idx..].trim().to_string();
return Some(DeclaredDep {
name,
version_req: Some(version_req),
kind: DepKind::Normal,
});
}
}
if spec == "nim" {
return None;
}
Some(DeclaredDep {
name: spec.to_string(),
version_req: None,
kind: DepKind::Normal,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ManifestParser;
#[test]
fn test_parse_nimble() {
let content = r#"# Package
name = "mypkg"
version = "0.1.0"
author = "Alice"
description = "My package"
license = "MIT"
# Dependencies
requires "nim >= 1.6.0"
requires "httpclient >= 1.0"
requires "json >= 0.9", "os"
"#;
let m = NimbleParser.parse(content).unwrap();
assert_eq!(m.ecosystem, "nimble");
assert_eq!(m.name.as_deref(), Some("mypkg"));
assert_eq!(m.version.as_deref(), Some("0.1.0"));
assert!(!m.dependencies.iter().any(|d| d.name == "nim"));
assert!(m.dependencies.iter().any(|d| d.name == "httpclient"));
assert!(m.dependencies.iter().any(|d| d.name == "json"));
assert!(m.dependencies.iter().any(|d| d.name == "os"));
}
}