use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
pub struct SpagoParser;
impl ManifestParser for SpagoParser {
fn filename(&self) -> &'static str {
"spago.yaml"
}
fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
let mut name: Option<String> = None;
let mut version: Option<String> = None;
let mut deps: Vec<DeclaredDep> = Vec::new();
#[derive(PartialEq, Clone, Copy)]
enum Section {
None,
Package,
Dependencies,
}
let mut section = Section::None;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if !line.starts_with(' ') && !line.starts_with('\t') {
if trimmed.starts_with("package:") {
section = Section::Package;
} else {
section = Section::None;
}
continue;
}
if section == Section::Package {
if let Some(rest) = trimmed.strip_prefix("name:") {
let v = rest.trim().trim_matches('"').trim_matches('\'').to_string();
if !v.is_empty() && name.is_none() {
name = Some(v);
}
continue;
}
if let Some(rest) = trimmed.strip_prefix("version:") {
let v = rest.trim().trim_matches('"').trim_matches('\'').to_string();
if !v.is_empty() && version.is_none() {
version = Some(v);
}
continue;
}
if trimmed.starts_with("dependencies:") {
section = Section::Dependencies;
continue;
}
continue;
}
if section == Section::Dependencies {
if let Some(rest) = trimmed.strip_prefix("- ") {
if let Some(dep) = parse_spago_dep(rest) {
deps.push(dep);
}
continue;
}
if !trimmed.starts_with('-') {
section = Section::Package;
}
continue;
}
}
Ok(ParsedManifest {
ecosystem: "pursuit",
name,
version,
dependencies: deps,
})
}
}
fn parse_spago_dep(rest: &str) -> Option<DeclaredDep> {
let rest = rest.trim();
if rest.is_empty() {
return None;
}
if let Some(colon_pos) = rest.find(':') {
let pkg_name = rest[..colon_pos].trim().to_string();
let ver_str = rest[colon_pos + 1..].trim();
let version_req = if ver_str.is_empty() {
None
} else {
Some(ver_str.trim_matches('"').trim_matches('\'').to_string())
};
if !pkg_name.is_empty() {
return Some(DeclaredDep {
name: pkg_name,
version_req,
kind: DepKind::Normal,
});
}
}
let pkg_name = rest.trim_matches('"').trim_matches('\'').trim().to_string();
if pkg_name.is_empty() {
return None;
}
Some(DeclaredDep {
name: pkg_name,
version_req: None,
kind: DepKind::Normal,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ManifestParser;
const SAMPLE: &str = r#"package:
name: my-project
version: 0.1.0
dependencies:
- prelude
- effect
- console
- aff: ">=7.0.0 <8.0.0"
workspace:
extra_packages: {}
"#;
#[test]
fn test_parse_spago() {
let m = SpagoParser.parse(SAMPLE).unwrap();
assert_eq!(m.ecosystem, "pursuit");
assert_eq!(m.name.as_deref(), Some("my-project"));
assert_eq!(m.version.as_deref(), Some("0.1.0"));
let names: Vec<&str> = m.dependencies.iter().map(|d| d.name.as_str()).collect();
assert!(names.contains(&"prelude"), "{names:?}");
assert!(names.contains(&"effect"), "{names:?}");
assert!(names.contains(&"console"), "{names:?}");
assert!(names.contains(&"aff"), "{names:?}");
let aff = m.dependencies.iter().find(|d| d.name == "aff").unwrap();
assert_eq!(aff.version_req.as_deref(), Some(">=7.0.0 <8.0.0"));
}
#[test]
fn test_workspace_excluded() {
let content = r#"package:
name: lib
version: 1.0.0
dependencies:
- prelude
workspace:
extra_packages: {}
"#;
let m = SpagoParser.parse(content).unwrap();
assert_eq!(m.name.as_deref(), Some("lib"));
assert_eq!(m.dependencies.len(), 1);
}
}