use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
use serde_json::Value;
pub struct ElmParser;
impl ManifestParser for ElmParser {
fn filename(&self) -> &'static str {
"elm.json"
}
fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
let json: Value =
serde_json::from_str(content).map_err(|e| ManifestError(e.to_string()))?;
let elm_type = json.get("type").and_then(|v| v.as_str());
let is_application = elm_type == Some("application");
let name = json
.get("name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let version = json
.get("version")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let mut deps = Vec::new();
let dep_value = json.get("dependencies");
if is_application {
if let Some(direct) = dep_value
.and_then(|v| v.get("direct"))
.and_then(|v| v.as_object())
{
for (pkg, ver) in direct {
deps.push(DeclaredDep {
name: pkg.clone(),
version_req: ver.as_str().map(|s| s.to_string()),
kind: DepKind::Normal,
});
}
}
} else {
if let Some(obj) = dep_value.and_then(|v| v.as_object()) {
for (pkg, ver) in obj {
deps.push(DeclaredDep {
name: pkg.clone(),
version_req: ver.as_str().map(|s| s.to_string()),
kind: DepKind::Normal,
});
}
}
}
if let Some(test_deps) = json.get("test-dependencies") {
let direct_obj = test_deps.get("direct").and_then(|v| v.as_object());
let indirect_obj = test_deps.get("indirect").and_then(|v| v.as_object());
if direct_obj.is_some() || indirect_obj.is_some() {
for obj in [direct_obj, indirect_obj].into_iter().flatten() {
for (pkg, ver) in obj {
deps.push(DeclaredDep {
name: pkg.clone(),
version_req: ver.as_str().map(|s| s.to_string()),
kind: DepKind::Dev,
});
}
}
} else if let Some(obj) = test_deps.as_object() {
for (pkg, ver) in obj {
deps.push(DeclaredDep {
name: pkg.clone(),
version_req: ver.as_str().map(|s| s.to_string()),
kind: DepKind::Dev,
});
}
}
}
Ok(ParsedManifest {
ecosystem: "elm",
name,
version,
dependencies: deps,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ManifestParser;
#[test]
fn test_elm_application() {
let content = r#"{
"type": "application",
"source-directories": ["src"],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/core": "1.0.5",
"elm/html": "1.0.0"
},
"indirect": {
"elm/json": "1.1.3"
}
},
"test-dependencies": {
"direct": {
"elm-explorations/test": "2.1.2"
},
"indirect": {}
}
}"#;
let m = ElmParser.parse(content).unwrap();
assert_eq!(m.ecosystem, "elm");
let normal: Vec<_> = m
.dependencies
.iter()
.filter(|d| d.kind == DepKind::Normal)
.collect();
assert_eq!(normal.len(), 2);
assert!(normal.iter().any(|d| d.name == "elm/core"));
assert!(normal.iter().any(|d| d.name == "elm/html"));
let dev: Vec<_> = m
.dependencies
.iter()
.filter(|d| d.kind == DepKind::Dev)
.collect();
assert_eq!(dev.len(), 1);
assert_eq!(dev[0].name, "elm-explorations/test");
assert_eq!(dev[0].version_req.as_deref(), Some("2.1.2"));
}
#[test]
fn test_elm_package() {
let content = r#"{
"type": "package",
"name": "elm/html",
"summary": "Fast HTML",
"version": "1.0.0",
"elm-version": "0.19.0 <= v < 0.20.0",
"exposed-modules": ["Html"],
"dependencies": {
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/virtual-dom": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"elm-explorations/test": "1.0.0 <= v < 2.0.0"
}
}"#;
let m = ElmParser.parse(content).unwrap();
assert_eq!(m.ecosystem, "elm");
assert_eq!(m.name.as_deref(), Some("elm/html"));
assert_eq!(m.version.as_deref(), Some("1.0.0"));
let normal: Vec<_> = m
.dependencies
.iter()
.filter(|d| d.kind == DepKind::Normal)
.collect();
assert_eq!(normal.len(), 2);
let dev: Vec<_> = m
.dependencies
.iter()
.filter(|d| d.kind == DepKind::Dev)
.collect();
assert_eq!(dev.len(), 1);
assert_eq!(dev[0].name, "elm-explorations/test");
}
}